Skip to content

time: DLL side-loading vulnerability on non-English Windows systems #75648

@ozanh

Description

@ozanh

Summary

A DLL side-loading vulnerability affects all Windows builds of Go when running on non-English Windows. When the
standard library time package initializes the local time zone (time.initLocal), it can trigger Windows to call
RegLoadMUIStringW (via GetMUIStringValue) without specifying a search directory on the first call. As a result,
Windows may attempt to load tzres.dll from the application's current working directory before falling back to
System32. This creates a DLL side-loading risk on machines with non-English locales.

Why tzres.dll is loaded

  • time.initLocal lazily initializes local time.Location data.
  • If a time zone abbreviation is not found in Go's internal map on non-English systems, the runtime calls into Windows
    to retrieve a localized string (via GetMUIStringValue).
  • The underlying Windows call (RegLoadMUIStringW) is invoked first without a directory parameter, which leads
    Windows to look for tzres.dll in the current working directory.
  • Only on a subsequent attempt does Windows use the System32 directory, so the initial nil-directory call is the root
    cause of the observable attempt to load tzres.dll from the CWD.

Reproduction

  1. Use a non-English Windows installation (e.g., I used Turkish Windows 10).
  2. Run a minimal Go program that touches time.Local. For example, this one line triggers the behavior:
    println(time.Local.String())
  3. Observe the behavior with Sysinternals Process Monitor.

Note about attempted mitigations (no effect)

The following Windows API calls, when invoked early, do not prevent the observed RegLoadMUIStringW behavior and
therefore do not stop tzres.dll being probed in the CWD:

  • SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32)
  • SetDllDirectoryW(L"")
  • SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE | BASE_SEARCH_PATH_PERMANENT)

Workaround Used

As a temporary mitigation in our environment, we used Go's -overlay build feature to override the GetMUIStringValue
function. This allowed us to patch the behavior at build time and avoid the vulnerable DLL load sequence without
modifying upstream Go sources.

Note: The Go Security team has requested that this report be tracked as a public issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.OS-WindowsSecurity

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions