# Root Cause Analysis — CVE-2026-50475

## Summary

| Field | Value |
|-------|-------|
| **CVE** | CVE-2026-50475 |
| **Binary** | netio.sys (Network I/O Subsystem) |
| **Vulnerability** | Off-by-one information disclosure in `NsipGetAllInformationProviderParameters` |
| **Impact** | Information Disclosure (kernel pointer leak) |
| **CVSS 3.1** | 5.5 — AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| **CWE** | CWE-823: Use of Out-of-Range Pointer Offset |
| **Pre-patch version** | 10.0.26100.8737 (KB5095093, June 2026) |
| **Post-patch version** | 10.0.26100.8875 (KB5101650, July 2026) |
| **Patch Tuesday** | July 14, 2026 |

---

## Component Overview

The Network I/O Subsystem (`netio.sys`) is a core Windows kernel-mode driver
that manages the Network Store Interface (NSI).  NSI provides a unified
interface for querying and configuring network parameters.  User-mode clients
communicate with it by sending IOCTLs to `\\Device\\Nsi`.

NSI organizes network modules (TCP, UDP, IP, etc.) via "NMP contexts" — each
identified by a GUID.  Each NMP context contains a **vtable array** of
information provider structures.  The vtable is indexed by a `TableIndex`
parameter supplied in user IOCTL input buffers.

---

## Ghidra-Verified Vulnerable Code (pre-patch netio.sys 10.0.26100.8737)

### NsipGetAllInformationProviderParameters @ 0x1400638a0

```c
ulonglong NsipGetAllInformationProviderParameters(longlong param_1)
{
  int iVar1;
  longlong *plVar2;
  ulonglong uVar7;
  int *piVar5;
  undefined8 *puVar6;

  plVar2 = *(longlong **)(param_1 + 0x10);  // user-controlled input buffer
  iVar1 = *(int *)(param_1 + 0x20);          // operation type

  if (iVar1 == 0) {
    piVar5 = NsipGetNmpContext((uint *)&local_38);  // lookup by GUID
    if (piVar5 != (int *)0x0) {

      // ▼▼▼ VULNERABLE BOUNDS CHECK ▼▼▼
      if (*(uint *)(plVar2 + 3) <= *(uint *)(*(longlong *)(piVar5 + 0xc) + 4)) {
      //  ^^^^^^^^^^^^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      //  user-supplied TableIndex    maxVtableIndex from NMP context
      //
      //  BUG: uses <= (less-or-equal) instead of < (less-than)
      //  When TableIndex == maxVtableIndex, the check PASSES but the
      //  subsequent array access reads PAST the end of the vtable.

        uVar7 = 0;
        puVar6 = (undefined8 *)
                 ((ulonglong)*(uint *)(plVar2 + 3) * 0x68 +
                  *(longlong *)(*(longlong *)(piVar5 + 0xc) + 8));
        //        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //        vtable_base + (TableIndex * sizeof(entry))
        //        With TableIndex == maxVtableIndex, this reads one
        //        entry PAST the end of the allocated vtable array.

        goto LAB_14006394b;  // copies 0x10 bytes from puVar6 to output
      }
      NsipDereferenceNmpContext(piVar5);
    }
    uVar7 = 0xc0000225;  // STATUS_NOT_FOUND
  }
  // ... other operation types ...

LAB_14006394b:
    // Copies 0x10 bytes from the vtable entry (puVar6) to the output buffer
    puVar3 = *(undefined8 **)(param_1 + 0x48);
    if (puVar3 != (undefined8 *)0x0) {
      *(undefined4 *)(param_1 + 0x50) = 0x10;  // output size = 16 bytes
      *puVar3 = *puVar6;       // first 8 bytes  → leaked kernel pointer
      puVar3[1] = puVar6[1];   // second 8 bytes → leaked kernel pointer
    }
    // ...
}
```

### The Fix (post-patch)

The patch changes the bounds check from `<=` to `<`:

```c
// PRE-PATCH (VULNERABLE):
if (*(uint *)(plVar2 + 3) <= *(uint *)(*(longlong *)(piVar5 + 0xc) + 4))

// POST-PATCH (FIXED):
if (*(uint *)(plVar2 + 3) <  *(uint *)(*(longlong *)(piVar5 + 0xc) + 4))
```

