1. Overview

  • Unpatched Binary: http_unpatched.sys
  • Patched Binary: http_patched.sys
  • Overall Similarity: 0.9105 (91.05%)
  • Diff Statistics: Matched: 3865 | Changed: 282 | Identical: 3583 | Unmatched: 0 (both directions)
  • Verdict: The patched build introduces a new global, UxKirNewUma, which is assigned from a staged feature flag at init: UxKirNewUma = Feature_NewUMAHttpSys__private_IsEnabledDeviceUsageNoInline() != 0. Across the IOCTL structure-parsing functions the patch adds an alternate code path taken only when UxKirNewUma != 0, which alignment-checks embedded pointers, reads each length/pointer field through user-memory helpers (RtlReadUShortFromUser, RtlReadULong64FromUser, RtlReadULongFromUser), and copies payload bytes with a fault-tolerant routine (UxTryCopyFromMode) instead of memmove. When UxKirNewUma == 0 (the default state, and every code path in the unpatched build) the patched code executes the identical legacy path. The old path is fully retained in the else branch, so in its default configuration the patched binary is behaviorally identical to the unpatched binary. This is a feature-staged rollout of a user-mode-access hardening refactor, not a delivered fix for a demonstrable, reachable vulnerability.

2. Vulnerability Summary

  • Severity: Informational (no security-relevant change delivered)
  • Vulnerability Class: Not applicable. The change is a defense-in-depth user-mode-access refactor (single-capture reads, pointer-alignment checks, structured-exception-guarded copy) staged behind a runtime feature flag. No CWE is asserted because no demonstrable, reachable memory-safety defect is fixed in the shipped default path.
  • Affected Functions: UlpCopySingleMultipleHeader (unpatched 0x1400DBD58 / patched 0x1400DB3B0), UlpGetMultipleKnownHeader (unpatched 0x1400DD284 / patched 0x1400DCD90), UlpComputeAuthHeaderSize (unpatched 0x1400DB618 / patched 0x1400DA0C0), UlReceiveHttpRequestFastIo (unpatched 0x140014E50 / patched 0x140094DC0).
  • Root Cause / Nature of Change: The patched build adds a runtime-gated alternate path across the IOCTL structure-parsing functions. The gate is the global UxKirNewUma, which is not a security check but a Windows feature-staging flag set from Feature_NewUMAHttpSys__private_IsEnabledDeviceUsageNoInline() during initialization (alongside the other UxKir* feature/rollback gates). When the flag is clear, the parsers run the same logic as the unpatched build: one coarse UlProbeForRead of the header array, direct reads of each entry's length and value pointer, a per-value UlProbeAnsiString readability probe, a destination bounds check before the write, and a memmove copy. When the flag is set, the added path pre-validates array alignment (UlpProbeAlignmentOfHeaders, patched 0x1400DDAF4), reads each field once through RtlRead*FromUser helpers, enforces pointer alignment (ptr & 7 for 64-bit / ptr & 3 for 32-bit) returning STATUS_INVALID_PARAMETER (0xC000000D), re-validates each value pointer (UlProbeAlignmentAnsiString, patched 0x1400D4D74), and copies through UxTryCopyFromMode. Because the legacy path is retained verbatim in the UxKirNewUma == 0 branch and the flag is off by default, the patched binary as shipped behaves identically to the unpatched binary. The legacy path already probes each value pointer before copying and performs a destination bounds check (if ( a7 < *v13 + v34 + 3 ) / &v13[v19 + *v23 + 3] <= &a8[a7]) before the write; the header length is a 16-bit field. No out-of-bounds primitive is demonstrable from either build's default behavior.
  • Attacker-Reachable Entry Point & Data Flow: These functions are reached through the HTTP Server API (HttpSendHttpResponse / HttpReceiveHttpRequest / HttpSetServiceConfigurationNtDeviceIoControlFile → the driver's IOCTL dispatch → the request/response structure handlers). The data flow is real, but the added validation is not active in the default configuration and no demonstrable defect exists in the retained legacy path, so this reachability does not establish a delivered fix.

3. Pseudocode Diff

UlpCopySingleMultipleHeader — unpatched 0x1400DBD58 vs patched 0x1400DB3B0

The unpatched function has a single path. The patched function keeps that exact path under if ( UxKirNewUma == 0 ) and adds a second path under else for the feature-on state.

// === UNPATCHED 0x1400DBD58 (single path) ===
v14 = IoIs32bitProcess(a1);
UlProbeForRead(v9, v10 * (... ? 8 : 16), v14 ? 4 : 8, a2);   // one coarse probe
for ( i = 0; i < a3; ++i ) {
    v17 = &v9[16 * i];                       // 64-bit entry, array base not alignment-checked
    v18 = *((volatile void **)v17 + 1);      // value pointer, captured once into Src
    v19 = *(unsigned __int16 *)v17;          // length, captured once
    if ( (_WORD)v19 != 0 ) {
        UlProbeAnsiString(v18, v19, v16);                    // per-value readability probe
        if ( &v13[v19 + *v23 + 3] <= &a8[a7] ) {             // destination bounds check
            memmove(v13, v23 + 16, *v23);                    // copy name
            memmove(v20 + 1, Src, v19);                      // copy value via memmove
            v12 = UlSetParsedHeader(a6, v23 + 64, *v23, v20 + 1, v19);
        } else return STATUS_INSUFFICIENT_RESOURCES;
    }
}

// === PATCHED 0x1400DB3B0 ===
if ( UxKirNewUma == 0 ) {
    // identical to the unpatched single path above (UlProbeForRead, direct reads,
    // UlProbeAnsiStringOld, destination bounds check, memmove name + memmove value)
} else {
    v12 = UlpProbeAlignmentOfHeaders(a1, a3, a4);            // added array-wide alignment pre-validation
    if ( v12 < 0 ) return v12;
    for ( j = 0; j < a3; ++j ) {
        UShortFromUser = RtlReadUShortFromUser(&v9[16*j]);       // single-capture length (guarded)
        Ptr32FromMode  = RtlReadULong64FromUser(v26 + 8);        // single-capture value ptr (guarded)
        if ( UShortFromUser != 0 ) {
            v12 = UlProbeAlignmentAnsiString(Ptr32FromMode, UShortFromUser);   // per-value alignment+probe
            if ( v12 < 0 ) return v12;
            if ( &v14[UShortFromUser + 3 + *Src] > &a8[a7] ) return STATUS_INSUFFICIENT_RESOURCES;
            memmove(v14, Src + 16, *Src);                        // copy name
            v12 = UxTryCopyFromMode(v27 + 1, Ptr32FromMode, UShortFromUser);   // fault-tolerant value copy
            v12 = UlSetParsedHeader(a6, Src + 64, *Src, v27 + 1, UShortFromUser);
        }
    }
}

The two branches differ only by the added alignment/single-capture/fault-tolerant-copy logic; the legacy branch is retained unchanged. Note the legacy branch already performs the destination bounds check and the per-value probe present in the unpatched build.

4. Assembly Analysis

The following instructions are real and present in the unpatched build. They are the legacy path that is retained verbatim in the patched build's UxKirNewUma == 0 branch; they are not evidence of a delivered fix.

Unpatched UlpGetMultipleKnownHeader (0x1400DD284)

00000001400DD2B2  mov     r14, [rdx+8]              ; embedded descriptor ptr
00000001400DD300  movups  xmm1, xmmword ptr [r14]   ; direct read of the descriptor
00000001400DD366  cmp     cx, ax                    ; count < 0x2710 (10000)
00000001400DD3D0  cmp     esi, 0x1d                 ; index <= 29

In the patched UlpGetMultipleKnownHeader (0x1400DCD90) the descriptor pointer from [a2+8] is alignment-checked (ptr & 7 for 64-bit, ptr & 3 for 32-bit) returning 0xC000000D, and the 0x18-byte descriptor is read field-by-field through RtlReadULongFromUser / RtlReadUShortFromUser — but only inside the if ( UxKirNewUma != 0 ) branch. The if ( UxKirNewUma == 0 ) branch retains the UlProbeForRead + direct-read logic. The count < 0x2710 and index <= 0x1d checks exist in both builds.

Unpatched UlpCopySingleMultipleHeader (0x1400DBD58) — copy loop

00000001400DBE4F  mov     rcx, [rax+8]             ; value pointer load (captured once)
00000001400DBED5  call    memmove                  ; copy value

In the patched build these instructions remain in the UxKirNewUma == 0 branch; the UxKirNewUma != 0 branch substitutes RtlReadULong64FromUser for the value-pointer load and UxTryCopyFromMode for the value memmove, and adds UlpProbeAlignmentOfHeaders before the loop and UlProbeAlignmentAnsiString per entry.

5. Trigger Conditions

There is no demonstrable security trigger. The functions are reachable from the HTTP Server API IOCTL surface (HttpInitialize(HTTP_INITIALIZE_SERVER) → queue handle → HttpSendHttpResponse / HttpReceiveHttpRequest / HttpSetServiceConfiguration), but in both builds' default path each header entry's value pointer is validated by UlProbeAnsiString and the destination write is guarded by the bounds check &v13[v19 + *v23 + 3] <= &a8[a7]; the entry length is a 16-bit field. No input has been shown to reach an out-of-bounds read or write in either build. The only behavioral difference requires the staged feature Feature_NewUMAHttpSys to be enabled at runtime, which does not change the shipped default behavior.

6. Exploit Primitive & Development Notes

No exploit primitive is demonstrable. The legacy path retained in both builds probes each value pointer before the copy and bounds-checks the destination before the write, and the length field is 16-bit. Neither an out-of-bounds read (information disclosure) nor an out-of-bounds write (pool corruption) has been shown to be reachable in the default path, so no primitive, grooming strategy, or mitigation-bypass claim is asserted.

7. Observation Playbook

The addresses below are real and can be used to observe where the feature-gated path diverges. They are diagnostic anchors for the code difference, not a proof-of-concept for a vulnerability.

  • Divergence points: text bp http+0xDBD58 ; UlpCopySingleMultipleHeader entry (patched 0x1400DB3B0) bp http+0xDBED5 ; legacy value memmove (retained under UxKirNewUma == 0) bp http+0xDD284 ; UlpGetMultipleKnownHeader entry (patched 0x1400DCD90)
  • What to inspect:
  • 0x1400DBE4F (mov rcx, [rax+8]): per-entry value-pointer load in the legacy path.
  • 0x1400DBED5 (call memmove): legacy value copy; in the feature-on path this is replaced by UxTryCopyFromMode.
  • 0x1400DD2B2 (mov r14, [rdx+8]) / 0x1400DD300 (movups [r14]): legacy descriptor read; the feature-on path alignment-checks r14 and reads via RtlRead*FromUser first.
  • Runtime gate: UxKirNewUma (a data global) is 0 unless Feature_NewUMAHttpSys__private_IsEnabledDeviceUsageNoInline() returns true; the added checks execute only when it is nonzero.
  • Struct/offset notes:
  • Header array count validation: < 0x2710 (10000) in both builds.
  • Known-header index validation: <= 0x1d (29) in both builds.
  • 64-bit array entry stride: 0x10 bytes.

8. Changed Functions — Full Triage

  • UlpCopySingleMultipleHeader (unpatched 0x1400DBD58 / patched 0x1400DB3B0) (response header string builder) | Similarity: 0.6055 | Not security-relevant as delivered. Patched adds a UxKirNewUma-gated path (array alignment pre-validation UlpProbeAlignmentOfHeaders, single-capture reads RtlReadUShortFromUser / RtlReadULong64FromUser, per-value UlProbeAlignmentAnsiString, and UxTryCopyFromMode in place of memmove). The UxKirNewUma == 0 branch retains the unpatched logic verbatim, including the destination bounds check and per-value probe.
  • UlpGetMultipleKnownHeader (unpatched 0x1400DD284 / patched 0x1400DCD90) (known-header descriptor validator) | Similarity: 0.6512 | Not security-relevant as delivered. Patched adds ptr & 7 / ptr & 3 alignment validation on the descriptor pointer at [a2+8] (returning 0xC000000D) and single-captures the 0x18-byte descriptor via RtlReadULongFromUser / RtlReadUShortFromUser, but only under UxKirNewUma != 0. The count < 0x2710 and index <= 0x1d checks exist in both builds; the legacy UlProbeForRead + direct-read path is retained.
  • UlReceiveHttpRequestFastIo (unpatched 0x140014E50 / patched 0x140094DC0) (fast-I/O HTTP request receive handler) | Similarity: 0.6071 | Not security-relevant as delivered. Patched adds a top-level structure capture (UxCaptureUserMemory, 0x140030D20) and UxKirNewUma-gated alignment checks; the legacy inline parse is retained in the UxKirNewUma == 0 branch.
  • UlpComputeAuthHeaderSize (unpatched 0x1400DB618 / patched 0x1400DA0C0) (auth/known-header size accumulator) | Similarity: 0.9078 | Not security-relevant as delivered. Patched routes each entry through single-capture helpers (RtlReadULongFromUser / RtlReadUShortFromUser) under UxKirNewUma != 0; the legacy direct-read accumulation is retained in the UxKirNewUma == 0 branch. The scheme-name validator is the same function, rebased.
  • UxSslConnectionControl (unpatched 0x14009B0A0 / patched 0x14009C080) and UlSendResponseOrEntityBodyFastIoOld (unpatched 0x14009ECF0 / patched 0x14009FDA0) (large HTTP parsers) | Similarity: 0.9502 | Behavioral (feature-staging). These reference UxKirNewUma at multiple sites, threading the same feature-gated path into the primary request/response processing surfaces; the remaining diff is helper-rename and ETW/globals rebasing. Old path retained in each case.

9. Unmatched Functions

  • Removed: None.
  • Added: None. (The change is delivered by modifying existing structure-parsing functions and gating an alternate capture/validation path behind the staged feature flag UxKirNewUma, with the legacy path retained. No IOCTL sanitizer functions are added or removed.)

10. Confidence & Caveats

  • Confidence Level: High. The UxKirNewUma gate is assigned from Feature_NewUMAHttpSys__private_IsEnabledDeviceUsageNoInline() at init, and every affected function retains its legacy path in the UxKirNewUma == 0 branch, as confirmed in both the disassembly and the decompiled output. The added path is not active in the default configuration.
  • Assessment: This is a feature-staged (Feature_NewUMAHttpSys, "new user-mode access") hardening refactor of the IOCTL structure parsers, rolled out behind a runtime flag with the old path retained. It is defense-in-depth robustness work (single-capture reads, pointer-alignment enforcement, fault-tolerant copy), not a fix for a demonstrable, reachable memory-safety vulnerability in the shipped default path. Severity: Informational; no CWE asserted.
  • Assumptions Made: Entry points (HttpSendHttpResponse, HttpReceiveHttpRequest, HttpSetServiceConfiguration) are inferred from the structure-parsing behavior of http.sys and the Windows HTTP API; symbols are stripped and dispatch is function-pointer routed. This does not affect the verdict, which rests on the retained legacy path and the feature-flag gate.