May 6, 2019·4 min read·engineeringgosecuritywindowslinking

Go 1.11.10 backports Windows DLL loading restrictions

Go 1.11.10 brings the system DLL loading fix to the older release branch. It also accepts valid ELF inputs with repeated section names when linking C++ code.

Staying on an older Go release branch does not remove the need to understand changes in the runtime. Go 1.11.10 brings a Windows DLL-loading security fix to that branch, alongside a linker correction for valid ELF object files. Both changes concern the boundary between a Go executable and code supplied through the platform or another compiler.

The release history dates this patch to May 6, 2019. The release comparison shows the Windows fix identified as CVE-2019-9634. Its commit states that some loading paths remained after earlier call sites had moved to safer loading mechanisms.

I appreciate that Go lets application code remain fairly direct while the runtime handles platform details. That division makes the runtime version a meaningful deployment input. A program can contain no explicit DLL-loading code of its own and still inherit the runtime's decisions about where Windows libraries come from.

A bare DLL name leaves a search decision

The patch introduces a helper called windowsLoadSystemLib. Its purpose is to load a library expected to reside in the Windows system directory. A bare filename alone leaves room for directory-search behavior, which is the source of the directory-injection risk addressed by this change.

When the required Windows facilities exist, the helper calls LoadLibraryExA with _LOAD_LIBRARY_SEARCH_SYSTEM32. The restriction expresses where the requested library must be found. The runtime also checks for the relevant loader functions and AddDllDirectory before enabling this route.

On older or unpatched systems, it obtains the system directory using GetSystemDirectoryA, appends the library name and passes that absolute path to LoadLibraryA. This route uses a different loading operation while preserving the intended directory. If retrieving the system directory fails or produces an unusable result, the runtime terminates instead of proceeding with an unrestricted search.

The older-system behavior is an essential part of the fix. Previously, the fallback for syscall.loadsystemlibrary acknowledged a potential security risk and used ordinary filename loading when the preferred facility was unavailable. Its patched entry point accepts an absolute filepath alongside the filename, so it can preserve the restriction in its fallback too.

The tracking issue opened in March states the policy in terms of the loader calls. Prefer LoadLibraryEx, or use LoadLibrary with a correctly computed absolute system path. It permits an exception for the exact name kernel32.dll. The commit similarly distinguishes that library from the others, so a description claiming that all loading now uses absolute paths would be inaccurate.

Implicit runtime imports need the same policy

Some affected libraries previously entered the runtime through implicit dynamic imports. The patch removes those imports for functions from winmm.dll and ws2_32.dll, then loads the libraries explicitly through the new helper. It locates the required function addresses after loading and checks that they are available.

Existing explicit loads of advapi32.dll and ntdll.dll also move to the helper. These changes explain why the issue cannot be assessed solely by searching an application's packages for its own use of LoadLibrary. Operating-system support inside the runtime has loading requirements of its own.

The security property is specific. Runtime loading restricts the source directory for the affected system libraries. General verification of arbitrary DLL contents and policies for an application's plugins remain outside this patch. Those would require separate mechanisms and evidence.

This also establishes the practical upgrade unit. A compiler update changes future builds, while an already built program still contains its existing runtime. Programs using the affected implementation need rebuilding and redistribution with the corrected toolchain. The older release branch now offers that implementation without requiring a move to the newer major Go release.

I favor that narrow backport because it makes the decision easier to review. The release comparison exposes the code that changes and the policy it implements. There is no need to infer a new application architecture from a patch whose purpose is to constrain existing runtime loading.

Valid ELF sections can share a name

The linker correction handles a different boundary. The release comparison explains that newer Clang versions can produce multiple ELF sections named .text when using vague C++ linkage. That input is valid ELF, but Go's internal linker previously reported that a section symbol had been listed multiple times.

The fix renames section symbol names when they collide. It does not require the original object file to abandon valid section naming. The linker must distinguish its own internal symbols without treating a repeated external section name as proof that the input is invalid.

This is a useful compatibility detail for programs that incorporate C++ object code. The Go source may be unchanged while a C++ compiler changes how it emits valid object files. A resulting link failure can therefore come from an assumption in the receiving linker rather than an invalid source declaration.

The associated test has its own qualification. A follow-up commit requires cgo support for the test, explaining that it needs the correct flags for invoking the C compiler. The test does not inherently require cgo semantics, but the test environment needs the platform knowledge supplied by that support. That distinction keeps the test's prerequisite separate from the behavior being tested.

Reading a small release by its boundaries

The Windows and ELF changes solve different problems, but both show why platform integration belongs in a patch review. Windows library loading must constrain the directory chosen for system code. ELF loading must accept valid input even when several sections have the same name. Treating either assumption as an incidental detail would miss the behavior corrected by this release.

For an application remaining on Go 1.11, the May patch supplies the corrected runtime and linker through the existing branch. Windows executables receive the system-directory loading policy on rebuild, while affected C++ integrations can link object files that the older linker rejected. Those outcomes follow directly from the two implementation changes in the release comparison.