Skip to content

Commit 9b6e9f0

Browse files
zx2c4alexbrainman
authored andcommitted
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 Reviewed-on: https://go-review.googlesource.com/c/go/+/165798 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Alex Brainman <[email protected]>
1 parent 243c8eb commit 9b6e9f0

File tree

5 files changed

+101
-20
lines changed

5 files changed

+101
-20
lines changed

src/runtime/os_windows.go

+54-10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
//go:cgo_import_dynamic runtime._GetProcessAffinityMask GetProcessAffinityMask%3 "kernel32.dll"
3030
//go:cgo_import_dynamic runtime._GetQueuedCompletionStatus GetQueuedCompletionStatus%5 "kernel32.dll"
3131
//go:cgo_import_dynamic runtime._GetStdHandle GetStdHandle%1 "kernel32.dll"
32+
//go:cgo_import_dynamic runtime._GetSystemDirectoryA GetSystemDirectoryA%2 "kernel32.dll"
3233
//go:cgo_import_dynamic runtime._GetSystemInfo GetSystemInfo%1 "kernel32.dll"
3334
//go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll"
3435
//go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll"
@@ -47,12 +48,9 @@ const (
4748
//go:cgo_import_dynamic runtime._VirtualAlloc VirtualAlloc%4 "kernel32.dll"
4849
//go:cgo_import_dynamic runtime._VirtualFree VirtualFree%3 "kernel32.dll"
4950
//go:cgo_import_dynamic runtime._VirtualQuery VirtualQuery%3 "kernel32.dll"
50-
//go:cgo_import_dynamic runtime._WSAGetOverlappedResult WSAGetOverlappedResult%5 "ws2_32.dll"
5151
//go:cgo_import_dynamic runtime._WaitForSingleObject WaitForSingleObject%2 "kernel32.dll"
5252
//go:cgo_import_dynamic runtime._WriteConsoleW WriteConsoleW%5 "kernel32.dll"
5353
//go:cgo_import_dynamic runtime._WriteFile WriteFile%5 "kernel32.dll"
54-
//go:cgo_import_dynamic runtime._timeBeginPeriod timeBeginPeriod%1 "winmm.dll"
55-
//go:cgo_import_dynamic runtime._timeEndPeriod timeEndPeriod%1 "winmm.dll"
5654

5755
type stdFunction unsafe.Pointer
5856

@@ -75,6 +73,7 @@ var (
7573
_GetProcessAffinityMask,
7674
_GetQueuedCompletionStatus,
7775
_GetStdHandle,
76+
_GetSystemDirectoryA,
7877
_GetSystemInfo,
7978
_GetSystemTimeAsFileTime,
8079
_GetThreadContext,
@@ -96,19 +95,17 @@ var (
9695
_VirtualAlloc,
9796
_VirtualFree,
9897
_VirtualQuery,
99-
_WSAGetOverlappedResult,
10098
_WaitForSingleObject,
10199
_WriteConsoleW,
102100
_WriteFile,
103-
_timeBeginPeriod,
104-
_timeEndPeriod,
105101
_ stdFunction
106102

107103
// Following syscalls are only available on some Windows PCs.
108104
// We will load syscalls, if available, before using them.
109105
_AddDllDirectory,
110106
_AddVectoredContinueHandler,
111107
_GetQueuedCompletionStatusEx,
108+
_LoadLibraryExA,
112109
_LoadLibraryExW,
113110
_ stdFunction
114111

@@ -126,6 +123,12 @@ var (
126123
// links wrong printf function to cgo executable (see issue
127124
// 12030 for details).
128125
_NtWaitForSingleObject stdFunction
126+
127+
// These are from non-kernel32.dll, so we prefer to LoadLibraryEx them.
128+
_timeBeginPeriod,
129+
_timeEndPeriod,
130+
_WSAGetOverlappedResult,
131+
_ stdFunction
129132
)
130133

131134
// Function to be called by windows CreateThread
@@ -173,6 +176,26 @@ func windowsFindfunc(lib uintptr, name []byte) stdFunction {
173176
return stdFunction(unsafe.Pointer(f))
174177
}
175178

179+
var sysDirectory [521]byte
180+
var sysDirectoryLen uintptr
181+
182+
func windowsLoadSystemLib(name []byte) uintptr {
183+
if useLoadLibraryEx {
184+
return stdcall3(_LoadLibraryExA, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32)
185+
} else {
186+
if sysDirectoryLen == 0 {
187+
l := stdcall2(_GetSystemDirectoryA, uintptr(unsafe.Pointer(&sysDirectory[0])), uintptr(len(sysDirectory)-1))
188+
if l == 0 || l > uintptr(len(sysDirectory)-1) {
189+
throw("Unable to determine system directory")
190+
}
191+
sysDirectory[l] = '\\'
192+
sysDirectoryLen = l + 1
193+
}
194+
absName := append(sysDirectory[:sysDirectoryLen], name...)
195+
return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&absName[0])))
196+
}
197+
}
198+
176199
func loadOptionalSyscalls() {
177200
var kernel32dll = []byte("kernel32.dll\000")
178201
k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0])))
@@ -182,17 +205,19 @@ func loadOptionalSyscalls() {
182205
_AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000"))
183206
_AddVectoredContinueHandler = windowsFindfunc(k32, []byte("AddVectoredContinueHandler\000"))
184207
_GetQueuedCompletionStatusEx = windowsFindfunc(k32, []byte("GetQueuedCompletionStatusEx\000"))
208+
_LoadLibraryExA = windowsFindfunc(k32, []byte("LoadLibraryExA\000"))
185209
_LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000"))
210+
useLoadLibraryEx = (_LoadLibraryExW != nil && _LoadLibraryExA != nil && _AddDllDirectory != nil)
186211

187212
var advapi32dll = []byte("advapi32.dll\000")
188-
a32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&advapi32dll[0])))
213+
a32 := windowsLoadSystemLib(advapi32dll)
189214
if a32 == 0 {
190215
throw("advapi32.dll not found")
191216
}
192217
_RtlGenRandom = windowsFindfunc(a32, []byte("SystemFunction036\000"))
193218

194219
var ntdll = []byte("ntdll.dll\000")
195-
n32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&ntdll[0])))
220+
n32 := windowsLoadSystemLib(ntdll)
196221
if n32 == 0 {
197222
throw("ntdll.dll not found")
198223
}
@@ -205,6 +230,27 @@ func loadOptionalSyscalls() {
205230
}
206231
}
207232

233+
var winmmdll = []byte("winmm.dll\000")
234+
m32 := windowsLoadSystemLib(winmmdll)
235+
if m32 == 0 {
236+
throw("winmm.dll not found")
237+
}
238+
_timeBeginPeriod = windowsFindfunc(m32, []byte("timeBeginPeriod\000"))
239+
_timeEndPeriod = windowsFindfunc(m32, []byte("timeEndPeriod\000"))
240+
if _timeBeginPeriod == nil || _timeEndPeriod == nil {
241+
throw("timeBegin/EndPeriod not found")
242+
}
243+
244+
var ws232dll = []byte("ws2_32.dll\000")
245+
ws232 := windowsLoadSystemLib(ws232dll)
246+
if ws232 == 0 {
247+
throw("ws2_32.dll not found")
248+
}
249+
_WSAGetOverlappedResult = windowsFindfunc(ws232, []byte("WSAGetOverlappedResult\000"))
250+
if _WSAGetOverlappedResult == nil {
251+
throw("WSAGetOverlappedResult not found")
252+
}
253+
208254
if windowsFindfunc(n32, []byte("wine_get_version\000")) != nil {
209255
// running on Wine
210256
initWine(k32)
@@ -311,8 +357,6 @@ func osinit() {
311357

312358
loadOptionalSyscalls()
313359

314-
useLoadLibraryEx = (_LoadLibraryExW != nil && _AddDllDirectory != nil)
315-
316360
disableWER()
317361

318362
initExceptionHandler()

src/runtime/syscall_windows.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,13 @@ func compileCallback(fn eface, cleanstack bool) (code uintptr) {
104104

105105
const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
106106

107+
// When available, this function will use LoadLibraryEx with the filename
108+
// parameter and the important SEARCH_SYSTEM32 argument. But on systems that
109+
// do not have that option, absoluteFilepath should contain a fallback
110+
// to the full path inside of system32 for use with vanilla LoadLibrary.
107111
//go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary
108112
//go:nosplit
109-
func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
113+
func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) {
110114
lockOSThread()
111115
defer unlockOSThread()
112116
c := &getg().m.syscall
@@ -121,15 +125,9 @@ func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) {
121125
}{filename, 0, _LOAD_LIBRARY_SEARCH_SYSTEM32}
122126
c.args = uintptr(noescape(unsafe.Pointer(&args)))
123127
} else {
124-
// User doesn't have KB2533623 installed. The caller
125-
// wanted to only load the filename DLL from the
126-
// System32 directory but that facility doesn't exist,
127-
// so just load it the normal way. This is a potential
128-
// security risk, but so is not installing security
129-
// updates.
130128
c.fn = getLoadLibrary()
131129
c.n = 1
132-
c.args = uintptr(noescape(unsafe.Pointer(&filename)))
130+
c.args = uintptr(noescape(unsafe.Pointer(&absoluteFilepath)))
133131
}
134132

135133
cgocall(asmstdcallAddr, unsafe.Pointer(c))

src/syscall/dll_windows.go

+26-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func Syscall12(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 ui
2828
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)
2929
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)
3030
func loadlibrary(filename *uint16) (handle uintptr, err Errno)
31-
func loadsystemlibrary(filename *uint16) (handle uintptr, err Errno)
31+
func loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle uintptr, err Errno)
3232
func getprocaddress(handle uintptr, procname *uint8) (proc uintptr, err Errno)
3333

3434
// A DLL implements access to a single DLL.
@@ -37,6 +37,26 @@ type DLL struct {
3737
Handle Handle
3838
}
3939

40+
// We use this for computing the absolute path for system DLLs on systems
41+
// where SEARCH_SYSTEM32 is not available.
42+
var systemDirectoryPrefix string
43+
44+
func init() {
45+
n := uint32(MAX_PATH)
46+
for {
47+
b := make([]uint16, n)
48+
l, e := getSystemDirectory(&b[0], n)
49+
if e != nil {
50+
panic("Unable to determine system directory: " + e.Error())
51+
}
52+
if l <= n {
53+
systemDirectoryPrefix = UTF16ToString(b[:l]) + "\\"
54+
break
55+
}
56+
n = l
57+
}
58+
}
59+
4060
// LoadDLL loads the named DLL file into memory.
4161
//
4262
// 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) {
5373
var h uintptr
5474
var e Errno
5575
if sysdll.IsSystemDLL[name] {
56-
h, e = loadsystemlibrary(namep)
76+
absoluteFilepathp, err := UTF16PtrFromString(systemDirectoryPrefix + name)
77+
if err != nil {
78+
return nil, err
79+
}
80+
h, e = loadsystemlibrary(namep, absoluteFilepathp)
5781
} else {
5882
h, e = loadlibrary(namep)
5983
}

src/syscall/security_windows.go

+1
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ type Tokenprimarygroup struct {
290290
//sys OpenProcessToken(h Handle, access uint32, token *Token) (err error) = advapi32.OpenProcessToken
291291
//sys GetTokenInformation(t Token, infoClass uint32, info *byte, infoLen uint32, returnedLen *uint32) (err error) = advapi32.GetTokenInformation
292292
//sys GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err error) = userenv.GetUserProfileDirectoryW
293+
//sys getSystemDirectory(dir *uint16, dirLen uint32) (len uint32, err error) = kernel32.GetSystemDirectoryW
293294

294295
// An access token contains the security information for a logon session.
295296
// The system creates an access token when a user logs on, and every

src/syscall/zsyscall_windows.go

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)