When `TableIndex == maxVtableIndex`, the pre-patch code falls through to the
array access; the post-patch code correctly rejects it with `STATUS_NOT_FOUND`.

---

## Call Chain — IOCTL to Vulnerable Code

```
User-mode:
  CreateFileW(L"\\\\.\\Nsi", ...)
    → NtCreateFile → opens \\Device\\Nsi

  DeviceIoControl(hDevice, 0x120007, inBuf, inSize, outBuf, outSize, ...)
    → NtDeviceIoControlFile

Kernel (netio.sys):
  IRP_MJ_DEVICE_CONTROL handler
    → dispatches IOCTL 0x120007
      → NsipGetAllInformationProviderParameters @ 0x1400638a0
        → NsipGetNmpContext @ 0x140029290        (GUID lookup)
        → vtable[TableIndex] read                 (off-by-one HERE)
        → copies 0x10 bytes to output buffer
```

### IOCTL Input Buffer Layout

The IOCTL `0x120007` input buffer contains:

| Offset | Size | Field | Description |
|--------|------|-------|-------------|
| +0x00 | 16 | ModuleId (GUID) | NMP module GUID — identifies which network module |
| +0x10 | 4 | OperationType | Must be 0 for the vulnerable path |
| +0x18 | 4 | TableIndex | Index into the vtable array — the attacker-controlled value |

### Triggering the Bug

1. **Target GUID**: `{eb004a03-9b1a-11d4-9123-0050047759bc}` — this is
   `TcpNsiInterfaceDispatch`, registered by `tcpip.sys`.
2. **maxVtableIndex**: `0x26` for this module.
3. **Set TableIndex = 0x26**: the off-by-one allows this value through the
   bounds check, reading 0x10 bytes from `vtable[0x26]` which is one entry
   past the allocated array.
4. **Leaked data**: the 0x10 bytes at this location contain **tcpip.sys
   kernel addresses** — function pointers from adjacent data structures
   in the kernel pool.

---

## Exploitation Scenario

### What an Attacker Gets

The 16 bytes leaked from the out-of-bounds vtable read contain kernel
addresses belonging to `tcpip.sys`.  This defeats:

- **KASLR** — the attacker learns the load address of tcpip.sys, and from
  there can compute the base of ntoskrnl.exe and other kernel modules via
  known offsets.
- **kCFG/kASLR mitigations** — precise function pointer addresses enable
  ROP gadget selection.

### Exploitation Chain

This is a **primitive** bug, not directly exploitable for code execution.
The typical chain is:

1. **Leak phase** — call the IOCTL repeatedly with different GUIDs/indices
   to map kernel module base addresses.
2. **Weaponize** — combine with a separate write primitive (e.g., a UAF or
   pool overflow in another driver) to build a full exploit chain.
3. **Execute** — use the leaked addresses for ROP/JOP to bypass kCFG and
   execute arbitrary code in the kernel.

### Impact

- **Confidentiality: HIGH** — kernel address disclosure
- **Integrity: NONE** — read-only primitive
- **Availability: NONE** — no crash or DoS

---

## NsipGetNmpContext — GUID Resolution (@ 0x140029290)

The function resolves the user-supplied GUID to an NMP (Network Module
Provider) context:

```c
int * NsipGetNmpContext(uint *param_1)
{
  // First tries NsiHandleFactory hash table lookup
  if (*param_1 != 0) {
    RtlAcquireReadLock(NsiNmpListLock, ...);
    if (NsiHandleFactory != NULL) {
      slot = (*NsiHandleFactory - 1) & *param_1;
      if (hash_table[slot].key == *param_1) {
        piVar6 = hash_table[slot].value;
        InterlockedIncrement(&piVar6->refcount);
        return piVar6;
      }
    }
  }

  // Falls back to NsiNmpList linked-list walk
  // Compares GUID fields to find the matching NMP context
  foreach (entry in NsiNmpList) {
    if (entry.guid matches param_1.guid) {
      InterlockedIncrement(&entry->refcount);
      return entry;
    }
  }

  return NULL;
}
```

The returned NMP context contains at offset `+0x30` (`piVar5 + 0xc`) a
pointer to the vtable descriptor:

```
NMP context:
  +0x00  refcount
  +0x30  → vtable_descriptor
            +0x04  maxVtableIndex (uint32)  ← the upper bound
            +0x08  vtable_base (pointer)    ← array of 0x68-byte entries
```

---

## Detection Rules

### YARA Rule — Detect PoC Binary

```yara
rule CVE_2026_50475_NETIO_OOB_Read_PoC
{
    meta:
        description = "Detects trigger PoC for CVE-2026-50475 NETIO.sys OOB read"
        author = "OnlyFm252 Blue Team"
        date = "2026-07"
        cve = "CVE-2026-50475"
        reference = "https://talosintelligence.com/vulnerability_reports/TALOS-2026-2443"

    strings:
        // Device path string for \\.\Nsi
        $device_nsi = "\\\\.\\Nsi" wide ascii

        // IOCTL code 0x120007
        $ioctl_code = { 07 00 12 00 }

        // TcpNsiInterfaceDispatch GUID bytes
        // {eb004a03-9b1a-11d4-9123-0050047759bc}
        $tcp_guid = { 03 4a 00 eb 1a 9b d4 11 91 23 00 50 04 77 59 bc }

        // TableIndex = 0x26 (maxVtableIndex for off-by-one)
        $table_index_26 = { 26 00 00 00 }

        // CVE identifier string
        $cve_str = "CVE-2026-50475" ascii wide

        // DeviceIoControl API
        $api_ioctl = "DeviceIoControl" ascii

    condition:
        uint16(0) == 0x5A4D and
        filesize < 500KB and
        $device_nsi and
        $ioctl_code and
        ($tcp_guid or $table_index_26) and
        ($cve_str or $api_ioctl)
}
```

### YARA Rule — Detect Generic NSI OOB Exploitation

```yara
rule CVE_2026_50475_Generic_NSI_Exploit
{
    meta:
        description = "Generic detection of NSI device IOCTL exploitation attempts"
        author = "OnlyFm252 Blue Team"
        date = "2026-07"
        cve = "CVE-2026-50475"

    strings:
        $device1 = "\\\\.\\Nsi" wide
        $device2 = "\\Device\\Nsi" wide
        $api1 = "DeviceIoControl" ascii
        $api2 = "NtDeviceIoControlFile" ascii
        $guid = { 03 4a 00 eb 1a 9b d4 11 91 23 00 50 04 77 59 bc }

    condition:
        uint16(0) == 0x5A4D and
        ($device1 or $device2) and
        ($api1 or $api2) and
        $guid
}
```

### Sigma Rule — Detect NSI Device Access

```yaml
title: Suspicious NSI Device Access (CVE-2026-50475)
id: a3f8c7d2-9e4b-4a1c-b5d6-7f2e8c3a9b0d
status: experimental
description: |
    Detects processes opening \\Device\\Nsi that are not known legitimate
    network stack components. CVE-2026-50475 exploits IOCTL 0x120007 to
    this device for kernel info disclosure.
author: OnlyFm252 Blue Team
date: 2026/07/14
references:
    - https://talosintelligence.com/vulnerability_reports/TALOS-2026-2443
    - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-50475
tags:
    - attack.discovery
    - attack.t1082
    - cve.2026.50475
logsource:
    product: windows
    category: file_event
detection:
    selection_device:
        TargetFilename|contains:
            - '\\Device\\Nsi'
            - '\\.\\Nsi'
    filter_legitimate:
        Image|endswith:
            - '\svchost.exe'
            - '\lsass.exe'
            - '\services.exe'
            - '\System'
            - '\csrss.exe'
            - '\wininit.exe'
            - '\dns.exe'
            - '\dhcp.exe'
            - '\ipconfig.exe'
            - '\netsh.exe'
            - '\route.exe'
            - '\tracert.exe'
            - '\ping.exe'
            - '\nslookup.exe'
            - '\NetworkService'
    condition: selection_device and not filter_legitimate
falsepositives:
    - Network diagnostic tools
    - VPN clients
    - Custom network monitoring software
level: medium
```

### Sigma Rule — Detect KASLR Bypass Indicators

```yaml
title: Potential KASLR Bypass via NSI Info Leak (CVE-2026-50475)
id: b4e9d8c3-0f5a-4b2d-c6e7-8g3f9d4a0c1e
status: experimental
description: |
    Detects rapid repeated access to \\Device\\Nsi from a single process,
    which may indicate systematic kernel address enumeration via
    CVE-2026-50475 off-by-one read.
author: OnlyFm252 Blue Team
date: 2026/07/14
references:
    - https://talosintelligence.com/vulnerability_reports/TALOS-2026-2443
tags:
    - attack.discovery
    - attack.t1082
    - attack.defense_evasion
    - attack.t1211
    - cve.2026.50475
logsource:
    product: windows
    category: driver_load
detection:
    selection:
        ImageLoaded|endswith: '\netio.sys'
    timeframe: 5m
    condition: selection | count() by Computer > 5
falsepositives:
    - System boot sequence
    - Network reconfiguration events
level: high
```

### Sysmon Configuration — Monitor NSI Device Access

```xml
<!--
  Sysmon config snippet for CVE-2026-50475 detection.
  Add these rules to your existing Sysmon configuration.
-->

<!-- Rule: FileCreate events for \\Device\\Nsi access -->
<RuleGroup name="CVE-2026-50475" groupRelation="or">

  <!-- Event ID 11: FileCreate — detect handle opens to Nsi device -->
  <FileCreate onmatch="include">
    <Rule groupRelation="and">
      <TargetFilename condition="contains">\Device\Nsi</TargetFilename>
      <!-- Exclude known legitimate callers -->
      <Image condition="excludes">svchost.exe</Image>
      <Image condition="excludes">lsass.exe</Image>
      <Image condition="excludes">services.exe</Image>
      <Image condition="excludes">csrss.exe</Image>
    </Rule>
  </FileCreate>

  <!-- Event ID 1: ProcessCreate — detect PoC-like command lines -->
  <ProcessCreate onmatch="include">
    <Rule groupRelation="or">
      <CommandLine condition="contains">CVE-2026-50475</CommandLine>
      <CommandLine condition="contains">\\.\Nsi</CommandLine>
      <CommandLine condition="contains">\Device\Nsi</CommandLine>
    </Rule>
  </ProcessCreate>

  <!-- Event ID 7: ImageLoad — detect netio.sys loading -->
  <ImageLoad onmatch="include">
    <Rule groupRelation="and">
      <ImageLoaded condition="end with">netio.sys</ImageLoaded>
      <Signed condition="is">false</Signed>
    </Rule>
  </ImageLoad>

</RuleGroup>
```

### KQL Query — Microsoft Sentinel / Defender for Endpoint

```kql
// Detect processes accessing \\Device\\Nsi that are not standard Windows components
// Potential CVE-2026-50475 exploitation attempt
DeviceFileEvents
| where FileName has_any ("Nsi", "nsi")
    or FolderPath has_any ("\\Device\\Nsi", "\\\\.\\Nsi")
| where InitiatingProcessFileName !in~
    ("svchost.exe", "lsass.exe", "services.exe", "system",
     "csrss.exe", "wininit.exe", "dns.exe", "dhcp.exe",
     "ipconfig.exe", "netsh.exe", "ping.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName,
          InitiatingProcessCommandLine, FolderPath, FileName
| sort by Timestamp desc
```

---

## Patch Verification

To confirm a system is patched, check the netio.sys version:

```powershell
# Check netio.sys version — must be ≥ 10.0.26100.8875
(Get-Item "$env:SystemRoot\System32\drivers\netio.sys").VersionInfo.FileVersion
```

The patched version changes the single comparison instruction at the bounds
check site in `NsipGetAllInformationProviderParameters`:

```
Pre-patch:  cmp  r8d, [rcx+4]   ;  followed by  jbe  (jump if below-or-equal)
Post-patch: cmp  r8d, [rcx+4]   ;  followed by  jb   (jump if below)
```

This is a **one-instruction fix** — `jbe` (opcode `0x76`) → `jb` (opcode `0x72`).

---

## References

- [TALOS-2026-2443](https://talosintelligence.com/vulnerability_reports/TALOS-2026-2443)
- [MSRC Advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-50475)
- [CWE-823: Use of Out-of-Range Pointer Offset](https://cwe.mitre.org/data/definitions/823.html)
