Skip to content

[BUG] Windows: getErrorMessage() reads uninitialized pointer when FormatMessage fails #3714

Description

@phil-opp

On Windows, getErrorMessage() constructs a std::string from lpMsgBuf without checking whether FormatMessage actually wrote to it. When FormatMessage fails, lpMsgBuf is still uninitialized and std::string's constructor runs strlen on an indeterminate stack value.

It also leaks the LocalAlloc'd buffer on the success path.

Description

src/backend/common/module_loading_windows.cpp:31-41:

string getErrorMessage() {
    const char* lpMsgBuf;                    // uninitialized
    DWORD dw = GetLastError();

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                      FORMAT_MESSAGE_IGNORE_INSERTS,
                  NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                  (LPTSTR)&lpMsgBuf, 0, NULL);
    string error_message(lpMsgBuf);          // no check, no LocalFree
    return error_message;
}

FormatMessage returns 0 and does not touch the output pointer when the message cannot be formatted — which happens for error codes with no entry in the system message table (common for loader and NTSTATUS-derived codes, and for codes left behind by a DLL whose DllMain failed). The return value is discarded here, so lpMsgBuf is then dereferenced regardless.

FORMAT_MESSAGE_ALLOCATE_BUFFER also requires the caller to LocalFree the buffer; that never happens, so every successful call leaks.

Why this is on a hot path

getErrorMessage() is called on every failed LoadLibrary during backend probing, src/api/unified/symbol_manager.cpp:

AF_TRACE("Failed to load {}", getErrorMessage());

AF_TRACE evaluates its arguments unconditionally — the spdlog level filter applies after the call — so this runs even with tracing disabled. The unified loader tries roughly a dozen path prefixes across each backend, so on a typical process start most of those attempts fail and this function runs tens of times before any user code executes.

Other callers include src/backend/cuda/cusparseModule.cpp, src/backend/cuda/cudnnModule.cpp, and src/backend/common/graphics_common.cpp.

Impact

Reading an indeterminate pointer produces either a garbage error string or STATUS_ACCESS_VIOLATION (0xC0000005), during library initialization and before any user code runs. Intermittent, since it depends on the stack residue at that address.

Reproducible Code and/or Steps

System Information

Checklist

  • Using the latest available ArrayFire release
  • GPU drivers are up to date

Disclaimer

Found by Claude Opus 5. The prompt was to look for potential sources for 0xC0000005 errors on Windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions