@bradfitz suggested I open an issue for this rather than merely pushing fixes up to gerrit, so that we can track this for a 1.12 point release.
This runtime PR cleans up some LoadLibrary usage: https://go-review.googlesource.com/c/go/+/165798
And this x/sys PR makes the fallback there more reliable: https://go-review.googlesource.com/c/sys/+/165759
The goal is that everywhere LoadLibraryEx preferred, but when not possible, LoadLibrary is called only with either an absolute path computed properly with GetSystemDirectory() or with the exact string kernel32.dll.
I haven't yet dynamically traced the exes yet to verify I've whacked them all now, but hopefully I or someone else can get that done before this issue is closed.
Previously: #14959
I assume this is a continuation of that effort?
/cc @alexbrainman
There's also #28978 regarding winmm.dll. This patchset was posted to those comments: https://github.com/golang/go/compare/master...sj26:load-winmm-dynamically and would fix winmm.dll loading to use LoadLibrarxEx as well, if this has not already been merged in.
Thanks for directing me toward it. https://go-review.googlesource.com/c/go/+/165798 now fixes the winmm.dll issue too.
Change https://golang.org/cl/165798 mentions this issue: runtime: safely load DLLs
Change https://golang.org/cl/165759 mentions this issue: windows: use proper system directory path in fallback loader
I can confirm that the vulnerability is there and appears to be at least mitigated by the CL:

(Rumor has it, Microsoft will soon fix all security vulnerabilities by removing calc.exe from the default install. Time to look for a new career I guess.)
Boring code:
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if (fdwReason != DLL_PROCESS_ATTACH)
return TRUE;
STARTUPINFOW si = { .cb = sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
CreateProcessW(L"c:\\windows\\system32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
TerminateProcess(GetCurrentProcess(), 0);
return TRUE;
}
__declspec(dllexport) __stdcall MMRESULT timeBeginPeriod(UINT uPeriod) { }
__declspec(dllexport) __stdcall MMRESULT timeEndPeriod(UINT uPeriod) { }
The dynamic tracing situation after applying the CLs linked herein is starting to look decent:

We now only ever LoadLibrary on kernel32.dll, and the rest get LoadLibraryEx with SEARCH_SYSTEM32.
Meanwhile statically the situation is looking better too:

All of those DLLs have been in the KnownDLLs entry going back to Windows XP SP3 at the latest, which is what we're going for. But more to the point, if you compile a more "pure" Go program that doesn't use cgo, the result is totally clean:

I'll continue to play with this and trace things further, like undocumented lower level loading functions like LdrLoadDll and such.
@zx2c4, thanks for your work on this. (And I'm a huge fan of WireGuard!)
Any idea on how we might write a test to catch regressions in this space in the future? Can we do that tracing on a child process ala strace/dtruss without using some GUI app manually?
What about a test trying to attack it instead? Try to run testprog.exe in a directory with a handful of malicious DLLs (that just os.Exit(1)) with all the common names. Then see if testprog.exe exited successfully or not. Then the question is how to build that malicious DLL within the Go build system. We don't have a C compiler available in our builder images, IIRC. Perhaps we just check in the malicious exit-with-failure DLL in as a testdata file? Kinda sketchy to check in random binaries, though, even if we name it sketchytest.dll
That's a nice idea. We could write it in Go assembly, I suppose. Or if you know a way of creating a Go shared object with some exported symbols that doesn't pull in the Go runtime, but that sounds impossible. In addition to "all the common names", we could strings the binary looking for anything that ends in ".dll".
bradfitz added this to the Go1.13 milestone a day ago
Can we add the 1.12.1 and security marker for this as well? And NeedsFix-->HasFix.
@gopherbot, please backport to Go 1.12.
Backport issue(s) opened: #30666 (for 1.12).
Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://golang.org/wiki/MinorReleases.
@zx2c4, we don't have a HasFix label. https://dev.golang.org/ cross-references our open bugs against open code reviews.
I can confirm that the vulnerability is there and appears to be at least mitigated by the CL:
Do your changes also mitigate use of kernel32.dll file instead of winmm.dll ? See my comment here https://github.com/golang/go/issues/28978#issuecomment-442380094
Alex
Do your changes also mitigate use of kernel32.dll file instead of winmm.dll ? See my comment here #28978 (comment)
kernel32.dll (and ntdll.dll) are generally loaded into every win32 subsystem process regardless of anything else and I'm not so sure those can be injected at all. (We could probably even get the address of kernel32 by following the breadcrumbs from gs:[60h], but nobody does that.)
Could you verify your findings about notepad in that comment? I realize that if you move notepad outside of system32 to a different directory, you might have general problems starting it. But can you actually inject something malicious into it by dropping a trojan'd kernel32.dll? Note that if your findings are correct, you should probably witness either your payload being executed (e.g. calc.exe), or if you didn't bother to make a real dll, at least the error box from the linker.
Could you verify your findings about notepad in that comment? I realize that if you move notepad outside of system32 to a different directory, you might have general problems starting it.
What I did last time it this:
C:\Users\Alex\AppData\Local\Temp>mkdir z
C:\Users\Alex\AppData\Local\Temp>cd z
C:\Users\Alex\AppData\Local\Temp\z>copy c:\Windows\notepad.exe .
1 file(s) copied.
C:\Users\Alex\AppData\Local\Temp\z>echo abc > kernel32.dll
C:\Users\Alex\AppData\Local\Temp\z>notepad.exe
C:\Users\Alex\AppData\Local\Temp\z>
and notepad.exe refused to start.
But you are correct, notepad.exe refused to run even after I deleted kernel32.dll file. So notepad.exe is special.
I tried running other programs (including one compiled with Go) in C:\Users\Alex\AppData\Local\Temp\z directory, and they seems to ignore corrupted kernel32.dll there. And corrupted ntdll.dll too.
So, you are correct about kernel32.dll and ntdll.dll (unlike winmm.dll) are treated differently by Windows.
Alex
Change https://golang.org/cl/168339 mentions this issue: [release-branch.go1.12] runtime: safely load DLLs
@gopherbot, please backport to Go 1.11.
It seems that with this work, Go is holding itself to a higher bar than other language runtimes (including VC++). The fact that you can inject code into processes by putting DLLs next to the EXE is a known design characteristic of the Windows loader.
Raymond Chen explains this behavior back in 2013. I do not believe Microsoft guidance has changed since then. https://devblogs.microsoft.com/oldnewthing/20130802-00/?p=3633
Change https://golang.org/cl/175378 mentions this issue: [release-branch.go1.11] runtime: safely load DLLs
Most helpful comment
I can confirm that the vulnerability is there and appears to be at least mitigated by the CL:
(Rumor has it, Microsoft will soon fix all security vulnerabilities by removing calc.exe from the default install. Time to look for a new career I guess.)
Boring code: