CVE-2026-70111: The Crash That Was Almost Nothing
Microsoft patched CVE-2026-70111 yesterday. The Corelight blog post covers the what: unauthenticated remote kernel DoS on Windows Server 2019/2022/2025 via a malformed DFS referral response, no auth required, domain controllers included. This is the how.
Why DFS
We’ve been systematically fuzzing MS-RPC interfaces. Enumerate the registered endpoints, walk the opnums, throw shaped garbage at each one, watch for crashes. About 1,000 interfaces, ~10,000 functions. Most return access denied or do nothing interesting.
DFS got bumped up the queue because of its history as a coercion vector. DFSCoerce abuses NetrDfsRemoveStdRoot to force a server to authenticate to an attacker-controlled UNC path, one of several techniques attackers use to steal NTLM credentials from domain controllers. We’d already been poking at the DFS management interface (MS-DFSNM, UUID 4fc742e0) for a different set of bugs. During that work we confirmed DFSCoerce was still functional on Server 2022 and noticed something: when a coerced server connects to your rogue SMB endpoint, it doesn’t just authenticate. It also processes whatever DFS referral response you send back.
That’s a client-side kernel parsing path. An attacker-controlled response going directly into mup.sys. We wanted to know what happened if the response was wrong.
The Fuzzer
The fuzzer enumerates valid DFS referral response structures (V1 through V4) and generates test cases by varying field values systematically: field widths, offset fields pointing at interesting boundaries, zero lengths, maximum values, mismatched counts. We ran it overnight against a Server 2022 domain controller with automatic reboot on BSOD.
The run was 60 test cases. DC went down at case 54.
We were about to give up on the referral path, for the record. Cases 1–53 had come back clean: wrong version numbers, malformed TLV lengths, truncated payloads, null pointers, misaligned offsets. The server handled all of them. Case 54 was a V3 NameList referral with NumberOfExpandedNames = 1 and the expanded name set to a single null wide character (\x00\x00). The most structurally boring variant in the set.
The DC rebooted in about three seconds.
Two Bugs, One Crash
Reproducing the crash meant reversing mup.sys. Symbol analysis and attack surface enumeration pointed at PktpCheckReferralSyntax, the function that validates referral responses before they’re processed. It loops over each expanded name and calls PktpCheckReferralString to verify the string is within buffer bounds. Here’s what that loop actually does:
// PktpCheckReferralSyntax (reconstructed from symbol and binary analysis)
for (ndx = 0; ndx < refV3->NumberOfExpandedNames; ndx++) {
status = PktpCheckReferralString(
dfsSpecialName, // <-- this is wrong
(PCHAR)ReferralBuffer,
ReferralBufferEnd);
if (!NT_SUCCESS(status)) return status;
dfsSpecialName += wcslen(dfsSpecialName) + 1;
}
dfsSpecialName is validated twice. dfsExpandedNames, computed from the attacker-controlled ExpandedNameOffset field, is never passed to PktpCheckReferralString. It never gets checked at all.
This is a copy-paste bug. Whoever wrote this loop copied the dfsSpecialName call, changed the advance at the end, and forgot to change the argument. The ExpandedNameOffset field in a DFS V3 NameList referral is entirely attacker-controlled and the kernel accepts it without bounds checking.
That’s Bug A. It enables an OOB read: point ExpandedNameOffset past the end of the referral buffer and mup.sys scans memory it shouldn’t.
Bug B is in PktExpandSpecialEntryFromReferral. Older builds of the function contain a guard:
// PktExpandSpecialEntryFromReferral (older builds)
if (wcslen(wzExpandedName) > 0) {
RtlCopyMemory(buffer, wzExpandedName, ...);
}
Server 2022’s mup.sys removed that guard. When Bug A lets an empty expanded name through validation (valid in-bounds offset, just a null wchar), Server 2022 uses the zero-length string as an active DC hostname. Null pointer dereference. PAGE_FAULT_IN_NONPAGED_AREA in mup.sys. The DC doesn’t get a chance to log anything.
Neither bug alone gets you the crash on modern Windows. Bug A bypasses validation; Bug B removes the thing that would have saved you after validation failed. The copy-paste bug survived every refactor intact. The guard that compensated for it didn’t.
The Payload
The crash payload is 68 bytes:
RESP_GET_DFS_REFERRAL (68 bytes):
PathConsumed: 38
NumberOfReferrals: 1
Flags: 0x00000003
DFS_REFERRAL_V3 (NameList, 60 bytes):
VersionNumber: 3
Size: 60
ServerType: 0
Flags: 0x0002 ← NameListReferral
TimeToLive: 300
SpecialNameOffset: 18
NumberOfExpandedNames: 1
ExpandedNameOffset: 58 ← points to last 2 bytes of entry
SpecialName: "\\lab.internal\dfs" (UTF-16LE)
ExpandedName: "\0" ← single null wchar at offset 58-59
2600 0100 0300 0000 0300 3c00 0000 0200
2c01 0000 1200 0100 3a00 5c00 5c00 7400
6900 6d00 6100 6400 6f00 7200 2e00 6c00
6f00 6300 6100 6c00 5c00 6400 6600 7300
0000 0000
ExpandedNameOffset = 58 points to a valid in-bounds location within the 60-byte entry, so Bug A’s validation bypass isn’t even needed here. The offset is fine. The string content is the problem. One null wchar, no guard, immediate crash.
We reproduced 11 times across three sessions. 11/11.
Exploitation Ceiling
CVSS 9.8 is technically defensible: unauthenticated, remote, kernel, no user interaction. What it doesn’t capture is that this specific null dereference doesn’t go anywhere interesting. SMEP and SMAP make null-page abuse not viable on modern hardware. The fault happens inside a kernel worker thread with no useful register state. Reliability as DoS: 100%. Reliability as anything else: 0%.
The OOB read from Bug A alone (non-empty string at an out-of-bounds offset) is a more interesting primitive, but it requires triggering the DC-referral path rather than the share-referral path, which is harder to coerce. We didn’t find a weaponizable path there either.
So: reliable kernel DoS, no RCE. Every DC in the environment that processes a crafted referral reboots. If an attacker can coerce several DCs simultaneously (DFSCoerce-style techniques support this), that’s domain-wide disruption from a single script. Not an account to compromise, not a payload to execute. Just chaos.
Detection
The crash leaves no useful local logs on the victim. Connection drops, then the machine reboots. What you can see from the network:
Zeek will show a sudden cluster of S1, RSTR, and RSTRH connection states on the affected server: the signature of a machine that dropped all its TCP state mid-stream. That’s post-incident. By then the crash has already happened.
The Corelight Feed Suricata rule fires on the malformed packet in transit, before it reaches the kernel. The detection concept: an SMB2 IOCTL response carrying FSCTL_DFS_GET_REFERRALS with a V3 NameList referral (Flags & 0x0002) and NumberOfExpandedNames >= 1 is suspicious by default. Legitimate DFS V3 NameList referrals come only from domain controllers. If the source IP isn’t a known DC, it’s a true positive regardless of whether the expanded name is malformed.
What We Shipped
Patch is out. The reproduction VM sits idle now, waiting to become relevant again when the next mup.sys variant ships. The fuzzer is already on the next interface. Case 54 gets a special place in the changelog.