# CVE-2026-49177 — Windows `tcpip.sys` Out-of-Bounds Read in `IppQualifyAddresses` via the NSI Address-Sort IOCTL

---

## Summary

| | |
|---|---|
| **Product** | Microsoft Windows — `tcpip.sys` (core kernel TCP/IP driver) |
| **CVE ID** | CVE-2026-49177 |
| **Impact** | Information Disclosure / Denial of Service |
| **MSRC severity** | Important — Information Disclosure |
| **CVSS (MSRC)** | 5.5 |
| **CVSS (Talos)** | 8.4 — `CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:H` |
| **CWE** | CWE-125: Out-of-bounds Read |
| **Confirmed vulnerable** | `tcpip.sys` 10.0.26100.8457 (Talos) / .8737 (our pre-patch) |
| **Pre-patch binary** | `tcpip.sys` 10.0.26100.8737 (Jun 23 2026) — SHA256 `9b09022f6e0387547b385be35075049f8fef199a07daf81541d8cf30e072e3e4` |
| **Post-patch binary** | `tcpip.sys` 10.0.26100.8875 (KB, Jul 14 2026 fix) — SHA256 `45c1b98901dbad8a6f24e5f1ea542c63cedf2f64be18b37e43944f4e1a3a7ae3` |
| **Feature flag** | `Feature_4272399675` — **the fix is CFR-gated** |

---

## Product Description

`tcpip.sys` is the kernel-mode driver implementing the Windows IPv4/IPv6 stack —
IP, TCP, UDP, ICMP, routing and packet handling. Parts of its address
configuration are reachable from user mode through the **Network Store Interface
(NSI)**: `DeviceIoControl(\Device\Nsi, IOCTL 0x120007)` is dispatched by
`nsiproxy.sys`, funnelled through `NETIO!NsiGetParameterEx`, and lands in
tcpip's parameter handlers. The IOCTL and its input buffer are reachable by an
ordinary local user, so every field of that buffer is attacker input.

This is the **same `\Device\Nsi` IOCTL 0x120007 surface** as CVE-2026-50475
(netio.sys, June 2026) — a distinct off-by-one one month earlier on the same
entry point.

---

## Vulnerability Summary

The IOCTL drives `IpGetAllSortedAddressParameters`, which passes the user
`KeyBuffer` (fixed size **`0x36bc` bytes**) into `IppCreateSortedAddressPairsEx`.
Its fifth argument, `MaxDestCount`, is taken from that buffer. When non-zero,
`IppQualifyAddresses` walks the destination list `MaxDestCount` times with **no
upper bound**, striding `0x1c` bytes per iteration from `KeyBuffer+8`:

```c
// IppQualifyAddresses (tcpip.sys 10.0.26100.8457) — from Talos TALOS-2026-2427
int32_t *IppQualifyAddresses(int64_t *arg1, int32_t *MaxDestCount, void *keybuf_off4, void *arg4)
{
    uint64_t n = *(uint32_t *)MaxDestCount;          // attacker-controlled count
    if ((uint32_t)n) {
        char *r8 = (char *)keybuf_off4 + 9;          // start at KeyBuffer+4 +8 +1
        uint32_t i = 0;
        do {
            char rcx = r8[-1];                       // (8) read — walks off the buffer
            /* ... classify address byte ... */
            i += 1;
            r8 = &r8[0x1c];                          // stride 0x1c per destination
        } while (i < (uint32_t)n);                   // (6) bound is the raw count only
    }
    return MaxDestCount;
}
```

`KeyBuffer` is `0x36bc` bytes; the walk begins `4 + 8` bytes in and consumes
`0x1c` per entry. The largest count that stays in bounds is therefore:

```
(0x36bc - 4 - 8) / 0x1c = 0x1f4   (500)
```

Any `MaxDestCount > 0x1f4` reads past the end of the buffer. Talos's PoC sets it
to `0xbbbbbbbb`, walking far into adjacent NonPagedPool and bugchecking `0x50`
(PAGE_FAULT_IN_NONPAGED_AREA) at `tcpip!IppQualifyAddresses+0x50`.

---

## Prerequisites and Constraints

- Local authenticated session (standard user; `PR:L`, no admin)
- Ability to `DeviceIoControl(\Device\Nsi, 0x120007)` — available to normal users
- No user interaction, no special hardware
- Primitive is an out-of-bounds **read**: information disclosure of adjacent
  kernel pool, or a bugcheck DoS when the read crosses an unmapped page. Talos's
  `S:C / C:H` scoring reflects the disclosure reaching across a trust boundary.

---

## Vulnerability Details

### Call Chain (from the Talos crash stack)

```
User mode:
  DeviceIoControl(\Device\Nsi, IOCTL 0x120007, KeyBuffer[0x36bc])
        ↓
Kernel mode:
  nsiproxy!NsippDispatch → NsippGetParameter
    → NETIO!NsiGetParameterEx
      → tcpip!IpGetAllSortedAddressParameters      // reads KeyBuffer at arg+0x10
        → tcpip!IppCreateSortedAddressPairsEx      // MaxDestCount = 5th arg, from KeyBuffer
          → tcpip!IppQualifyAddresses              // *** OOB read: loop bound = MaxDestCount ***
```

### Root Cause

`MaxDestCount` is read from the user IOCTL buffer and used directly as the loop
trip count. The `0x36bc` buffer can hold at most `0x1f4` `0x1c`-byte destination
records after the `4 + 8` byte prologue, but nothing checks the count against
that maximum before the walk. `IppCreateSortedAddressPairsEx` guards only
`MaxDestCount != 0` (Talos step 3), never `MaxDestCount <= 0x1f4`.

### The patch (confirmed — diff, .8737 → .8875)

The fix is in **`IpGetAllSortedAddressParameters`** (the caller), not
`IppQualifyAddresses` itself. It reads the CFR flag state and adds a
`MaxDestCount` bound before dispatching the sort:

```c
// IpGetAllSortedAddressParameters (tcpip.sys 10.0.26100.8875) — PATCHED, from our diff
uVar3 = Feature_4272399675__private_featureState & 1;
if ((uVar3 == 0) || ((uint)piVar1[0xdad] < 0x1f5)) {   // piVar1[0xdad] = MaxDestCount
    ...
    uVar4 = IppCreateSortedAddressPairsEx(...);         // reached only when count <= 0x1f4
    ...
}
```

`piVar1[0xdad]` is the user-controlled `MaxDestCount`; `< 0x1f5` means the sort
runs only when `MaxDestCount <= 0x1f4` (500) — **exactly the Talos bound**
`(0x36bc - 4 - 8) / 0x1c`. Requests above 500 destinations are refused before
`IppCreateSortedAddressPairsEx` / `IppQualifyAddresses` ever walk the buffer.

### Patch Completeness Assessment

**The fix is CFR-gated behind `Feature_4272399675`.** The guard is
`uVar3 == 0 || MaxDestCount < 0x1f5`, where `uVar3 = featureState & 1`. When the
flag is **disabled** (`uVar3 == 0`), the `||` short-circuits and the sort proceeds
with **no bound** — the original out-of-bounds-read path still executes on a fully
patched `.8875` binary. So patch state is not determined by file version alone;
only with `Feature_4272399675` enabled is the corrected path live. This places
CVE-2026-49177 alongside the CLFS, `lserver`, `tapisrv` and `http.sys` fixes in
this corpus whose memory-safety remediation ships behind a runtime flag.

---

## Detection Guidance

**Crash signature.** Bugcheck `0x50` PAGE_FAULT_IN_NONPAGED_AREA with the
faulting instruction in `tcpip!IppQualifyAddresses` (Talos observed
`+0x50`, `movzx ecx, byte ptr [r8-1]`), failure bucket
`AV_NETIO!NsiGetParameterEx`. A read fault, address in NonPagedPool.

**Behavioural.** Non-system processes issuing `DeviceIoControl` to `\Device\Nsi`
with IOCTL `0x120007` and an address-sort parameter whose destination count is
implausibly large. Legitimate `GetAddrInfoEx` / address-sorting callers stay
well under a few hundred destinations.

**Note.** Watch `\Device\Nsi` / IOCTL `0x120007` broadly — it is the shared
entry point for both this bug and CVE-2026-50475 (netio.sys).

---

## References

- Talos TALOS-2026-2427 — *Microsoft Windows TCPIP.SYS IppQualifyAddresses
  Out-of-Bounds Read* (KPC, Cisco Talos). Disclosed 2026-06-01, patched
  2026-07-14.
- MSRC advisory — CVE-2026-49177 (Windows TCP/IP, Information Disclosure)
- Related surface: CVE-2026-50475 (netio.sys, `\Device\Nsi` IOCTL 0x120007)
- Full binary diff: `/data/patch_diffs/tcpip_sys-cve-2026-49177-ghidriff.md`
