Skip to content

What SMB2 needs from a security package

What a failing session setup looks like on the wire

pktmon ships with Windows. These commands capture an SMB2 connection that authenticates with a custom security package:

pktmon filter add -p 445
pktmon start --etw -f C:\temp\capture.etl
<reproduce the mount>
pktmon stop
pktmon pcapng C:\temp\capture.etl -o capture.pcapng
pktmon filter remove
Wireshark capture of an SMB2 session showing Session Setup Response Error STATUS_INVALID_PARAMETER followed by a TCP reset and retry
capture.pcapng opened in Wireshark: Negotiate Protocol succeeds, the first Session Setup round returns the normal STATUS_MORE_PROCESSING_REQUIRED, then a later round returns Error: STATUS_INVALID_PARAMETER (0xc000000d). The client sends an RST right away and reconnects.

STATUS_MORE_PROCESSING_REQUIRED is expected in a multi-round NegoEx exchange. STATUS_INVALID_PARAMETER is not, and it is neither a network nor a credential problem. The security package returned the wrong answer to one query.

What SMB2 asks the package for

In each session setup round, the SMB2 NegoEx fast path calls QueryContextAttributes several times. In kernel mode, that reaches the package's own dispatch (SpGetExtendedInformation/EIDQueryAttributes). The attributes seen in a live trace, most frequent first:

  • SECPKG_ATTR_SIZES, SECPKG_ATTR_NAMES, SECPKG_ATTR_PACKAGE_INFO, SECPKG_ATTR_NEGOTIATION_INFO, SECPKG_ATTR_USER_FLAGS, SECPKG_ATTR_LOGOFF_TIME: asked every round, mostly bookkeeping.
  • SECPKG_ATTR_SESSION_KEY: asked several times per round. The SMB2 signing key derives from it.
  • SECPKG_ATTR_NEGO_KEYS: server side only. negoexts.dll uses it to check the NegoEx MIC. The client never asks for it and computes its MIC from the outer session key. negoexts!WSTRetrieveSessionKeys accepts one failure here, STATUS_NOT_FOUND ("use the outer key"). Any other error fails the session on the client.
  • SECPKG_ATTR_FLAGS and SECPKG_ATTR_IS_LOOPBACK: see below.

A package that doesn't know one of these attributes still has to answer something. The wrong answer is what broke the session above.

How a rejected query becomes a wire error

The package's default case for an unknown ContextAttribute was:

default:
    Status = SEC_E_INVALID_PARAMETER;   // 0x8009035D

Most SSPI code does this for an attribute it doesn't implement. But the tolerance in negoexts.dll (the STATUS_NOT_FOUND case for nego keys, above) is its own logic. The kernel fast path under it doesn't share it. ksecdd.sys and srv2.sys pass the package's SEC_E_INVALID_PARAMETER through unchanged. It reaches the wire as STATUS_INVALID_PARAMETER, and the whole session fails, not just that query.

So an attribute that looks optional still needs a valid answer.

Case study: SECPKG_ATTR_IS_LOOPBACK

The query behind the capture above is SECPKG_ATTR_IS_LOOPBACK, id 37 (0x25). It is a recent addition to sspi.h and is missing from older copies of the headers, so the package had no case for it and fell into default:. The live trace shows it:

Live ETW trace showing KspQueryAttributes attribute 0x25 failing with SEC_E_INVALID_PARAMETER, code 0x8009035D
Live trace, kernel path: KspQueryAttributes(173) : Enter ContextId = ... attribute 0x25, then EIDQueryAttributes(472) : ContextAttribute=0x00000025 SEC_E_INVALID_PARAMETER. 0x25 is 37, SECPKG_ATTR_IS_LOOPBACK. The next query, SECPKG_ATTR_NAMES (attribute 0x1), returns Status = 0x00000000.

The attribute only asks whether the authentication targets the local machine. The fast path still fails the session when the package returns an error. The fix:

case SECPKG_ATTR_IS_LOOPBACK:
    // Real struct layout isn't publicly documented; writing just the
    // leading BOOLEAN is safe regardless of the caller's actual buffer size.
    *((PBOOLEAN) pBuffer) = FALSE;
    Status = STATUS_SUCCESS;
    break;

The answer is always FALSE: this package only authenticates remote peers, never a loopback connection. With this case in place, the session setup completes.

Related: a kernel crash in NegoExtsDeleteKernelContext

SMB2 can also hit a kernel crash in ksecpkg!NegoExtsDeleteKernelContext. That one comes from the package lookup, not from an attribute answer. It is covered, with its own capture, in Load a kernel-mode security package without a reboot.