Skip to content

SteamNetworkingThreadProc: stack-buffer-overflow (48-byte read of 24-byte info) in Windows thread-naming RaiseException under MSVC AddressSanitizer (x64) #418

Description

@drsnuggles8

Background

I'm using GameNetworkingSockets as the networking layer in my game engine, and as part of CI we run the test suite under sanitizers. On the Windows + MSVC AddressSanitizer job (/fsanitize=address, x64, Release), every test process that calls GameNetworkingSockets_Init() aborts with a stack-buffer-overflow a moment after the service thread spawns. The same suites are completely clean under Linux ASan/UBSan/TSan — it's Windows-only.

I traced it to the GNS service thread's thread-naming code. I believe it's a genuine (if benign-at-runtime) stack over-read with a trivial fix — details below.

Environment

  • GameNetworkingSockets: current master (reproduced on d67d1673, but the offending line is byte-identical on master today)
  • Compiler: MSVC, /fsanitize=address, x64, Release
  • OS: Windows 11

ASan report

==NNN==ERROR: AddressSanitizer: stack-buffer-overflow ...
READ of size 48 at 0x... thread T4
    #0 memmove
    #1 KERNELBASE.dll                                         (RaiseException)
    #2 SteamNetworkingSockets_SetPreFormatDebugOutputHandler  (ICF-folded -> SteamNetworkingThreadProc)
    ...
    #5 asan_thread_start

Address 0x... is located in stack of thread T4 at offset 56 in frame
  This frame has 1 object(s):
    [32, 56) 'info' <== Memory access at offset 56 overflows this variable

Thread T4 created by T0 here:
    ...
    #5 GameNetworkingSockets_Init

Note: this is a Release build without /DEBUG, so the inner frames get ICF-folded onto the nearest exported symbols and misreport as SteamNetworkingSockets_* / pugi::xpath_*. But the 24-byte info stack object, the RaiseException (KERNELBASE) frame, and "created by … GameNetworkingSockets_Init" pin it to SteamNetworkingThreadProc().

Root cause

In src/steamnetworkingsockets/clientlib/steamnetworkingsockets_socketthread.cpp, SteamNetworkingThreadProc() names the service thread with the legacy MSVC RaiseException trick (~line 3405):

typedef struct tagTHREADNAME_INFO
{
    DWORD   dwType;
    LPCSTR  szName;
    DWORD   dwThreadID;
    DWORD   dwFlags;
} THREADNAME_INFO;            // sizeof == 24 on x64 (4 + pad + 8 + 4 + 4)

THREADNAME_INFO info;
...
RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info );

RaiseException(code, flags, nNumberOfArguments, lpArguments) reads nNumberOfArguments * sizeof(ULONG_PTR) bytes from lpArguments. The argument count is computed with sizeof(DWORD) as the divisor:

arch sizeof(info) sizeof(DWORD) nNumberOfArguments bytes read = n * sizeof(ULONG_PTR) buffer result
x64 24 4 6 6 × 8 = 48 24 24-byte over-read
x86 16 4 4 4 × 4 = 16 16 OK

On x64 (LLP64), sizeof(DWORD) (4) ≠ sizeof(ULONG_PTR) (8), so the count is doubled and RaiseException reads 48 bytes out of the 24-byte info, running 24 bytes into adjacent stack. On x86 the divisor coincidentally matches the pointer size, so it's correct there — which is why this stayed latent. It's harmless in normal runs (the extra "arguments" are ignored, and __except(EXCEPTION_CONTINUE_EXECUTION) swallows the exception), but it is a real out-of-bounds read and MSVC ASan correctly halts on it.

Suggested fix

Use sizeof(ULONG_PTR) as the divisor — the canonical Microsoft "How to: Set a Thread Name in Native Code" form — which reads exactly sizeof(info) on both architectures (3 args on x64, 4 on x86):

-        RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info );
+        RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info );

(Alternatively, the whole RaiseException block could be replaced with SetThreadDescription() on Windows 10 1607+, but the one-line divisor fix is the minimal change.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions