Commit fc6457d1 authored by Jason A. Donenfeld's avatar Jason A. Donenfeld Committed by Brad Fitzpatrick

[release-branch.go1.12] 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 #30666
Updates #14959
Updates #28978
Updates #30642

Change-Id: I401a13ed8db248ab1bb5039bf2d31915cac72b93
Reviewed-on: https://go-review.googlesource.com/c/go/+/165798
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarAlex Brainman <alex.brainman@gmail.com>
(cherry picked from commit 9b6e9f0c)
Reviewed-on: https://go-review.googlesource.com/c/go/+/168339Reviewed-by: default avatarDmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: default avatarAndrew Bonventre <andybons@golang.org>
parent ff048033
...@@ -29,6 +29,7 @@ const ( ...@@ -29,6 +29,7 @@ const (
//go:cgo_import_dynamic runtime._GetProcessAffinityMask GetProcessAffinityMask%3 "kernel32.dll" //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._GetQueuedCompletionStatus GetQueuedCompletionStatus%5 "kernel32.dll"
//go:cgo_import_dynamic runtime._GetStdHandle GetStdHandle%1 "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._GetSystemInfo GetSystemInfo%1 "kernel32.dll"
//go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll" //go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll" //go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
...@@ -47,12 +48,9 @@ const ( ...@@ -47,12 +48,9 @@ const (
//go:cgo_import_dynamic runtime._VirtualAlloc VirtualAlloc%4 "kernel32.dll" //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._VirtualFree VirtualFree%3 "kernel32.dll"
//go:cgo_import_dynamic runtime._VirtualQuery VirtualQuery%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._WaitForSingleObject WaitForSingleObject%2 "kernel32.dll"
//go:cgo_import_dynamic runtime._WriteConsoleW WriteConsoleW%5 "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._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 type stdFunction unsafe.Pointer
...@@ -75,6 +73,7 @@ var ( ...@@ -75,6 +73,7 @@ var (
_GetProcessAffinityMask, _GetProcessAffinityMask,
_GetQueuedCompletionStatus, _GetQueuedCompletionStatus,
_GetStdHandle, _GetStdHandle,
_GetSystemDirectoryA,
_GetSystemInfo, _GetSystemInfo,
_GetSystemTimeAsFileTime, _GetSystemTimeAsFileTime,
_GetThreadContext, _GetThreadContext,
...@@ -96,12 +95,9 @@ var ( ...@@ -96,12 +95,9 @@ var (
_VirtualAlloc, _VirtualAlloc,
_VirtualFree, _VirtualFree,
_VirtualQuery, _VirtualQuery,
_WSAGetOverlappedResult,
_WaitForSingleObject, _WaitForSingleObject,
_WriteConsoleW, _WriteConsoleW,
_WriteFile, _WriteFile,
_timeBeginPeriod,
_timeEndPeriod,
_ stdFunction _ stdFunction
// Following syscalls are only available on some Windows PCs. // Following syscalls are only available on some Windows PCs.
...@@ -109,6 +105,7 @@ var ( ...@@ -109,6 +105,7 @@ var (
_AddDllDirectory, _AddDllDirectory,
_AddVectoredContinueHandler, _AddVectoredContinueHandler,
_GetQueuedCompletionStatusEx, _GetQueuedCompletionStatusEx,
_LoadLibraryExA,
_LoadLibraryExW, _LoadLibraryExW,
_ stdFunction _ stdFunction
...@@ -126,6 +123,12 @@ var ( ...@@ -126,6 +123,12 @@ var (
// links wrong printf function to cgo executable (see issue // links wrong printf function to cgo executable (see issue
// 12030 for details). // 12030 for details).
_NtWaitForSingleObject stdFunction _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 // Function to be called by windows CreateThread
...@@ -173,6 +176,26 @@ func windowsFindfunc(lib uintptr, name []byte) stdFunction { ...@@ -173,6 +176,26 @@ func windowsFindfunc(lib uintptr, name []byte) stdFunction {
return stdFunction(unsafe.Pointer(f)) 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() { func loadOptionalSyscalls() {
var kernel32dll = []byte("kernel32.dll\000") var kernel32dll = []byte("kernel32.dll\000")
k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0]))) k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0])))
...@@ -182,17 +205,19 @@ func loadOptionalSyscalls() { ...@@ -182,17 +205,19 @@ func loadOptionalSyscalls() {
_AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000")) _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000"))
_AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000")) _AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
_GetQueuedCompletionStatusEx = windowsFindfunc(k32, []byte("GetQueuedCompletionStatusEx\000")) _GetQueuedCompletionStatusEx = windowsFindfunc(k32, []byte("GetQueuedCompletionStatusEx\000"))
_LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000"))
_LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000")) _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000"))
useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil)
var advapi32dll = []byte("advapi32.dll\000") var advapi32dll = []byte("advapi32.dll\000")
a32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&advapi32dll[0]))) a32 := windowsLoadSystemLib(advapi32dll)
if a32 == 0 { if a32 == 0 {
throw("advapi32.dll not found") throw("advapi32.dll not found")
} }
_RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000")) _RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
var ntdll = []byte("ntdll.dll\000") var ntdll = []byte("ntdll.dll\000")
n32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&ntdll[0]))) n32 := windowsLoadSystemLib(ntdll)
if n32 == 0 { if n32 == 0 {
throw("ntdll.dll not found") throw("ntdll.dll not found")
} }
...@@ -205,6 +230,27 @@ func loadOptionalSyscalls() { ...@@ -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 { if windowsFindfunc(n32, []byte("wine_get_version\000")) != nil {
// running on Wine // running on Wine
initWine(k32) initWine(k32)
...@@ -311,8 +357,6 @@ func osinit() { ...@@ -311,8 +357,6 @@ func osinit() {
loadOptionalSyscalls() loadOptionalSyscalls()
useLoadLibraryEx = (_LoadLibraryExW != nil && _AddDllDirectory != nil)
disableWER() disableWER()
initExceptionHandler() initExceptionHandler()
......
...@@ -104,9 +104,13 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) { ...@@ -104,9 +104,13 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) {
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 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:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
//go:nosplit //go:nosplit
func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
lockOSThread() lockOSThread()
defer unlockOSThread() defer unlockOSThread()
c := &getg().m.syscall c := &getg().m.syscall
...@@ -121,15 +125,9 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { ...@@ -121,15 +125,9 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
}{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32} }{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32}
c.args = uintptr(noescape(unsafe.Pointer(&args))) c.args = uintptr(noescape(unsafe.Pointer(&args)))
} else { } 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.fn = getLoadLibrary()
c.n = 1 c.n = 1
c.args = uintptr(noescape(unsafe.Pointer(&filename))) c.args = uintptr(noescape(unsafe.Pointer(&absoluteFilepath)))
} }
cgocall(asmstdcallAddr, unsafe.Pointer(c)) cgocall(asmstdcallAddr, unsafe.Pointer(c))
......
...@@ -28,7 +28,7 @@ func Syscall12(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 ui ...@@ -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 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 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 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) func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)
// A DLL implements access to a single DLL. // A DLL implements access to a single DLL.
...@@ -37,6 +37,26 @@ type DLL struct { ...@@ -37,6 +37,26 @@ type DLL struct {
Handle Handle 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. // LoadDLL loads the named DLL file into memory.
// //
// If name is not an absolute path and is not a known system DLL used by // 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) { ...@@ -53,7 +73,11 @@ func LoadDLL(name string) (*DLL, error) {
var h uintptr var h uintptr
var e Errno var e Errno
if sysdll.IsSystemDLL[name] { 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 { } else {
h, e = loadlibrary(namep) h, e = loadlibrary(namep)
} }
......
...@@ -290,6 +290,7 @@ type Tokenprimarygroup struct { ...@@ -290,6 +290,7 @@ type Tokenprimarygroup struct {
//sys OpenProcessToken(h Handle, access uint32, token *Token) (err error) = advapi32.OpenProcessToken //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 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 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. // An access token contains the security information for a logon session.
// The system creates an access token when a user logs on, and every // The system creates an access token when a user logs on, and every
......
...@@ -190,6 +190,7 @@ var ( ...@@ -190,6 +190,7 @@ var (
procOpenProcessToken = modadvapi32.NewProc("OpenProcessToken") procOpenProcessToken = modadvapi32.NewProc("OpenProcessToken")
procGetTokenInformation = modadvapi32.NewProc("GetTokenInformation") procGetTokenInformation = modadvapi32.NewProc("GetTokenInformation")
procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW")
procGetSystemDirectoryW = modkernel32.NewProc("GetSystemDirectoryW")
) )
func GetLastError() (lasterr error) { func GetLastError() (lasterr error) {
...@@ -1916,3 +1917,16 @@ func GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) { ...@@ -1916,3 +1917,16 @@ func GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) {
} }
return 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
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment