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

FieldValue
Primary CWECWE-121: Stack-based Buffer Overflow
Related CWECWE-120: Buffer Copy without Checking Size of Input

Section 3: Severity

Microsoft Advisory Score (CVSS 3.1)

FieldValue
Score9.8 (Critical)
VectorCVSS: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 GroupMetricValue
Base - ExploitabilityAttack Vector (AV)Network
Attack Complexity (AC)Low
Attack Requirements (AT)Present
Privileges Required (PR)None
User Interaction (UI)None
Base - Vulnerable SystemConfidentiality (VC)None
Integrity (VI)None
Availability (VA)High
Base - Subsequent SystemConfidentiality (SC)None
Integrity (SI)None
Availability (SA)High
ThreatExploit Maturity (E)Proof-of-Concept
FieldValue
CVSS 4.0 Score8.8 (High)
CVSS 4.0 VectorCVSS: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

ProductCPE 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)

FieldValue
ProductWindows Server 2025
Build26100.32690
Binarynetlogon.dll
File Version10.0.26100.32684
Size1,044,480 bytes
SHA256C1F5FA84AE46E1A39592284EBB354BD055743D72AB417C1B90A7D99383289594
Installed KBsKB5082062 (April 2026)

Tested Environment (Fixed)

FieldValue
Patch KBKB5089549 (May 2026)
Build26100.32860
Binarynetlogon.dll
File Version10.0.26100.32684
Size1,044,480 bytes
SHA256A59733767285859FC3F982C46EA9C1173F4BF025D1569C03B4D137B55132B2DB
Feature FlagFeature_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:

ComponentSourceMax bytesControlled by
Opcodeconstant2N/A
Server nameDC config74 (36 wchars + null + align)Server
UsernameCLDAP filter262 (130 wchars + null + align)Attacker
Domain nameDC config66 (32 wchars + null + align)Server
Domain GUIDAD16Server
Zero GUIDconstant16Server
DNS forest nameAD configvariableServer
DNS domain nameAD configvariable (compressed)Server
DNS hostnameAD configvariable (compressed)Server
IP + flags + terminatorruntime16Server

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:

AspectVulnerablePatched
Copy methodManual char-by-char loop with counterRtlStringCbCopyExW with byte budget
Size parameterMax character count (130 = 130 wchars = 260 bytes)Max byte count (130 = 130 bytes = 65 wchars)
Return valuePointer (no error indication)DWORD (0 = success, 87 = ERROR_INVALID_PARAMETER)
Caller behaviorReturn value ignored, no abort on overflowEvery call checked; function aborts immediately on error
Minimum size checkNoneif (a2 < 2) return 87
Alignment overflow checkNoneif (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 pyasn1 library
  • Network: UDP port 389 accessible from attacker to DC (Windows Firewall may need to be disabled)

Steps:

  1. 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.local and set a long DNS hostname (up to 63 characters for the hostname label).

  2. Verify CLDAP connectivity with a benign ping (short username):

    python poc_cve_2026_41089.py <DC_IP> <dns_domain> 10
    

    Expected: “Response received: NNN bytes”

  3. Send the overflow payload (130-character username):

    python poc_cve_2026_41089.py <DC_IP> <dns_domain> 130
    
  4. Expected 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.

  5. 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)

MetricShort domain (example.com)Long domain (54 chars)
Buffer size528 bytes528 bytes
Total data written363 bytes492+ bytes
Overflow past buffer0 bytes2 bytes (14-char hostname)
LSASS crashNoYes (with 63-char hostname)
Exception codeN/A0xc0000409 (STATUS_STACK_BUFFER_OVERRUN)
DC rebootNoYes
Attack packets11
Authentication requiredNoNo

Overflow scaling by DNS hostname length:

Hostname label lengthOverflow extentResult
14 chars2 bytesNo crash (below stack cookie)
~30 chars~18 bytesStack cookie corrupted, crash
63 chars (max DNS label)~51 bytesConfirmed crash + DC reboot

Stack layout past the buffer:

Offset from buffer endContent
+0x00GS 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

OffsetFieldDescription
00x30LDAP Message (BER SEQUENCE tag)
+2-4Message IDBER INTEGER
+50x63SearchRequest (APPLICATION[3] CONSTRUCTED)
variableBase DNEmpty OCTET STRING (0x04 0x00)
variableScopeENUMERATED 0 (baseObject)
variableFilterAND filter (0xA0) containing DnsDomain, User, NtVer
variableUser valueOCTET STRING — attack payload (100+ bytes of UTF-8)
variableNtVer valueOCTET STRING — 4 bytes LE (0x02 0x00 0x00 0x00 for exploit)
variableAttributesSEQUENCE 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
RuleMatch means
CVE_2026_41089_Netlogon_Vulnerable_PutUnicodeStringUnpatched — vulnerable to CVE-2026-41089
CVE_2026_41089_Netlogon_PatchedPatched — KB5089549 applied with Feature_404993339

Section 8: References

SourceURL
Microsoft Advisoryhttps://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-41089
MITRE CVEhttps://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-41089
NVDhttps://nvd.nist.gov/vuln/detail/CVE-2026-41089
Microsoft KB (Server 2025)https://support.microsoft.com/help/KB5089549