1. Overview

Field Value
Unpatched binary http_unpatched.sys
Patched binary http_patched.sys
Overall similarity 0.9915
Matched functions 3584
Changed functions 13 (behavioral + cosmetic)
Identical functions 3571
Unmatched (either direction) 0

Verdict: This patch is a Known Issue Rollback (KIR) servicing change. KIR flags are Microsoft's mechanism for turning a shipped code change on or off remotely (via registry/servicing) so a "known issue" can be rolled back without a new binary. The patch retires three KIR gates — UxKirHttpReadingTrailersFromUm, UxKirWritingAndReadingUm, and UxKirHKERangeRequestH2StatusCode — and adds one new gate, UxKirBranchCacheIgnoreResponseCaching. Retiring a KIR gate collapses its two branches into whichever behavior is now permanent.

No integer-overflow fix and no pool-overflow fix is present in this diff. The size arithmetic in the range-response builder is byte-for-byte identical between the two builds, and the trailer serializer bounds-checks every copy in both builds. The changes are limited to removing the KIR branch selectors and one KIR-gated HTTP status override.


2. Change Summary

Item #1 — Informational: Trailer serializer KIR-branch consolidation in UlGenerateTrailers (sub_1C000CF94)

  • Severity: Informational (no security-relevant change)
  • Class: Refactoring — Known Issue Rollback gate removal
  • Affected function: UlGenerateTrailers (sub_1C000CF94) (same address in both builds)

What the function does. UlGenerateTrailers (sub_1C000CF94) serializes a list of parsed HTTP trailer entries into a caller-supplied output buffer. Arguments: a2 = trailer list container (count at +0x8, entry array pointer at +0x10), a4 = output buffer, a5 = output buffer size, a6 = parsed-header sink, a7 = out bytes-written. Each entry is a 24-byte structure: name_len (16-bit) at +0x0, value_len (16-bit) at +0x2, name_ptr at +0x8, value_ptr at +0x10. The entry count is rejected if zero or >= 0x2710 (10000), returning 0xC000000D (STATUS_INVALID_PARAMETER).

The copy is bounds-checked in both builds. For every name and value the function calls UlProbeAnsiString (sub_1c0039258) to validate the source, then compares the length against the running remaining-buffer counter (v10, initialized from a5):

UlProbeAnsiString(name_ptr, name_len, ...);
if ((unsigned int)name_len > remaining) { status = 0xC000009A; goto done; } // STATUS_INSUFFICIENT_RESOURCES
memmove(out, name_ptr, name_len);
out += name_len;
remaining -= name_len;

The same guard protects the value copy. This check is present in the unpatched build, in the patched build, and in both sides of the KIR branch inside the unpatched build. There is no path in which memmove runs with a length larger than the tracked remaining space, so this function does not write past its output buffer.

What the KIR gate changed. In the unpatched build, UxKirHttpReadingTrailersFromUm (read at 0x1C000D0E5) selected between two structurally identical branches. The only difference: when the gate is nonzero the source pointer is taken from a value cached one line earlier (Src = *(entry+8)); when zero the same pointer is re-read from the entry (*(entry+8)) at the memmove. Both branches probe, both apply the identical len > remaining check, both decrement remaining the same way. The patched build removes the gate and always re-reads from the entry. This is a pointer-caching cleanup, not a security boundary change.

Item #2 — Informational: Range-response KIR status override removed in UlPrepareParsedHeaderRangeResponse (sub_1C005E348)

  • Severity: Informational (no security-relevant change)
  • Affected function: UlPrepareParsedHeaderRangeResponse (sub_1C005E348) (patched at 0x1C005E208)

What the function does. UlPrepareParsedHeaderRangeResponse (sub_1C005E348) builds an HTTP byte-range (multipart/206) response. It counts range/segment overlaps, computes a header buffer size, allocates it with ExAllocatePool3, then fills the multipart boundary and Content-Range headers via UlpGenerateRangeResponseHeaders (sub_1C005F748).

The size arithmetic is identical in both builds. In both http_unpatched.sys and http_patched.sys the allocation size is computed as:

v23 = (unsigned int)(v21 + v5);   // v21 = 2*range_count + 1 ; v5 = overlap count
v24 = v22 + 80 * v23;             // v22 = fixed header base ; 32-bit unsigned
Pool3 = ExAllocatePool3(64, v24, 'UlyF', &UxLowPriorityPool, 1);

v24 is a 32-bit unsigned value and 80 * v23 is a 32-bit multiply. This expression is byte-for-byte the same in both builds (unpatched http_unpatched.c and patched http_patched.c both read v24 = v22 + 80 * v23). The patch does not widen it, cap it, or add an overflow check. The header region it sizes is filled by UlpGenerateRangeResponseHeaders (sub_1C005F748), which receives the remaining size v30 as an explicit bound; it is a separate buffer from anything touched by UlGenerateTrailers.

What the KIR gate changed. The unpatched build contained a block gated on UxKirHKERangeRequestH2StatusCode that, after the range headers were generated, forced the response status to 206 across three fields when it was not already 206:

if (UxKirHKERangeRequestH2StatusCode != 0 && *(WORD*)(a1+64) != 206) {
    *(WORD*)(*(QWORD*)(a1+24) + 1692) = 206;
    *(WORD*)(a1+64) = 206;
    if (*(QWORD*)(a1+400)) *(WORD*)(*(QWORD*)(a1+400) + 120) = 206;
}

The patched build removes this block. Because the block runs after the buffer is already sized and the range headers already generated, it does not feed back into the allocation size. It is a response-status behavior toggle, not a memory-safety change.


3. Pseudocode Diff

UlGenerateTrailers (sub_1C000CF94) — trailer serializer

// === UNPATCHED — KIR gate selects source-pointer handling ===
for (i = 0; i < count; i++) {                       // count < 0x2710
    name_len = entry[i].name_len;                   // 16-bit
    if (name_len == 0) continue;
    src = entry[i].name_ptr;
    if (UxKirHttpReadingTrailersFromUm != 0) {       // UxKirHttpReadingTrailersFromUm gate
        UlProbeAnsiString(src, name_len, ...);
        if (name_len > remaining) return 0xC000009A; // bounds check PRESENT
        memmove(out, src, name_len);                 // cached pointer
    } else {
        UlProbeAnsiString(src, name_len, ...);
        if (name_len > remaining) return 0xC000009A; // bounds check PRESENT
        memmove(out, entry[i].name_ptr, name_len);   // re-read pointer
    }
    out += name_len; remaining -= name_len;
    // ... identical structure for value_len ...
    status = UlSetParsedHeader(sink, name, name_len, value, value_len);
}
*bytes_written = a5 - remaining;

// === PATCHED — gate removed, single re-read path ===
for (i = 0; i < count; i++) {
    name_len = entry[i].name_len;
    if (name_len == 0) continue;
    UlProbeAnsiString(entry[i].name_ptr, name_len, ...);
    if (name_len > remaining) return 0xC000009A;      // same bounds check
    memmove(out, entry[i].name_ptr, name_len);
    out += name_len; remaining -= name_len;
    // ... value_len handled the same way ...
}

UlPrepareParsedHeaderRangeResponse (sub_1C005E348) — range response builder

// Size arithmetic — IDENTICAL in both builds:
v23 = (unsigned int)(2*range_count + 1 + overlap_count);
v24 = header_base + 80 * v23;                 // 32-bit; unchanged by patch
pool = ExAllocatePool3(64, v24, 'UlyF', &UxLowPriorityPool, 1);
hdr  = pool + 80*v23; rem = v24 - 80*v23;
if (range_count > 1) UlpGenerateCommonBoundaryBuffer(hdr, ...);
status = UlpGenerateRangeResponseHeaders(range_count, ..., hdr, rem, ...);

// === UNPATCHED only, AFTER sizing/filling ===
if (UxKirHKERangeRequestH2StatusCode != 0 && status_word != 206)
    force status to 206 in three fields;   // removed in patched

// === PATCHED ===
// KIR block gone; status is not force-promoted here.

4. Assembly Analysis

UlGenerateTrailers (sub_1C000CF94) — KIR gate and guarded copies (unpatched)

0x1C000CF94  mov     r11, rsp
0x1C000CFB2  mov     r14, r9                  ; r14 = output buffer (arg4)
             mov     rsi, rdx                 ; rsi = trailer list (arg2)
             mov     r15d, [rsp+0xd0]         ; r15d = output buffer size (arg5)
             mov     edi, r15d                ; edi  = remaining counter
0x1C000D021  movzx   r12d, word [rsi+8]       ; trailer count (16-bit)
             mov     eax, 0x2710              ; reject count >= 10000
             cmp     r12w, ax
             jb      ...                       ; STATUS_INVALID_PARAMETER otherwise
