From 88e6565360c5f9ad5e219f53472ad8f2a3f7518e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 8 Mar 2019 04:23:39 +0100 Subject: build: patch golang against dll injection Signed-off-by: Jason A. Donenfeld --- Makefile | 13 +- build.bat | 45 +++--- golang-runtime-dll-injection.patch | 316 +++++++++++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+), 20 deletions(-) create mode 100644 golang-runtime-dll-injection.patch diff --git a/Makefile b/Makefile index 720c0099..8a93a700 100644 --- a/Makefile +++ b/Makefile @@ -4,17 +4,26 @@ WINDRES := x86_64-w64-mingw32-windres export CGO_ENABLED := 1 export GOOS := windows export GOARCH := amd64 +REAL_GOROOT := $(shell go env GOROOT) +export GOROOT := $(PWD)/deps/go +export PATH := $(GOROOT)/bin:$(PATH) DEPLOYMENT_HOST ?= winvm DEPLOYMENT_PATH ?= Desktop all: wireguard.exe +deps/.prepared: + mkdir -p deps + rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/" + patch -f -N -r- -d deps/go -p1 < golang-runtime-dll-injection.patch + touch "$@" + resources.syso: resources.rc manifest.xml ui/icon/icon.ico $(WINDRES) -i $< -o $@ -O coff rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) -wireguard.exe: resources.syso $(call rwildcard,,*.go *.c *.h) +wireguard.exe: resources.syso $(call rwildcard,,*.go *.c *.h) deps/.prepared go build -ldflags="-H windowsgui -s -w" -v -o $@ deploy: wireguard.exe @@ -22,6 +31,6 @@ deploy: wireguard.exe scp wireguard.exe $(DEPLOYMENT_HOST):$(DEPLOYMENT_PATH) clean: - rm -rf resources.syso wireguard.exe + rm -rf resources.syso wireguard.exe deps .PHONY: deploy clean all diff --git a/build.bat b/build.bat index 0c8f1831..082310d2 100644 --- a/build.bat +++ b/build.bat @@ -1,21 +1,8 @@ @echo off set STARTDIR=%cd% set OLDPATH=%PATH% -if not exist deps\.prepared call :installdeps -set PATH=%STARTDIR%\deps\x86_64-w64-mingw32-native\bin\;%STARTDIR%\deps\go\bin\;%PATH% -set CC=x86_64-w64-mingw32-gcc.exe -set CFLAGS=-O3 -Wall -std=gnu11 -set GOOS=windows -set GOARCH=amd64 -set GOPATH=%STARTDIR%\deps\gopath -set GOROOT=%STARTDIR%\deps\go -set CGO_ENABLED=1 -echo Assembling resources -windres.exe -i resources.rc -o resources.syso -O coff || goto :error -echo Building program -go build -ldflags="-H windowsgui -s -w" -v -o wireguard.exe || goto :error -goto :out +if exist deps\.prepared goto :build :installdeps rmdir /s /q deps 2> NUL mkdir deps || goto :error @@ -24,19 +11,41 @@ goto :out curl -#fo go.zip https://dl.google.com/go/go1.12.windows-amd64.zip || goto :error echo Downloading mingw curl -#fo mingw.zip https://musl.cc/x86_64-w64-mingw32-native.zip || goto :error + echo Downloading patch + curl -L#fo patch.zip https://sourceforge.net/projects/gnuwin32/files/patch/2.5.9-7/patch-2.5.9-7-bin.zip || goto :error echo Extracting golang tar -xf go.zip || goto :error echo Extracting mingw tar -xf mingw.zip || goto :error + echo Extracting patch + tar -xf patch.zip --strip-components 1 bin || goto :error + echo Patching golang + .\patch.exe -f -N -r- -d go -p1 --binary < ..\golang-runtime-dll-injection.patch || goto :error echo Cleaning up - del go.zip mingw.zip || goto :error + del patch.exe patch.zip go.zip mingw.zip || goto :error copy /y NUL .prepared > NUL || goto :error cd .. || goto :error - exit /b -:error - echo Failed with error #%errorlevel%. +:build + set PATH=%STARTDIR%\deps\x86_64-w64-mingw32-native\bin\;%STARTDIR%\deps\go\bin\;%PATH% + set CC=x86_64-w64-mingw32-gcc.exe + set CFLAGS=-O3 -Wall -std=gnu11 + set GOOS=windows + set GOARCH=amd64 + set GOPATH=%STARTDIR%\deps\gopath + set GOROOT=%STARTDIR%\deps\go + set CGO_ENABLED=1 + echo Assembling resources + windres.exe -i resources.rc -o resources.syso -O coff || goto :error + echo Building program + go build -ldflags="-H windowsgui -s -w" -v -o wireguard.exe || goto :error + echo Success. Launch wireguard.exe. + :out set PATH=%OLDPATH% cd %STARTDIR% exit /b %errorlevel% + +:error + echo Failed with error #%errorlevel%. + goto :out diff --git a/golang-runtime-dll-injection.patch b/golang-runtime-dll-injection.patch new file mode 100644 index 00000000..af0fc87b --- /dev/null +++ b/golang-runtime-dll-injection.patch @@ -0,0 +1,316 @@ +From a5dacf9b3b54bc24733776a42e7907964f23cd3b Mon Sep 17 00:00:00 2001 +From: "Jason A. Donenfeld" +Date: Wed, 6 Mar 2019 19:26:29 +0100 +Subject: [PATCH] runtime: safely load DLLs + +While many other call sites have been moved to using the proper +higher-level system loading, these areas were left out. This prevents +DLL directory injection attacks. This includes both the runtime load +calls (using LoadLibrary prior) and the implicitly linked ones via +cgo_import_dynamic, which we move to our LoadLibraryEx. The goal is to +only loosely load kernel32.dll and strictly load all others. + +Meanwhile we make sure that we never fallback to insecure loading on +older or unpatched systems. + +This is CVE-2019-9634. + +Fixes #14959 +Fixes #28978 +Fixes #30642 + +Change-Id: I401a13ed8db248ab1bb5039bf2d31915cac72b93 +--- + src/runtime/os_windows.go | 64 +++++++++++++++++++++++++++------ + src/runtime/syscall_windows.go | 14 ++++---- + src/syscall/dll_windows.go | 28 +++++++++++++-- + src/syscall/security_windows.go | 1 + + src/syscall/zsyscall_windows.go | 14 ++++++++ + 5 files changed, 101 insertions(+), 20 deletions(-) + +diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go +index 2e1ec58a0d..d3e84fe3dc 100644 +--- a/src/runtime/os_windows.go ++++ b/src/runtime/os_windows.go +@@ -29,6 +29,7 @@ const ( + //go:cgo_import_dynamic runtime._GetProcessAffinityMask GetProcessAffinityMask%3 "kernel32.dll" + //go:cgo_import_dynamic runtime._GetQueuedCompletionStatus GetQueuedCompletionStatus%5 "kernel32.dll" + //go:cgo_import_dynamic runtime._GetStdHandle GetStdHandle%1 "kernel32.dll" ++//go:cgo_import_dynamic runtime._GetSystemDirectoryA GetSystemDirectoryA%2 "kernel32.dll" + //go:cgo_import_dynamic runtime._GetSystemInfo GetSystemInfo%1 "kernel32.dll" + //go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll" + //go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll" +@@ -47,12 +48,9 @@ const ( + //go:cgo_import_dynamic runtime._VirtualAlloc VirtualAlloc%4 "kernel32.dll" + //go:cgo_import_dynamic runtime._VirtualFree VirtualFree%3 "kernel32.dll" + //go:cgo_import_dynamic runtime._VirtualQuery VirtualQuery%3 "kernel32.dll" +-//go:cgo_import_dynamic runtime._WSAGetOverlappedResult WSAGetOverlappedResult%5 "ws2_32.dll" + //go:cgo_import_dynamic runtime._WaitForSingleObject WaitForSingleObject%2 "kernel32.dll" + //go:cgo_import_dynamic runtime._WriteConsoleW WriteConsoleW%5 "kernel32.dll" + //go:cgo_import_dynamic runtime._WriteFile WriteFile%5 "kernel32.dll" +-//go:cgo_import_dynamic runtime._timeBeginPeriod timeBeginPeriod%1 "winmm.dll" +-//go:cgo_import_dynamic runtime._timeEndPeriod timeEndPeriod%1 "winmm.dll" + + type stdFunction unsafe.Pointer + +@@ -75,6 +73,7 @@ var ( + _GetProcessAffinityMask, + _GetQueuedCompletionStatus, + _GetStdHandle, ++ _GetSystemDirectoryA, + _GetSystemInfo, + _GetSystemTimeAsFileTime, + _GetThreadContext, +@@ -96,12 +95,9 @@ var ( + _VirtualAlloc, + _VirtualFree, + _VirtualQuery, +- _WSAGetOverlappedResult, + _WaitForSingleObject, + _WriteConsoleW, + _WriteFile, +- _timeBeginPeriod, +- _timeEndPeriod, + _ stdFunction + + // Following syscalls are only available on some Windows PCs. +@@ -109,6 +105,7 @@ var ( + _AddDllDirectory, + _AddVectoredContinueHandler, + _GetQueuedCompletionStatusEx, ++ _LoadLibraryExA, + _LoadLibraryExW, + _ stdFunction + +@@ -126,6 +123,12 @@ var ( + // links wrong printf function to cgo executable (see issue + // 12030 for details). + _NtWaitForSingleObject stdFunction ++ ++ // These are from non-kernel32.dll, so we prefer to LoadLibraryEx them. ++ _timeBeginPeriod, ++ _timeEndPeriod, ++ _WSAGetOverlappedResult, ++ _ stdFunction + ) + + // Function to be called by windows CreateThread +@@ -173,6 +176,26 @@ func windowsFindfunc(lib uintptr, name []byte) stdFunction { + return stdFunction(unsafe.Pointer(f)) + } + ++var sysDirectory [521]byte ++var sysDirectoryLen uintptr ++ ++func windowsLoadSystemLib(name []byte) uintptr { ++ if useLoadLibraryEx { ++ return stdcall3(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32) ++ } else { ++ if sysDirectoryLen == 0 { ++ l := stdcall2(_GetSystemDirectoryA, uintptr(unsafe.Pointer(&sysDirectory[0])), uintptr(len(sysDirectory)-1)) ++ if l == 0 || l > uintptr(len(sysDirectory)-1) { ++ throw("Unable to determine system directory") ++ } ++ sysDirectory[l] = '\\' ++ sysDirectoryLen = l + 1 ++ } ++ absName := append(sysDirectory[:sysDirectoryLen], name...) ++ return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0]))) ++ } ++} ++ + func loadOptionalSyscalls() { + var kernel32dll = []byte("kernel32.dll\000") + k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0]))) +@@ -182,17 +205,19 @@ func loadOptionalSyscalls() { + _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000")) + _AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000")) + _GetQueuedCompletionStatusEx = windowsFindfunc(k32, []byte("GetQueuedCompletionStatusEx\000")) ++ _LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000")) + _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000")) ++ useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil) + + var advapi32dll = []byte("advapi32.dll\000") +- a32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&advapi32dll[0]))) ++ a32 := windowsLoadSystemLib(advapi32dll) + if a32 == 0 { + throw("advapi32.dll not found") + } + _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000")) + + var ntdll = []byte("ntdll.dll\000") +- n32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&ntdll[0]))) ++ n32 := windowsLoadSystemLib(ntdll) + if n32 == 0 { + throw("ntdll.dll not found") + } +@@ -205,6 +230,27 @@ func loadOptionalSyscalls() { + } + } + ++ var winmmdll = []byte("winmm.dll\000") ++ m32 := windowsLoadSystemLib(winmmdll) ++ if m32 == 0 { ++ throw("winmm.dll not found") ++ } ++ _timeBeginPeriod = windowsFindfunc(m32, []byte("timeBeginPeriod\000")) ++ _timeEndPeriod = windowsFindfunc(m32, []byte("timeEndPeriod\000")) ++ if _timeBeginPeriod == nil || _timeEndPeriod == nil { ++ throw("timeBegin/EndPeriod not found") ++ } ++ ++ var ws232dll = []byte("ws2_32.dll\000") ++ ws232 := windowsLoadSystemLib(ws232dll) ++ if ws232 == 0 { ++ throw("ws2_32.dll not found") ++ } ++ _WSAGetOverlappedResult = windowsFindfunc(ws232, []byte("WSAGetOverlappedResult\000")) ++ if _WSAGetOverlappedResult == nil { ++ throw("WSAGetOverlappedResult not found") ++ } ++ + if windowsFindfunc(n32, []byte("wine_get_version\000")) != nil { + // running on Wine + initWine(k32) +@@ -311,8 +357,6 @@ func osinit() { + + loadOptionalSyscalls() + +- useLoadLibraryEx = (_LoadLibraryExW != nil && _AddDllDirectory != nil) +- + disableWER() + + initExceptionHandler() +diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go +index 8cfc71124a..36ad7511af 100644 +--- a/src/runtime/syscall_windows.go ++++ b/src/runtime/syscall_windows.go +@@ -104,9 +104,13 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) { + + const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 + ++// When available, this function will use LoadLibraryEx with the filename ++// parameter and the important SEARCH_SYSTEM32 argument. But on systems that ++// do not have that option, absoluteFilepath should contain a fallback ++// to the full path inside of system32 for use with vanilla LoadLibrary. + //go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary + //go:nosplit +-func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { ++func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) { + lockOSThread() + defer unlockOSThread() + c := &getg().m.syscall +@@ -121,15 +125,9 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { + }{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32} + c.args = uintptr(noescape(unsafe.Pointer(&args))) + } else { +- // User doesn't have KB2533623 installed. The caller +- // wanted to only load the filename DLL from the +- // System32 directory but that facility doesn't exist, +- // so just load it the normal way. This is a potential +- // security risk, but so is not installing security +- // updates. + c.fn = getLoadLibrary() + c.n = 1 +- c.args = uintptr(noescape(unsafe.Pointer(&filename))) ++ c.args = uintptr(noescape(unsafe.Pointer(&absoluteFilepath))) + } + + cgocall(asmstdcallAddr, unsafe.Pointer(c)) +diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go +index c57cd34f82..34925f74a4 100644 +--- a/src/syscall/dll_windows.go ++++ b/src/syscall/dll_windows.go +@@ -28,7 +28,7 @@ func Syscall12(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 ui + func Syscall15(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2 uintptr, err Errno) + func Syscall18(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18 uintptr) (r1, r2 uintptr, err Errno) + func loadlibrary(filename *uint16) (handle uintptr, err Errno) +-func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno) ++func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno) + func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno) + + // A DLL implements access to a single DLL. +@@ -37,6 +37,26 @@ type DLL struct { + Handle Handle + } + ++// We use this for computing the absolute path for system DLLs on systems ++// where SEARCH_SYSTEM32 is not available. ++var systemDirectoryPrefix string ++ ++func init() { ++ n := uint32(MAX_PATH) ++ for { ++ b := make([]uint16, n) ++ l, e := getSystemDirectory(&b[0], n) ++ if e != nil { ++ panic("Unable to determine system directory: " + e.Error()) ++ } ++ if l <= n { ++ systemDirectoryPrefix = UTF16ToString(b[:l]) + "\\" ++ break ++ } ++ n = l ++ } ++} ++ + // LoadDLL loads the named DLL file into memory. + // + // If name is not an absolute path and is not a known system DLL used by +@@ -53,7 +73,11 @@ func LoadDLL(name string) (*DLL, error) { + var h uintptr + var e Errno + if sysdll.IsSystemDLL[name] { +- h, e = loadsystemlibrary(namep) ++ absoluteFilepathp, err := UTF16PtrFromString(systemDirectoryPrefix + name) ++ if err != nil { ++ return nil, err ++ } ++ h, e = loadsystemlibrary(namep, absoluteFilepathp) + } else { + h, e = loadlibrary(namep) + } +diff --git a/src/syscall/security_windows.go b/src/syscall/security_windows.go +index ae8b3a17bf..db80d98a08 100644 +--- a/src/syscall/security_windows.go ++++ b/src/syscall/security_windows.go +@@ -290,6 +290,7 @@ type Tokenprimarygroup struct { + //sys OpenProcessToken(h Handle, access uint32, token *Token) (err error) = advapi32.OpenProcessToken + //sys GetTokenInformation(t Token, infoClass uint32, info *byte, infoLen uint32, returnedLen *uint32) (err error) = advapi32.GetTokenInformation + //sys GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) = userenv.GetUserProfileDirectoryW ++//sys getSystemDirectory(dir *uint16, dirLen uint32) (len uint32, err error) = kernel32.GetSystemDirectoryW + + // An access token contains the security information for a logon session. + // The system creates an access token when a user logs on, and every +diff --git a/src/syscall/zsyscall_windows.go b/src/syscall/zsyscall_windows.go +index de2d4f3adb..2348f6534f 100644 +--- a/src/syscall/zsyscall_windows.go ++++ b/src/syscall/zsyscall_windows.go +@@ -190,6 +190,7 @@ var ( + procOpenProcessToken = modadvapi32.NewProc("OpenProcessToken") + procGetTokenInformation = modadvapi32.NewProc("GetTokenInformation") + procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") ++ procGetSystemDirectoryW = modkernel32.NewProc("GetSystemDirectoryW") + ) + + func GetLastError() (lasterr error) { +@@ -1916,3 +1917,16 @@ func GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) { + } + return + } ++ ++func getSystemDirectory(dir *uint16, dirLen uint32) (len uint32, err error) { ++ r0, _, e1 := Syscall(procGetSystemDirectoryW.Addr(), 2, uintptr(unsafe.Pointer(dir)), uintptr(dirLen), 0) ++ len = uint32(r0) ++ if len == 0 { ++ if e1 != 0 { ++ err = errnoErr(e1) ++ } else { ++ err = EINVAL ++ } ++ } ++ return ++} +-- +2.20.1 + -- cgit v1.2.3-59-g8ed1b