1. Overview

  • Unpatched Binary: e1i63x64_unpatched.sys
  • Patched Binary: e1i63x64_patched.sys
  • Overall Similarity Score: 0.8577
  • Diff Statistics:
  • Matched Functions: 1201
  • Changed Functions: 257
  • Identical Functions: 944
  • Unmatched Functions (Unpatched/Patched): 0 / 0
  • Verdict: No security-relevant change. The NDIS OID request-type validation is present and byte-for-byte identical in both builds at the MiniportOidRequest dispatch entry: only RequestType values {0,1,2,12} are accepted, and any other value is rejected with status 0xC0010015 before any OID handler runs. The patch does not add this check. What the patch actually does is relocate OID extraction into a new helper, GetOid, that re-reads the same request-type set (a redundant re-check on a path where the type is already guaranteed valid) and emits a trace event; the OID handlers are refactored from taking a raw OID integer to taking the NDIS_OID_REQUEST pointer. The remainder of the changed-function set is an SR-IOV queue-to-VPort refactor, a new registry parameter, and tracing additions. None of these is a reachable security fix.

2. Change Summary

NDIS OID request-type validation — present in both builds (no delivered fix)

  • Severity: None (informational)
  • Change Class: Refactor / defense-in-depth relocation (no CWE)
  • Affected Functions: MiniportOidRequest (0x140026874 unpatched / 0x14000C5D4 patched) (OID request dispatch), GetOid (0x14000C3F8, patched only) (OID extraction helper), ReqQueryCommonInformation (0x140026E98 / 0x14000CFD4) (common query dispatch), RxFilterQueryOidHandler (0x1400133C8 / 0x14003E318) (receive-filter query handler), RxFilterSetOidHandler (0x140013730 / 0x14003E6B4) (receive-filter/VMQ set handler)
  • What was claimed vs. what is true: A missing request-type check would mean the unpatched driver acts on an OID under an unexpected NDIS_OID_REQUEST.RequestType. That is not the case. In both builds the sole NDIS OID entry point, MiniportOidRequest, reads RequestType at offset +0x4 and dispatches only for 0 (NdisRequestQueryInformation), 1 (NdisRequestSetInformation), 2 (NdisRequestQueryStatistics), and 12 (NdisRequestMethod); every other value returns 0xC0010015 immediately, before ReqQueryInformation, ReqQueryCommonInformation, RxFilterQueryOidHandler, or RxFilterSetOidHandler is ever reached.
  • What the patch changed: OID extraction was centralized into the new helper GetOid. In the unpatched build each dispatch reads the OID inline from the request (ReqQueryInformation does mov edi, [r8+0x20]) and passes it down as a raw unsigned long. In the patched build the dispatch and the receive-filter handlers instead take the NDIS_OID_REQUEST* and call GetOid, which reads RequestType at +0x4, returns the OID from +0x20 for types {0,1,2,12}, and otherwise returns 0 after emitting a trace event. Because MiniportOidRequest has already guaranteed the type is in {0,1,2,12} on every path that reaches these functions, GetOid's reject branch is unreachable for externally supplied requests; the helper always returns the real OID. The change is a refactor plus tracing, not a behavioral gate that was absent before.
  • Reachability: No RequestType outside {0,1,2,12} can reach any OID handler in either build. The only callers that construct an NDIS_OID_REQUEST with a fixed type internally (for example ReqQueryNdisStatistic) are driver-internal and not attacker-controlled.

3. Pseudocode Diff

The request-type gate exists in both builds at the dispatch entry. The patch moves OID extraction into a helper that repeats the same type set.

// --- PRESENT IN BOTH BUILDS ---
// OID request dispatch (MiniportOidRequest, 0x140026874 unpatched / 0x14000C5D4 patched)
int MiniportOidRequest(void* ctx, _NDIS_OID_REQUEST* req) {
    switch (req->RequestType) {          // offset +0x4
        case 0:  return ReqQueryInformation(...);   // Query
        case 1:  return ReqSetInformation(...);     // Set
        case 2:  return ReqQueryInformation(...);   // QueryStatistics
        case 12: return ReqMethod(...);             // Method
        default: return 0xC0010015;                 // rejected before any handler runs
    }
}

// --- UNPATCHED: OID read inline, passed as a raw integer ---
// Query dispatch (ReqQueryInformation, 0x140029818)
int ReqQueryInformation(REQUEST* this, ADAPTER_CONTEXT* ctx, _NDIS_OID_REQUEST* req) {
    unsigned oid = req->Oid;             // mov edi, [r8+0x20]
    ...
    ReqQueryCommonInformation(ctx, oid, ...);   // OID passed down as unsigned long
}
// ReqQueryCommonInformation (0x140026E98) receives the OID as unsigned long and switches on it.

// --- PATCHED: OID read through GetOid, same type set ---
// New helper (GetOid, 0x14000C3F8)
unsigned long GetOid(_NDIS_OID_REQUEST* req) {
    unsigned type = req->RequestType;   // offset +0x4
    if (type == 0 || type == 1 || type == 2 || type == 12)
        return req->Oid;                // offset +0x20
    /* emit trace */ return 0;          // unreachable for requests that pass MiniportOidRequest
}
// ReqQueryCommonInformation (0x14000CFD4) now takes _NDIS_OID_REQUEST* and does oid = GetOid(req).
// RxFilterQueryOidHandler / RxFilterSetOidHandler likewise take _NDIS_OID_REQUEST* and call GetOid.

4. Assembly Analysis

The request-type gate is identical in both builds (MiniportOidRequest)

Unpatched (0x140026874):

0x140026874  mov  r8d, [rdx+4]        ; RequestType
0x140026878  test r8d, r8d
0x14002687b  jz   0x1400268b7         ; type 0  -> ReqQueryInformation
0x14002687d  dec  r8d
0x140026880  jz   0x1400268a5         ; type 1  -> ReqSetInformation
0x140026882  dec  r8d
0x140026885  jz   0x1400268b7         ; type 2  -> ReqQueryInformation
0x140026887  cmp  r8d, 0xa
0x14002688b  jz   0x140026893         ; type 12 -> ReqMethod
0x14002688d  mov  eax, 0xc0010015     ; any other type: rejected
0x140026892  retn

Patched (0x14000C5D4) — same instructions, same constants:

0x14000c5d4  mov  r8d, [rdx+4]        ; RequestType
0x14000c5d8  test r8d, r8d
0x14000c5db  jz   0x14000c617         ; type 0  -> ReqQueryInformation
0x14000c5dd  dec  r8d
0x14000c5e0  jz   0x14000c605         ; type 1  -> ReqSetInformation
0x14000c5e2  dec  r8d
0x14000c5e5  jz   0x14000c617         ; type 2  -> ReqQueryInformation
0x14000c5e7  cmp  r8d, 0xa
0x14000c5eb  jz   0x14000c5f3         ; type 12 -> ReqMethod
0x14000c5ed  mov  eax, 0xc0010015     ; any other type: rejected
0x14000c5f2  retn

OID extraction: inline (unpatched) vs. helper call (patched)

Unpatched ReqQueryInformation (0x140029818) reads the OID directly from the request and passes it down:

0x14002982f  mov  edi, [r8+0x20]      ; OID read inline from NDIS_OID_REQUEST+0x20
...
0x1400299ce  call ReqQueryCommonInformation   ; OID passed as unsigned long (edi)

Unpatched ReqQueryCommonInformation (0x140026E98) receives the OID as an integer argument:

0x140026ed6  mov  r15d, r8d           ; OID from the raw integer argument
0x140026eea  mov  eax, 0x10203        ; upper bound of the OID switch
0x140026f0d  cmp  r15d, eax           ; switch dispatch on the OID

Patched ReqQueryCommonInformation (0x14000CFD4) takes the request pointer and obtains the OID through GetOid:

0x14000d026  mov  rcx, r8             ; rcx = NDIS_OID_REQUEST*
0x14000d043  call GetOid              ; read RequestType (already in {0,1,2,12}), return OID
0x14000d04b  mov  r15d, eax           ; OID
0x14000d04e  mov  eax, 0x10203
0x14000d05b  cmp  r15d, eax           ; switch dispatch on the OID

The GetOid helper itself (0x14000C3F8, patched only):

0x14000c407  mov  esi, [rcx+4]        ; RequestType
0x14000c40a  xor  edi, edi            ; default return = 0
0x14000c40e  test esi, esi
0x14000c410  jz   0x14000c476         ; type 0
0x14000c412  dec  edx
0x14000c414  jz   0x14000c476         ; type 1
0x14000c416  dec  edx
0x14000c418  jz   0x14000c476         ; type 2
0x14000c41a  cmp  edx, 0xa
0x14000c41d  jz   0x14000c476         ; type 12
0x14000c41f  ...                      ; otherwise emit trace, return 0
0x14000c476  mov  edi, [rcx+0x20]     ; accepted type: return the OID
0x14000c483  mov  eax, edi
0x14000c48a  retn

GetOid checks exactly the set {0,1,2,12} that MiniportOidRequest has already enforced on every reaching path, so its reject branch does not execute for externally supplied requests.


5. Reachability Analysis

  1. Single external entry: The only NDIS entry for OID requests is the MiniportOidRequest callback (registered at 0x140003C8C in the patched build). Query, Set, QueryStatistics, and Method requests all pass through it.
  2. Type gate first: In both builds MiniportOidRequest reads RequestType at offset +0x4 and dispatches only for {0,1,2,12}. A request with any other RequestType (e.g. 4) returns 0xC0010015 and never reaches ReqQueryInformation, ReqQueryCommonInformation, RxFilterQueryOidHandler, or RxFilterSetOidHandler.
  3. Consequence for the handlers: On every path that reaches the receive-filter/VMQ handlers, RequestType is already one of {0,1,2,12}. GetOid therefore always returns the real OID; there is no request-type value an attacker can supply that reaches a handler under the unpatched build but is blocked under the patched build.
  4. Net effect: The OID a handler acts on is the same in both builds. The patch changes how the OID is fetched (inline read vs. helper call) and adds a trace event, not whether an out-of-type request can reach a handler.

6. Impact Assessment

  • Nature: Refactor of OID extraction plus tracing. Not memory corruption and not a reachable logic bypass.
  • Affected OIDs: The receive-filter/VMQ OIDs (0x10224/0x10226/0x10228/0x10230) and the VMQ queue-information query (0xFF020188) are handled identically with respect to request type in both builds; each is reachable only under its legitimate NDIS request type in both.
  • Demonstrable primitive: None. Because the request-type set enforced by GetOid is already enforced upstream in both builds, the helper does not remove or add any reachable state transition for attacker input.
  • Mitigations: Not applicable; there is no exploitable condition to mitigate.

7. Verification Notes

For a researcher confirming this against the two binaries with a kernel debugger:

Breakpoints

  • bp e1i63x64_unpatched+0x26874 and bp e1i63x64_patched+0xC5D4 (MiniportOidRequest)
  • Why: The request-type gate. Break here and inspect [rdx+4] (RequestType). Both builds fall to mov eax, 0xC0010015; retn for any value outside {0,1,2,12}, so a crafted request never advances to a handler in either build.
  • bp e1i63x64_unpatched+0x29818 / bp e1i63x64_patched+0xFB48 (ReqQueryInformation)
  • Why: Query dispatch. In the unpatched build the OID is read inline (mov edi, [r8+0x20]); in the patched build the equivalent dispatch obtains it via GetOid.
  • bp e1i63x64_patched+0xC3F8 (GetOid, patched only)
  • Why: Confirm that on every reaching call RequestType is already in {0,1,2,12}, so GetOid returns the OID from [rcx+0x20] and never takes the reject path for externally supplied requests.

What to Inspect

At MiniportOidRequest in either build: * poi(rdx+4) = RequestType. Send RequestType = 4 and observe the return value 0xC0010015 at 0x14002688D (unpatched) / 0x14000C5ED (patched); execution does not reach any OID handler. * poi(rdx+0x20) = Oid. This is the field GetOid returns in the patched build and that ReqQueryInformation reads inline in the unpatched build.

Expected Observation

  • Both builds: A request with RequestType outside {0,1,2,12} is rejected at MiniportOidRequest with 0xC0010015. A request with a legitimate type is dispatched to the corresponding handler with the same OID in both builds.
  • Difference: In the patched build the dispatch and the receive-filter handlers fetch the OID through GetOid and emit a trace event; in the unpatched build they read the OID field inline. No handler becomes reachable or unreachable as a result.

Struct/Offset Notes

  • NDIS_OID_REQUEST offsets used by both builds:
  • +0x04: RequestType (accepted set {0,1,2,12}, enforced at MiniportOidRequest in both builds)
  • +0x20: Oid

8. Changed Functions — Full Triage

OID-handling refactor (not a security fix)

  • GetOid (0x14000C3F8) (new in patched): Reads RequestType at +0x4 and returns the OID at +0x20 for types {0,1,2,12}, otherwise 0 with a trace event. This is the same request-type set already enforced upstream at MiniportOidRequest in both builds, so the helper centralizes OID extraction and adds tracing rather than adding a reachable gate. In the unpatched build this address holds an unrelated function (IntCauseInterruptForVector).
  • ReqQueryCommonInformation (0x140026E98 / 0x14000CFD4) (common query dispatch): Signature changed from taking the OID as a raw unsigned long to taking a _NDIS_OID_REQUEST*; the OID is now obtained via GetOid. Register/helper relocations and trace calls added. The dispatch acts on the same OID value in both builds.
  • RxFilterQueryOidHandler (0x1400133C8 / 0x14003E318) (OID 0xFF020188, VMQ queue-info query): Signature changed; arg2 becomes a _NDIS_OID_REQUEST* and the OID is obtained via GetOid(arg2) before the 0xFF020188 check. The buffer/length guard (>= 0x8A08) and the RxFilterGetVMQInfo call are unchanged.
  • RxFilterSetOidHandler (0x140013730 / 0x14003E6B4) (VMQ/VLAN receive filter set, OID 0x10224/0x10226/0x10228/0x10230): Same signature refactor; OID obtained via GetOid. SR-IOV terminology changed from queue-based to VPort-based helpers (see below), and trace calls added per OID case.

Behavioral & Structural Changes

  • SRIOV_CTRL::GetNextUnusedQueue (0x14002B910) / SRIOV_CTRL::GetNextUnusedVPort (0x140047140): SR-IOV allocation renamed from queue-based to VPort-based; per-element structure size increased (reported 0x26c to 0x288) with additional field initialization. Feature refactor.
  • ADAPTER_CONTEXT::~ADAPTER_CONTEXT (0x14002DDEC) / ADAPTER_CONTEXT::~ADAPTER_CONTEXT (0x140049CE8): Adapter-context destructor. The set of buffers freed via NdisFreeMemory_0 changes and the base offset shifts 0xa60 -> 0xa68, consistent with the surrounding structure being relaid out by the SR-IOV change. Structural cleanup, not a security fix.
  • RECEIVE_FILTER::RxFilterReadRegistryParameters (0x140013488) / RECEIVE_FILTER::RxFilterReadRegistryParameters (0x14003E408): Registry parameter reader updated to parse a new value (VMQLegacyOn650) for I350 controllers. Feature addition.

Cosmetic Changes

  • e1000_read_pba_string_generic (0x1400419C4 / 0x140062DD4): NVM/EEPROM PBA string reader. Trace additions and register-allocation differences; core logic (the 0xfafa sentinel check, hex nibble formatting, length validation) is identical.
  • e1000_init_mac_params_ich8lan (0x140035ADC / 0x140051468): MAC function-pointer table initialization updated to point to relocated subroutines. No logical impact.

9. Unmatched Functions

  • Added: 0
  • Removed: 0

10. Confidence & Caveats

  • Confidence Level: High. The request-type dispatch (MiniportOidRequest) is present with identical instructions and constants in both builds, and it is the single external entry to the OID handlers. The patched-only helper GetOid re-checks the same request-type set on a path where the type is already guaranteed valid, so it does not change reachability.
  • Basis: Direct comparison of MiniportOidRequest (0x140026874 vs 0x14000C5D4), ReqQueryInformation (0x140029818 vs 0x14000FB48), ReqQueryCommonInformation (0x140026E98 vs 0x14000CFD4), and the receive-filter handlers, plus the GetOid body (0x14000C3F8).
  • Residual note: The bulk of the diff (SR-IOV queue-to-VPort rename, the new VMQLegacyOn650 registry parameter, and trace additions) is a servicing/feature update. No changed function in the set exposes a reachable security-relevant behavioral difference.