From b832732b5d8509512c5021b5f4b1cf3e2ec6ea2c Mon Sep 17 00:00:00 2001 From: Vorapol Rinsatitnon Date: Fri, 14 Feb 2025 11:35:56 +0700 Subject: [PATCH] Add back LoadLibraryA fallback --- src/runtime/export_windows_test.go | 4 ++ src/runtime/os_windows.go | 60 ++++++++++++++++++++++++++++- src/runtime/syscall_windows.go | 17 +++++++- src/runtime/syscall_windows_test.go | 23 ++++++++++- src/syscall/dll_windows.go | 28 +++++++++++++- src/syscall/security_windows.go | 1 + src/syscall/zsyscall_windows.go | 10 +++++ 7 files changed, 136 insertions(+), 7 deletions(-) diff --git a/src/runtime/export_windows_test.go b/src/runtime/export_windows_test.go index 13d30d4..5ff5229 100644 --- a/src/runtime/export_windows_test.go +++ b/src/runtime/export_windows_test.go @@ -39,3 +39,7 @@ func NewContextStub() *ContextStub { ctx.set_fp(getcallerfp()) return &ContextStub{ctx} } + +func LoadLibraryExStatus() (useEx, haveEx, haveFlags bool) { + return useLoadLibraryEx, _LoadLibraryExW != nil, _AddDllDirectory != nil +} diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index fd65e34..c85fab7 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -42,6 +42,7 @@ const ( //go:cgo_import_dynamic runtime._SetThreadContext SetThreadContext%2 "kernel32.dll" //go:cgo_import_dynamic runtime._LoadLibraryExW LoadLibraryExW%3 "kernel32.dll" //go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll" +//go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll" //go:cgo_import_dynamic runtime._PostQueuedCompletionStatus PostQueuedCompletionStatus%4 "kernel32.dll" //go:cgo_import_dynamic runtime._QueryPerformanceCounter QueryPerformanceCounter%1 "kernel32.dll" //go:cgo_import_dynamic runtime._QueryPerformanceFrequency QueryPerformanceFrequency%1 "kernel32.dll" @@ -75,6 +76,7 @@ var ( // Following syscalls are available on every Windows PC. // All these variables are set by the Windows executable // loader before the Go program starts. + _AddDllDirectory, _AddVectoredContinueHandler, _AddVectoredExceptionHandler, _CloseHandle, @@ -100,6 +102,7 @@ var ( _SetThreadContext, _LoadLibraryExW, _LoadLibraryW, + _LoadLibraryA, _PostQueuedCompletionStatus, _QueryPerformanceCounter, _QueryPerformanceFrequency, @@ -158,7 +161,6 @@ var ( ntdlldll = [...]uint16{'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0} powrprofdll = [...]uint16{'p', 'o', 'w', 'r', 'p', 'r', 'o', 'f', '.', 'd', 'l', 'l', 0} winmmdll = [...]uint16{'w', 'i', 'n', 'm', 'm', '.', 'd', 'l', 'l', 0} - ws2_32dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0} ) // Function to be called by windows CreateThread @@ -254,7 +256,36 @@ func windows_GetSystemDirectory() string { } func windowsLoadSystemLib(name []uint16) uintptr { - return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32) + if useLoadLibraryEx { + return stdcall3(_LoadLibraryExW, uintptr(unsafe.Pointer(&name[0])), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32) + } else { + var nameBytes [_MAX_PATH]byte + n := len(name) + if n > len(nameBytes) { + n = len(nameBytes) + } + for i := 0; i < n && name[i] != 0; i++ { + nameBytes[i] = byte(name[i]) + } + + // Construct the full path + var fullPath [_MAX_PATH]byte + copy(fullPath[:], sysDirectory[:sysDirectoryLen]) + pathLen := sysDirectoryLen + for i := 0; i < len(nameBytes) && nameBytes[i] != 0 && pathLen < _MAX_PATH; i++ { + fullPath[pathLen] = nameBytes[i] + pathLen++ + } + + // Ensure null-termination + if pathLen < _MAX_PATH { + fullPath[pathLen] = 0 + } else { + fullPath[_MAX_PATH-1] = 0 + } + + return stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&fullPath[0]))) + } } //go:linkname windows_QueryPerformanceCounter internal/syscall/windows.QueryPerformanceCounter @@ -272,6 +303,15 @@ func windows_QueryPerformanceFrequency() int64 { } func loadOptionalSyscalls() { + var kernel32dll = []byte("kernel32.dll\000") + k32 := stdcall1(_LoadLibraryA, uintptr(unsafe.Pointer(&kernel32dll[0]))) + if k32 == 0 { + throw("kernel32.dll not found") + } + _AddDllDirectory = windowsFindfunc(k32, []byte("AddDllDirectory\000")) + _LoadLibraryExW = windowsFindfunc(k32, []byte("LoadLibraryExW\000")) + useLoadLibraryEx = (_LoadLibraryExW != nil && _AddDllDirectory != nil) + a32 := windowsLoadSystemLib(advapi32dll[:]) if a32 == 0 { throw("advapi32.dll not found") @@ -366,6 +406,22 @@ const ( // in sys_windows_386.s and sys_windows_amd64.s: func getlasterror() uint32 +// When loading DLLs, we prefer to use LoadLibraryEx with +// LOAD_LIBRARY_SEARCH_* flags, if available. LoadLibraryEx is not +// available on old Windows, though, and the LOAD_LIBRARY_SEARCH_* +// flags are not available on some versions of Windows without a +// security patch. +// +// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says: +// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows +// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on +// systems that have KB2533623 installed. To determine whether the +// flags are available, use GetProcAddress to get the address of the +// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories +// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_* +// flags can be used with LoadLibraryEx." +var useLoadLibraryEx bool + var timeBeginPeriodRetValue uint32 // osRelaxMinNS indicates that sysmon shouldn't osRelax if the next diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go index 85b1b8c..eb808fe 100644 --- a/src/runtime/syscall_windows.go +++ b/src/runtime/syscall_windows.go @@ -413,10 +413,23 @@ func callbackWrap(a *callbackArgs) { 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 -func syscall_loadsystemlibrary(filename *uint16) (handle, err uintptr) { - handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32) +func syscall_loadsystemlibrary(filename *uint16, absoluteFilepath *uint16) (handle, err uintptr) { + if useLoadLibraryEx { + handle, _, err = syscall_SyscallN(uintptr(unsafe.Pointer(_LoadLibraryExW)), uintptr(unsafe.Pointer(filename)), 0, _LOAD_LIBRARY_SEARCH_SYSTEM32) + } else { + handle, _, err = syscall_SyscallN( + uintptr(unsafe.Pointer(_LoadLibraryW)), + uintptr(unsafe.Pointer(absoluteFilepath)), + ) + } KeepAlive(filename) + KeepAlive(absoluteFilepath) if handle != 0 { err = 0 } diff --git a/src/runtime/syscall_windows_test.go b/src/runtime/syscall_windows_test.go index 01a9ca3..53f7110 100644 --- a/src/runtime/syscall_windows_test.go +++ b/src/runtime/syscall_windows_test.go @@ -1160,7 +1160,10 @@ uintptr_t cfunc(void) { dll, err = syscall.LoadDLL(name) if err == nil { dll.Release() - t.Fatalf("Bad: insecure load of DLL by base name %q before sysdll registration: %v", name, err) + if wantLoadLibraryEx() { + t.Fatalf("Bad: insecure load of DLL by base name %q before sysdll registration: %v", name, err) + } + t.Skip("insecure load of DLL, but expected") } } @@ -1213,6 +1216,24 @@ func TestSyscallStackUsage(t *testing.T) { syscall.Syscall18(procSetEvent.Addr(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) } +// wantLoadLibraryEx reports whether we expect LoadLibraryEx to work for tests. +func wantLoadLibraryEx() bool { + return testenv.Builder() != "" && (runtime.GOARCH == "amd64" || runtime.GOARCH == "386") +} + +func TestLoadLibraryEx(t *testing.T) { + use, have, flags := runtime.LoadLibraryExStatus() + if use { + return // success. + } + if wantLoadLibraryEx() { + t.Fatalf("Expected LoadLibraryEx+flags to be available. (LoadLibraryEx=%v; flags=%v)", + have, flags) + } + t.Skipf("LoadLibraryEx not usable, but not expected. (LoadLibraryEx=%v; flags=%v)", + have, flags) +} + var ( modwinmm = syscall.NewLazyDLL("winmm.dll") modkernel32 = syscall.NewLazyDLL("kernel32.dll") diff --git a/src/syscall/dll_windows.go b/src/syscall/dll_windows.go index a7873e6..bd82b51 100644 --- a/src/syscall/dll_windows.go +++ b/src/syscall/dll_windows.go @@ -45,7 +45,7 @@ func Syscall18(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a //go:noescape func SyscallN(trap uintptr, args ...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. @@ -54,6 +54,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 @@ -70,7 +90,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 4e988c4..45b1908 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 c0585a6..85c66de 100644 --- a/src/syscall/zsyscall_windows.go +++ b/src/syscall/zsyscall_windows.go @@ -128,6 +128,7 @@ var ( procGetShortPathNameW = modkernel32.NewProc("GetShortPathNameW") procGetStartupInfoW = modkernel32.NewProc("GetStartupInfoW") procGetStdHandle = modkernel32.NewProc("GetStdHandle") + procGetSystemDirectoryW = modkernel32.NewProc("GetSystemDirectoryW") procGetSystemTimeAsFileTime = modkernel32.NewProc("GetSystemTimeAsFileTime") procGetTempPathW = modkernel32.NewProc("GetTempPathW") procGetTimeZoneInformation = modkernel32.NewProc("GetTimeZoneInformation") @@ -871,6 +872,15 @@ func GetStdHandle(stdhandle int) (handle Handle, 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 { + err = errnoErr(e1) + } + return +} + func GetSystemTimeAsFileTime(time *Filetime) { Syscall(procGetSystemTimeAsFileTime.Addr(), 1, uintptr(unsafe.Pointer(time)), 0, 0) return -- 2.39.5