0x1C000D0A8  movzx   r12d, word [r13+rax*8]   ; name_len (16-bit)
0x1C000D0E5  cmp     cs:UxKirHttpReadingTrailersFromUm, 0   ; KIR gate (removed in patch)
             call    UlProbeAnsiString        ; sub_1c0039258 — validate source
0x1C000D12A  cmp     ebx, edi                 ; name_len > remaining ?  BOUNDS CHECK
             jbe     ...                       ; if too big -> STATUS_INSUFFICIENT_RESOURCES
             call    memmove                  ; sub_1c0021580 — guarded copy
             add     r14, rbx                 ; out += name_len
             sub     edi, eax                 ; remaining -= name_len

The cmp ebx, edi guard at 0x1C000D12A (and its counterpart for the value copy) precedes every memmove, so no copy can exceed remaining. The over-length path sets 0xC000009A (STATUS_INSUFFICIENT_RESOURCES) and returns.

What the patch changes at the assembly level

  • cmp cs:UxKirHttpReadingTrailersFromUm, 0 at 0x1C000D0E5 and its branch are gone; the trailer loop keeps only the re-read source path. The per-copy cmp len, remaining guard remains.
  • In UlPrepareParsedHeaderRangeResponse (sub_1C005E348), the cmp cs:UxKirHKERangeRequestH2StatusCode, 0 block (at 0x1C005E5EB in unpatched) that force-set status 206 is removed. The v24 = header_base + 80 * v23 allocation size is unchanged.
  • In UxStartEnvironmentModule (sub_1C01767B0) the KIR initializers change: the unpatched build sets UxKirChunkExtHeaderFix, UxKirSetCbtHardeningAsMedium, UxKirHttpReadingTrailersFromUm, UxKirWritingAndReadingUm, UxKirHKERangeRequestH2StatusCode (five setnz cs:... at 0x1C017683D0x1C0176880); the patched build sets UxKirChunkExtHeaderFix, UxKirBranchCacheIgnoreResponseCaching, UxKirSetCbtHardeningAsMedium (three setnz cs:... at 0x1C017583D0x1C0175864).

5. Reachability Notes

UlGenerateTrailers (sub_1C000CF94) is called from UlFastSendHttpResponse (sub_1C00E31F0) with the output size taken from a response-structure field (*(a4 + 152)), and from a second response-build site. The size it receives is not derived from the range-response arithmetic in UlPrepareParsedHeaderRangeResponse; the two functions build different buffers and are not chained. The trailer count is capped at 0x2710 and each copy is bounds-checked against the remaining output space, so attacker-controlled trailer names/values cannot drive a write past the buffer.

UlPrepareParsedHeaderRangeResponse (sub_1C005E348) is reached on the range-response send path when a client requests byte ranges. Its 80 * v23 multiply is a 32-bit computation in both builds; whether v23 can be driven large enough to wrap is a property of both binaries equally and is unchanged by this patch. Nothing in the diff alters that arithmetic, adds a cap, or adds an overflow check.


6. Changed Functions — Full Triage

KIR-gate removals (behavior toggles, not memory-safety changes):

  • UlGenerateTrailers (sub_1C000CF94) — sim 0.7166 — Trailer serializer. Removed the UxKirHttpReadingTrailersFromUm gate that chose between a cached source pointer and a re-read; consolidated to the re-read path. Per-copy len > remaining bounds check present in both builds.
  • UlpCheckIfBypassRangeProcessing (sub_1C005F19C) — sim 0.7231 — Range-processing bypass decision. Removed UxKirHKERangeRequestH2StatusCode gating around a 206 status trace/UlpHkeBuilderModifyStatusCode (sub_1c01599d8) path; the condition ordering for the 206 bypass is simplified accordingly.
  • UlPrepareParsedHeaderRangeResponse (sub_1C005E348) — sim 0.9774 — Range/multipart response builder. Removed the UxKirHKERangeRequestH2StatusCode block that forced status to 206 after sizing. The v24 = header_base + 80 * v23 allocation size is identical between builds.
  • UlpGenerateRangeResponseHeaders (sub_1C005F748) — sim 0.879 — Content-Range/Accept-Ranges header emitter for 206 responses. Removed the UxKirHKERangeRequestH2StatusCode-gated UlpHkeBuilderModifyStatusCode (sub_1c01599d8) status-reporting call.
  • UlpCompleteSendToExtensionWorker (sub_1C0061260) — sim 0.9709 — Send-response completion callback (sendresponse.h:0x358). The range-response case (rcx_1 == 2) now has separate handling instead of sharing the rcx_1 == 1 cleanup path. Completion-path change; no memory-safety impact.
  • UlpRebuildResponseHeaders (sub_1C013F860) — sim 0.8734 — HTTP/2 response header rebuild under PushLockExclusive. Feature-gate consolidation on UlReInitializeParsedHeaders (sub_1c0020ac4/sub_1c0020e34) calls and reordered error-path cleanup. Refactoring only.
  • UxQuicConnectionCopyBindings (sub_1C0047B78) — sim 0.8581 — Removed the UxKirWritingAndReadingUm branch; register renaming (r12→r15). In the a2 != 0 path the gate had selected between a full 64-bit destination pointer and a 32-bit-truncated one; the patched build keeps the truncated-destination path (the gate-clear default). The memmove length (*(v11+8)) and the if (a4 >= size) write guard are identical in both builds.
  • UxStartEnvironmentModule (sub_1C01767B0) — sim 0.9156 — Environment-module startup. Removed the KIR initializers for UxKirHttpReadingTrailersFromUm, UxKirWritingAndReadingUm, and UxKirHKERangeRequestH2StatusCode; added the initializer for UxKirBranchCacheIgnoreResponseCaching. This is where the retired gates stop being populated at runtime and the new gate begins.

Cosmetic / compiler-generated:

  • UlHkeBuilderCopyFixedHeaders (sub_1C015889C) — sim 0.965 — Consumes the new UxKirBranchCacheIgnoreResponseCaching gate: the response-caching field write *(arg6+20) = *(arg6+8) is now guarded by if (UxKirBranchCacheIgnoreResponseCaching != 0 && arg6) instead of if (arg6). The per-header copy guard (if (v11 < value_len + name_len + 4) return STATUS_INVALID_PARAMETER) and all memmove lengths are identical in both builds. Callee addresses shifted by recompilation (memmove sub_1c0021580sub_1c0021200, UlSetParsedHeader sub_1c00e23e0sub_1c00e13e0).
  • wil_details_FeatureReporting_IncrementOpportunityInCache (sub_1C001EE38) and wil_details_FeatureReporting_IncrementUsageInCache (sub_1C001EF28) — sim ~0.930 — WIL feature-staging/telemetry cache helpers. Do-while → while(true)+break compiler transform and register renaming; a final assignment now uses the loop's live result variable. Not security-relevant.
  • wil_details_FeatureReporting_RecordUsageInCache (sub_1C001F020) — sim 0.9771 — WIL feature-staging/telemetry helper; atomic bit-manipulation loop rewritten by the compiler with register renaming. Logic unchanged.
  • sub_1C000D25B — sim 0.9763 — This is not a function entry: 0x1C000D25B (mov ebx, eax) falls inside UlGenerateTrailers (sub_1C000CF94), and no function begins there in either build. The delta at this address is part of the same UxKirHttpReadingTrailersFromUm gate consolidation already covered under UlGenerateTrailers above.

7. Unmatched Functions

None added, none removed at the function level. The diff is in-place modification of existing functions: retirement of three Known Issue Rollback gates (UxKirHttpReadingTrailersFromUm, UxKirWritingAndReadingUm, UxKirHKERangeRequestH2StatusCode) and introduction of one (UxKirBranchCacheIgnoreResponseCaching), plus the compiler-generated churn above.


8. Confidence & Caveats

Confidence: High. The KIR flag names resolve directly from the symbolized disassembly (UxKirHttpReadingTrailersFromUm at 0x1C000D0E5, UxKirHKERangeRequestH2StatusCode at 0x1C005E5EB, UxKirWritingAndReadingUm at 0x1C0047C2B), the five-to-three setnz cs:... change in UxStartEnvironmentModule (sub_1C01767B0) is explicit, and the range-response size arithmetic v24 = v22 + 80 * v23 is textually identical in both decompilations.

Key points a reader should retain:

  • The trailer serializer UlGenerateTrailers (sub_1C000CF94) bounds-checks every memmove against the remaining output buffer in both builds; the KIR gate only toggled whether the source pointer was cached or re-read. No out-of-bounds write path exists here.
  • The 32-bit 80 * v23 multiply in UlPrepareParsedHeaderRangeResponse (sub_1C005E348) is unchanged by this patch. If a researcher wishes to study that arithmetic, it must be studied as a property common to both binaries; it is not what this servicing update touches.
  • The changes are Known Issue Rollback lifecycle churn: features previously guarded by a rollback switch are now permanent (gate removed), and a new feature gains its own rollback switch. This is consistent with the very high overall similarity (0.9915) and the small, gate-local nature of each delta.