http.sys — Known Issue Rollback gate removal
KB5078883
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 at0x1C005E208)
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, 0at0x1C000D0E5and its branch are gone; the trailer loop keeps only the re-read source path. The per-copycmp len, remainingguard remains.- In
UlPrepareParsedHeaderRangeResponse (sub_1C005E348), thecmp cs:UxKirHKERangeRequestH2StatusCode, 0block (at0x1C005E5EBin unpatched) that force-set status 206 is removed. Thev24 = header_base + 80 * v23allocation size is unchanged. - In
UxStartEnvironmentModule (sub_1C01767B0)the KIR initializers change: the unpatched build setsUxKirChunkExtHeaderFix,UxKirSetCbtHardeningAsMedium,UxKirHttpReadingTrailersFromUm,UxKirWritingAndReadingUm,UxKirHKERangeRequestH2StatusCode(fivesetnz cs:...at0x1C017683D–0x1C0176880); the patched build setsUxKirChunkExtHeaderFix,UxKirBranchCacheIgnoreResponseCaching,UxKirSetCbtHardeningAsMedium(threesetnz cs:...at0x1C017583D–0x1C0175864).
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 theUxKirHttpReadingTrailersFromUmgate that chose between a cached source pointer and a re-read; consolidated to the re-read path. Per-copylen > remainingbounds check present in both builds.UlpCheckIfBypassRangeProcessing (sub_1C005F19C)— sim 0.7231 — Range-processing bypass decision. RemovedUxKirHKERangeRequestH2StatusCodegating 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 theUxKirHKERangeRequestH2StatusCodeblock that forced status to 206 after sizing. Thev24 = header_base + 80 * v23allocation size is identical between builds.UlpGenerateRangeResponseHeaders (sub_1C005F748)— sim 0.879 —Content-Range/Accept-Rangesheader emitter for 206 responses. Removed theUxKirHKERangeRequestH2StatusCode-gatedUlpHkeBuilderModifyStatusCode (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 thercx_1 == 1cleanup path. Completion-path change; no memory-safety impact.UlpRebuildResponseHeaders (sub_1C013F860)— sim 0.8734 — HTTP/2 response header rebuild underPushLockExclusive. Feature-gate consolidation onUlReInitializeParsedHeaders (sub_1c0020ac4/sub_1c0020e34)calls and reordered error-path cleanup. Refactoring only.UxQuicConnectionCopyBindings (sub_1C0047B78)— sim 0.8581 — Removed theUxKirWritingAndReadingUmbranch; register renaming (r12→r15). In thea2 != 0path 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 theif (a4 >= size)write guard are identical in both builds.UxStartEnvironmentModule (sub_1C01767B0)— sim 0.9156 — Environment-module startup. Removed the KIR initializers forUxKirHttpReadingTrailersFromUm,UxKirWritingAndReadingUm, andUxKirHKERangeRequestH2StatusCode; added the initializer forUxKirBranchCacheIgnoreResponseCaching. 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 newUxKirBranchCacheIgnoreResponseCachinggate: the response-caching field write*(arg6+20) = *(arg6+8)is now guarded byif (UxKirBranchCacheIgnoreResponseCaching != 0 && arg6)instead ofif (arg6). The per-header copy guard (if (v11 < value_len + name_len + 4) return STATUS_INVALID_PARAMETER) and allmemmovelengths are identical in both builds. Callee addresses shifted by recompilation (memmovesub_1c0021580→sub_1c0021200,UlSetParsedHeadersub_1c00e23e0→sub_1c00e13e0).wil_details_FeatureReporting_IncrementOpportunityInCache (sub_1C001EE38)andwil_details_FeatureReporting_IncrementUsageInCache (sub_1C001EF28)— sim ~0.930 — WIL feature-staging/telemetry cache helpers. Do-while →while(true)+breakcompiler 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 insideUlGenerateTrailers (sub_1C000CF94), and no function begins there in either build. The delta at this address is part of the sameUxKirHttpReadingTrailersFromUmgate consolidation already covered underUlGenerateTrailersabove.
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 everymemmoveagainst 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 * v23multiply inUlPrepareParsedHeaderRangeResponse (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.