Section 1: Overview
A stack-based buffer overflow vulnerability exists in the Windows Netlogon service’s DC locator ping response handler. When a domain controller processes a CLDAP search request, it serializes response data including attacker-supplied and server-side strings into a fixed-size stack buffer without adequate bounds checking. An unauthenticated remote attacker can send a single crafted CLDAP packet to a domain controller’s UDP port 389, causing the Netlogon service to crash the LSASS process and force the domain controller to reboot. The exploitability depends on the target domain controller’s DNS naming configuration — domain controllers with longer DNS domain names and hostnames are vulnerable. Microsoft addressed this vulnerability in the May 2026 security update.
Section 2: Vulnerability Type
| Field | Value |
|---|---|
| Primary CWE | CWE-121: Stack-based Buffer Overflow |
| Related CWE | CWE-120: Buffer Copy without Checking Size of Input |
Section 3: Severity
Microsoft Advisory Score (CVSS 3.1)
| Field | Value |
|---|---|
| Score | 9.8 (Critical) |
| Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C |
Our Assessment (CVSS 4.0)
| Metric Group | Metric | Value |
|---|---|---|
| Base - Exploitability | Attack Vector (AV) | Network |
| Attack Complexity (AC) | Low | |
| Attack Requirements (AT) | Present | |
| Privileges Required (PR) | None | |
| User Interaction (UI) | None | |
| Base - Vulnerable System | Confidentiality (VC) | None |
| Integrity (VI) | None | |
| Availability (VA) | High | |
| Base - Subsequent System | Confidentiality (SC) | None |
| Integrity (SI) | None | |
| Availability (SA) | High | |
| Threat | Exploit Maturity (E) | Proof-of-Concept |
| Field | Value |
|---|---|
| CVSS 4.0 Score | 8.8 (High) |
| CVSS 4.0 Vector | CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:H/E:P |
AT is Present because the target domain controller must have a sufficiently long DNS domain name (approximately 50+ characters) for the combined response data to exceed the 528-byte buffer. Domain controllers with short domain names (e.g., “example.com”) are not vulnerable. VC and VI are None because the overflow bytes are server-controlled DNS name data rather than attacker-controlled content, and a GS stack cookie prevents return address hijacking. SA is High because crashing LSASS on a domain controller denies all Kerberos and NTLM authentication for the domain.
Section 4: Affected Products
Affected Products
| Product | CPE 2.3 |
|---|---|
| Windows Server 2025 (Server Core) | cpe:2.3:o:microsoft:windows_server_2025:*:*:*:*:*:*:*:* |
| Windows Server 2022 (Server Core) | cpe:2.3:o:microsoft:windows_server_2022:*:*:*:*:*:*:*:* |
| Windows Server 2019 (Server Core) | cpe:2.3:o:microsoft:windows_server_2019:*:*:*:*:*:*:*:* |
| Windows Server 2016 (Server Core) | cpe:2.3:o:microsoft:windows_server_2016:*:*:*:*:*:*:*:* |
| Windows 11 Version 24H2 (x64/ARM64) | cpe:2.3:o:microsoft:windows_11_24h2:*:*:*:*:*:*:*:* |
| Windows 11 Version 23H2 (x64/ARM64) | cpe:2.3:o:microsoft:windows_11_23h2:*:*:*:*:*:*:*:* |
| Windows 10 Version 22H2 (x86/x64/ARM64) | cpe:2.3:o:microsoft:windows_10_22h2:*:*:*:*:*:*:*:* |
| Windows 10 Version 21H2 (x86/x64/ARM64) | cpe:2.3:o:microsoft:windows_10_21h2:*:*:*:*:*:*:*:* |
| Windows 10 Version 1809 (x86/x64) | cpe:2.3:o:microsoft:windows_10_1809:*:*:*:*:*:*:*:* |
| Windows 10 Version 1607 (x86/x64) | cpe:2.3:o:microsoft:windows_10_1607:*:*:*:*:*:*:*:* |
Note: While all Windows editions include netlogon.dll, the vulnerability is only exploitable on systems configured as Active Directory domain controllers, as the DC locator response handler is only active in that role.
Tested Environment (Vulnerable)
| Field | Value |
|---|---|
| Product | Windows Server 2025 |
| Build | 26100.32690 |
| Binary | netlogon.dll |
| File Version | 10.0.26100.32684 |
| Size | 1,044,480 bytes |
| SHA256 | C1F5FA84AE46E1A39592284EBB354BD055743D72AB417C1B90A7D99383289594 |
| Installed KBs | KB5082062 (April 2026) |
Tested Environment (Fixed)
| Field | Value |
|---|---|
| Patch KB | KB5089549 (May 2026) |
| Build | 26100.32860 |
| Binary | netlogon.dll |
| File Version | 10.0.26100.32684 |
| Size | 1,044,480 bytes |
| SHA256 | A59733767285859FC3F982C46EA9C1173F4BF025D1569C03B4D137B55132B2DB |
| Feature Flag | Feature_404993339 |
Section 5: Root Cause Analysis
5a. Detailed Description
The vulnerability exists in NetpLogonPutUnicodeString, a helper function in netlogon.dll that copies a null-terminated Unicode string into an output buffer. The function accepts a source string pointer, a maximum character count, and a pointer to an advancing cursor into the output buffer, but has no parameter for the output buffer’s total size:
// NetpLogonPutUnicodeString (vulnerable version, RVA 0x8902C)
_WORD *NetpLogonPutUnicodeString(__int64 source, __int64 max_chars, unsigned __int64 *cursor)
{
// Align cursor to 2-byte boundary
NetpULongPtrRoundUp(*cursor, 2, &aligned);
dest = aligned;
// Character-by-character copy — no output bounds check
wchar = *source;
if (wchar) {
do {
if (!max_chars--) break;
*dest = wchar; // WRITE with no buffer size check
dest += 2;
wchar = *(dest + src_offset);
} while (wchar);
}
*dest = 0; // null terminator
*cursor = dest + 2; // advance cursor
}
This function is called three consecutive times by BuildSamLogonResponse (RVA 0x6AF4C) to serialize a DC locator SAM Logon Response into a fixed-size output buffer:
// BuildSamLogonResponse+0x62 through +0x96
*(_WORD *)buffer = opcode; // 2 bytes
cursor = buffer + 2;
NetpLogonPutUnicodeString(domain_info + 216, 36, &cursor); // server name
NetpLogonPutUnicodeString(username, 130, &cursor); // ATTACKER-CONTROLLED
NetpLogonPutUnicodeString(domain_info + 72, 32, &cursor); // domain name
The hardcoded second argument (36, 130, 32) limits the number of wide characters copied per call. After these three string copies, the function writes two 16-byte GUIDs and three DNS-compressed domain names (forest, domain, hostname) via NlpUtf8ToCutf8. The total data written depends on the lengths of both attacker-supplied and server-configured values.
The output buffer originates from NlGetLocalPingResponse (RVA 0x10A50), which declares a 528-byte stack array:
char Src[528]; // [rsp+0xE0h]
This buffer is passed through LogonRequestHandler to BuildSamLogonResponse. The data breakdown:
| Component | Source | Max bytes | Controlled by |
|---|---|---|---|
| Opcode | constant | 2 | N/A |
| Server name | DC config | 74 (36 wchars + null + align) | Server |
| Username | CLDAP filter | 262 (130 wchars + null + align) | Attacker |
| Domain name | DC config | 66 (32 wchars + null + align) | Server |
| Domain GUID | AD | 16 | Server |
| Zero GUID | constant | 16 | Server |
| DNS forest name | AD config | variable | Server |
| DNS domain name | AD config | variable (compressed) | Server |
| DNS hostname | AD config | variable (compressed) | Server |
| IP + flags + terminator | runtime | 16 | Server |
The attacker controls the username (up to 130 wide characters = 260 bytes), which is the largest single component. The remaining ~266+ bytes come from server-configured data. When the server’s DNS domain name is long (e.g., “dept.division.engineering.enterprise.corporation.local”), the combined DNS names push the total past 528 bytes.
Critically, the bytes that overflow past the buffer boundary are the server’s own DNS names, not attacker-controlled data. The attacker’s username fills the middle of the buffer and pushes the server’s DNS data past the end. This means the attacker controls the overflow length (by choosing a username length up to 130 wchars) but not the overflow content.
The attack entry point is CLDAP (Connectionless LDAP, UDP port 389). An attacker sends a SearchRequest with filter (&(DnsDomain=<target>)(User=<130 chars>)(NtVer=\x02\x00\x00\x00)). The NtVer value must have bits 2-3 clear (e.g., value 2) to force the non-EX response path through BuildSamLogonResponse rather than BuildSamLogonResponseEx. The full call chain:
ntdsai!LDAP_CONN::SearchRequest
-> ntdsai!LDAP_GetRootDSEAttNetlogon
-> netlogon!I_NetLogonLdapLookupEx
-> netlogon!NlGetLocalPingResponse
-> netlogon!LogonRequestHandler
-> netlogon!BuildSamLogonResponse
-> netlogon!NetpLogonPutUnicodeString (x3, no bounds check)
-> netlogon!NlpUtf8ToCutf8 (x3, DNS names overflow past buffer end)
No authentication is required — the CLDAP handler processes requests before any credential check.
5b. Vulnerable Assembly and Call Stack
netlogon.dll 10.0.26100.32684:
; NetpLogonPutUnicodeString — copy loop with no output bounds check
; RVA 0x8902C
7ffb7356902c mov [rsp+10h], rbx
7ffb73569031 push rdi
7ffb73569032 sub rsp, 20h
7ffb73569036 xor edi, edi
7ffb73569038 lea r10, [netlogon!`string'] ; default empty string
7ffb73569047 mov rbx, r8 ; rbx = cursor pointer (a3)
7ffb7356904a mov r11d, edx ; r11d = max char count (a2)
7ffb7356904d cmovne r10, rcx ; r10 = source string if non-NULL
; ... alignment via NetpULongPtrRoundUp ...
; Copy loop:
7ffb7356907d mov eax, r11d ; eax = remaining count
7ffb73569080 dec r11d ; decrement counter
7ffb73569083 test eax, eax ; was it zero?
7ffb73569085 je done ; stop if exhausted
7ffb73569087 mov word ptr [rcx], dx ; WRITE — no bounds check on dest
7ffb7356908a add rcx, 2 ; advance dest cursor
7ffb7356908e movzx edx, word ptr [rcx+r10] ; read next source char
7ffb73569096 jne loop ; continue until null
; BuildSamLogonResponse — three calls with hardcoded max char counts
; RVA 0x6AF4C
7ffb7354afba lea edx, [rbx+24h] ; edx = 0x24 = 36 (server name)
7ffb7354afc1 call netlogon!NetpLogonPutUnicodeString
7ffb7354afca mov edx, 82h ; edx = 0x82 = 130 (username)
7ffb7354afcf mov rcx, r13 ; rcx = attacker username
7ffb7354afd2 call netlogon!NetpLogonPutUnicodeString
7ffb7354afdf lea edx, [rbx+20h] ; edx = 0x20 = 32 (domain name)
7ffb7354afe2 call netlogon!NetpLogonPutUnicodeString
Call Stack (captured via KDNET at BuildSamLogonResponse entry, triggered by CLDAP ping with 130-char username):
00 netlogon!BuildSamLogonResponse
01 netlogon!LogonRequestHandler+0x380
02 netlogon!NlGetLocalPingResponse+0x521
03 netlogon!I_NetLogonLdapLookupEx+0x59c
04 ntdsai!LDAP_GetRootDSEAttNetlogon+0x84
05 ntdsai!LDAP_GetDSEAtts+0x595
06 ntdsai!LDAP_CONN::SearchRequest+0x9f9
07 ntdsai!LDAP_CONN::ProcessRequestEx+0x334f
08 ntdsai!LDAP_CONN::IoCompletion+0x6c8
09 ntdsai!ProcessNewClient+0x162
5c. Fix (Patched Version)
The May 2026 patch introduces feature flag Feature_404993339 and replaces the unsafe NetpLogonPutUnicodeString with a bounded version using RtlStringCbCopyExW:
| Aspect | Vulnerable | Patched |
|---|---|---|
| Copy method | Manual char-by-char loop with counter | RtlStringCbCopyExW with byte budget |
| Size parameter | Max character count (130 = 130 wchars = 260 bytes) | Max byte count (130 = 130 bytes = 65 wchars) |
| Return value | Pointer (no error indication) | DWORD (0 = success, 87 = ERROR_INVALID_PARAMETER) |
| Caller behavior | Return value ignored, no abort on overflow | Every call checked; function aborts immediately on error |
| Minimum size check | None | if (a2 < 2) return 87 |
| Alignment overflow check | None | if (padding > remaining) return 87 |
The old function is preserved as NetpLogonPutUnicodeStringOld behind the feature flag:
if ( !Feature_404993339__private_IsEnabledDeviceUsageNoInline() )
{
// Legacy path — vulnerable
NetpLogonPutUnicodeStringOld(server, 36, &cursor);
NetpLogonPutUnicodeStringOld(user, 130, &cursor);
NetpLogonPutUnicodeStringOld(domain, 32, &cursor);
}
else
{
// Patched path — bounded, error-checked
if (NetpLogonPutUnicodeString(server, 0x24, &cursor)) return error;
if (NetpLogonPutUnicodeString(user, 0x82, &cursor)) return error;
if (NetpLogonPutUnicodeString(domain, 0x20, &cursor)) return error;
}
Additionally, NlGetLocalPingResponse zero-initializes the buffer when the flag is enabled:
if (Feature_404993339__private_IsEnabledDeviceUsageNoInline())
memset(Src, 0, 0x208);
5d. Impact
A single unauthenticated CLDAP packet (UDP 389) crashes the LSASS process on a vulnerable domain controller, forcing a system reboot. The overflow corrupts the GS stack cookie placed by BuildSamLogonResponse, triggering __fastfail(FAST_FAIL_STACK_COOKIE_CHECK_FAILURE) which terminates LSASS with exception code 0xc0000409 (STATUS_STACK_BUFFER_OVERRUN). Since LSASS hosts all authentication services (Kerberos, NTLM, Netlogon secure channel), its crash denies authentication to the entire Active Directory domain until the DC reboots. This was confirmed experimentally: a DC with the domain name “dept.division.engineering.enterprise.corporation.local” (54 characters) crashed and rebooted upon receiving the PoC packet.
Achieving remote code execution beyond DoS is unlikely in practice. The bytes that overflow the buffer boundary are the server’s own DNS-compressed hostname and domain labels, not attacker-controlled data. Even if the GS stack cookie could be bypassed (e.g., via a separate information disclosure), the attacker cannot place a controlled return address in the overflow region. The return address for NlGetLocalPingResponse sits 72 bytes (0x48) past the buffer end, while the maximum measured overflow with a 63-character DNS hostname label was approximately 51 bytes — 21 bytes short of reaching the return address. Extending the overflow further would require multiple long DNS subdomain labels in the server’s FQDN, which is limited by the 255-character DNS name maximum.
Section 6: Proof-of-Concept
6a. PoC Code
Download poc_cve_2026_41089.py (enterprise email verification required)
6b. Reproduce Instructions
Prerequisites:
- Target: Windows Server domain controller (pre-patch, without KB5089549) with a DNS domain name of 50+ characters
- Attacker: Python 3 with
pyasn1library - Network: UDP port 389 accessible from attacker to DC (Windows Firewall may need to be disabled)
Steps:
Ensure the target DC’s DNS domain name is sufficiently long. For testing, promote a server as a DC with a domain like
dept.division.engineering.enterprise.corporation.localand set a long DNS hostname (up to 63 characters for the hostname label).Verify CLDAP connectivity with a benign ping (short username):
python poc_cve_2026_41089.py <DC_IP> <dns_domain> 10Expected: “Response received: NNN bytes”
Send the overflow payload (130-character username):
python poc_cve_2026_41089.py <DC_IP> <dns_domain> 130Expected outcome: no CLDAP response (timeout). The LSASS process crashes with exception
0xc0000409. The domain controller reboots within approximately 60 seconds. After reboot, LSASS and Netlogon restart normally.Verify crash in the Application event log on the DC:
Faulting application name: lsass.exe Faulting module name: netlogon.DLL Exception code: 0xc0000409
6c. Test Results
Test environment: LONGDOMAINDC01.dept.division.engineering.enterprise.corporation.local (10.0.50.21)
| Metric | Short domain (example.com) | Long domain (54 chars) |
|---|---|---|
| Buffer size | 528 bytes | 528 bytes |
| Total data written | 363 bytes | 492+ bytes |
| Overflow past buffer | 0 bytes | 2 bytes (14-char hostname) |
| LSASS crash | No | Yes (with 63-char hostname) |
| Exception code | N/A | 0xc0000409 (STATUS_STACK_BUFFER_OVERRUN) |
| DC reboot | No | Yes |
| Attack packets | 1 | 1 |
| Authentication required | No | No |
Overflow scaling by DNS hostname length:
| Hostname label length | Overflow extent | Result |
|---|---|---|
| 14 chars | 2 bytes | No crash (below stack cookie) |
| ~30 chars | ~18 bytes | Stack cookie corrupted, crash |
| 63 chars (max DNS label) | ~51 bytes | Confirmed crash + DC reboot |
Stack layout past the buffer:
| Offset from buffer end | Content |
|---|---|
| +0x00 | GS stack cookie (corrupted at 2+ bytes overflow) |
| +0x48 (72 bytes) | NlGetLocalPingResponse return address (requires 72+ bytes to reach) |
WER crash log:
Faulting application name: lsass.exe, version: 10.0.26100.7309
Faulting module name: netlogon.DLL, version: 10.0.26100.32522
Exception code: 0xc0000409
Fault offset: 0x000000000002399d
Faulting process id: 0x31C
6d. Patched System Verification
The patched netlogon.dll (KB5089549, SHA256: A5973376...) was tested on the patched DC. When Feature_404993339 is enabled, BuildSamLogonResponse calls the new NetpLogonPutUnicodeString which uses RtlStringCbCopyExW with a byte-count budget. The same CLDAP packet sent to the patched DC returned a normal 166-byte response. No overflow, no crash.
Section 7: Detection
Note: The detection rules below are provided as a starting point. Validate and tune them in your own environment before deploying to production.
Section 7A: Network-Based Detection
Signature-Based Detection
The attack uses CLDAP (LDAP over UDP 389) with a SearchRequest containing a “User” filter attribute with an unusually long value (100+ UTF-8 bytes). Legitimate DC locator pings typically have short usernames (under 20 characters) or omit the User attribute entirely. The NtVer attribute value must have bits 2-3 clear (typically \x02\x00\x00\x00) to trigger the vulnerable non-EX response path — legitimate clients almost always set NtVer=6 (which includes the 5EX bit and takes a different, non-vulnerable code path).
Suricata Rules
# Detect CLDAP DC locator ping with oversized User attribute
alert udp $EXTERNAL_NET any -> $HOME_NET 389 (msg:"CVE-2026-41089 Netlogon CLDAP overflow - long User in DC ping"; \
content:"|30|"; depth:1; \
content:"User"; nocase; \
isdataat:100,relative; \
threshold:type limit, track by_src, count 1, seconds 60; \
reference:cve,2026-41089; \
classtype:attempted-admin; \
sid:2026041089; rev:1;)
# Detect CLDAP with NtVer=2 (non-EX path, required for exploit) and User attribute
alert udp $EXTERNAL_NET any -> $HOME_NET 389 (msg:"CVE-2026-41089 Netlogon CLDAP overflow - NtVer non-EX path"; \
content:"|30|"; depth:1; \
content:"NtVer"; \
content:"|04 04 02 00 00 00|"; distance:0; within:8; \
content:"User"; \
isdataat:80,relative; \
reference:cve,2026-41089; \
classtype:attempted-admin; \
sid:2026041090; rev:1;)
Byte Offset Reference
| Offset | Field | Description |
|---|---|---|
| 0 | 0x30 | LDAP Message (BER SEQUENCE tag) |
| +2-4 | Message ID | BER INTEGER |
| +5 | 0x63 | SearchRequest (APPLICATION[3] CONSTRUCTED) |
| variable | Base DN | Empty OCTET STRING (0x04 0x00) |
| variable | Scope | ENUMERATED 0 (baseObject) |
| variable | Filter | AND filter (0xA0) containing DnsDomain, User, NtVer |
| variable | User value | OCTET STRING — attack payload (100+ bytes of UTF-8) |
| variable | NtVer value | OCTET STRING — 4 bytes LE (0x02 0x00 0x00 0x00 for exploit) |
| variable | Attributes | SEQUENCE containing “Netlogon” |
Section 7B: Host-Based Detection (YARA)
The following YARA rules detect vulnerable (unpatched) versions of netlogon.dll by matching the byte patterns of the unsafe NetpLogonPutUnicodeString copy loop and the three consecutive unbounded calls in BuildSamLogonResponse.
rule CVE_2026_41089_Netlogon_Vulnerable_PutUnicodeString
{
meta:
description = "Detects vulnerable NetpLogonPutUnicodeString in netlogon.dll (unbounded copy loop)"
cve = "CVE-2026-41089"
component = "netlogon.dll"
severity = "Critical"
type = "vulnerability"
strings:
// NetpLogonPutUnicodeString copy loop — no output bounds check
// mov eax, r11d; dec r11d; test eax, eax; je short; mov [rcx], dx; add rcx, 2
$copy_loop = {
44 89 D8 // mov eax, r11d
41 FF CB // dec r11d
85 C0 // test eax, eax
74 ?? // je done
66 89 11 // mov word ptr [rcx], dx
48 83 C1 02 // add rcx, 2
}
// BuildSamLogonResponse — three calls with hardcoded max char counts 0x24, 0x82, 0x20
// lea edx, [rbx+24h]; call <NetpLogonPutUnicodeString>
// mov edx, 82h
// call <NetpLogonPutUnicodeString>
// lea edx, [rbx+20h]; call <NetpLogonPutUnicodeString>
$three_calls = {
8D 53 24 // lea edx, [rbx+24h]
E8 ?? ?? ?? ?? // call NetpLogonPutUnicodeString
[0-16]
BA 82 00 00 00 // mov edx, 82h
[0-8]
E8 ?? ?? ?? ?? // call NetpLogonPutUnicodeString
[0-16]
8D 53 20 // lea edx, [rbx+20h]
E8 ?? ?? ?? ?? // call NetpLogonPutUnicodeString
}
condition:
uint16(0) == 0x5A4D and
($copy_loop or $three_calls) and
for any i in (0..pe.number_of_sections - 1) : (
pe.sections[i].name == ".text"
)
}
rule CVE_2026_41089_Netlogon_Patched
{
meta:
description = "Detects patched netlogon.dll with Feature_404993339 and RtlStringCbCopyExW"
cve = "CVE-2026-41089"
component = "netlogon.dll"
type = "patch_verification"
strings:
$feature_flag = "Feature_404993339" ascii wide
$safe_copy = "RtlStringCbCopyExW" ascii
condition:
uint16(0) == 0x5A4D and
$feature_flag and $safe_copy
}
Usage:
# Scan a single DLL
yara -s cve_2026_41089.yar C:\Windows\System32\netlogon.dll
# Scan all domain controllers in a share
yara -r cve_2026_41089.yar \\dc01\c$\Windows\System32\netlogon.dll
| Rule | Match means |
|---|---|
CVE_2026_41089_Netlogon_Vulnerable_PutUnicodeString | Unpatched — vulnerable to CVE-2026-41089 |
CVE_2026_41089_Netlogon_Patched | Patched — KB5089549 applied with Feature_404993339 |
Section 8: References
| Source | URL |
|---|---|
| Microsoft Advisory | https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-41089 |
| MITRE CVE | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-41089 |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-41089 |
| Microsoft KB (Server 2025) | https://support.microsoft.com/help/KB5089549 |