blob: 19ea277b3a7a8f2f8aab897cf0d2d4b4bd332b04 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
package main
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"testing"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
//go:generate go run . -systemdll=false -output zhook_windows_test.go hook_windows_test.go
//sys regOpenKeyExWithGarbageUpperBits(key syscall.Handle, subkey *uint16, options uint32, desiredAccess uint32, result *syscall.Handle) (regerrno error) = hook.RegOpenKeyExWWithGarbageUpperBits
//sys isValidCodePageWithGarbageUpperBits(codePage uint32) (ok bool) = hook.IsValidCodePageWithGarbageUpperBits
//sys getHandleInformationWithGarbageUpperBits(handle syscall.Handle, flags *uint32) (err error) = hook.GetHandleInformationWithGarbageUpperBits
// hookC is built into hook.dll. Its functions call real 32-bit APIs and, like
// a non-conforming hook, leave garbage in the upper half of the return register.
const hookC = `#include <stdint.h>
#include <windows.h>
#define GARBAGE 0xDEADBEEF00000000ULL
__declspec(dllexport) uintptr_t RegOpenKeyExWWithGarbageUpperBits(HKEY key, LPCWSTR subkey, DWORD options, REGSAM desiredAccess, PHKEY result) {
return GARBAGE | (uint32_t)RegOpenKeyExW(key, subkey, options, desiredAccess, result);
}
__declspec(dllexport) uintptr_t IsValidCodePageWithGarbageUpperBits(UINT codePage) {
return GARBAGE | (uint32_t)IsValidCodePage(codePage);
}
__declspec(dllexport) uintptr_t GetHandleInformationWithGarbageUpperBits(HANDLE handle, LPDWORD flags) {
return GARBAGE | (uint32_t)GetHandleInformation(handle, flags);
}
`
func TestGeneratedCodeIgnoresUpperReturnBits(t *testing.T) {
if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
t.Skip("the return register has no upper 32 bits on this architecture")
}
if _, err := exec.LookPath("gcc"); err != nil {
t.Skip("skipping test: gcc is missing")
}
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "hook.c"), []byte(hookC), 0666); err != nil {
t.Fatal(err)
}
cmd := exec.Command("gcc", "-shared", "-s", "-Werror", "-o", "hook.dll", "hook.c", "-ladvapi32")
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("failed to build dll: %v - %v", err, string(out))
}
if err := windows.SetDllDirectory(dir); err != nil {
t.Fatal(err)
}
// FreeLibrary below invalidates the cached lazy DLL and procs, so recreate them.
modhook = syscall.NewLazyDLL("hook.dll")
procRegOpenKeyExWWithGarbageUpperBits = modhook.NewProc("RegOpenKeyExWWithGarbageUpperBits")
procIsValidCodePageWithGarbageUpperBits = modhook.NewProc("IsValidCodePageWithGarbageUpperBits")
procGetHandleInformationWithGarbageUpperBits = modhook.NewProc("GetHandleInformationWithGarbageUpperBits")
if err := modhook.Load(); err != nil {
t.Fatal(err)
}
// Windows cannot delete a loaded DLL, so free it before t.TempDir's cleanup.
defer func() {
if err := syscall.FreeLibrary(syscall.Handle(modhook.Handle())); err != nil {
t.Error(err)
}
}()
for _, tt := range []struct {
subkey string
want error
}{
{`SOFTWARE\Microsoft\Windows NT\CurrentVersion`, nil},
{`SOFTWARE\golang.org-x-sys-mkwinsyscall-missing`, syscall.ERROR_FILE_NOT_FOUND},
} {
subkey, err := windows.UTF16PtrFromString(tt.subkey)
if err != nil {
t.Fatal(err)
}
var h syscall.Handle
err = regOpenKeyExWithGarbageUpperBits(syscall.HKEY_LOCAL_MACHINE, subkey, 0, syscall.KEY_QUERY_VALUE, &h)
if err == nil {
k := registry.Key(h)
if _, err := k.Stat(); err != nil {
t.Error(err)
}
if err := k.Close(); err != nil {
t.Error(err)
}
}
if err != tt.want {
t.Errorf("regOpenKeyExWithGarbageUpperBits(%q): got %#v, want %#v", tt.subkey, err, tt.want)
}
}
for _, tt := range []struct {
codePage uint32
want bool
}{
{65001, true}, // UTF-8
{12345, false},
} {
if got := isValidCodePageWithGarbageUpperBits(tt.codePage); got != tt.want {
t.Errorf("isValidCodePageWithGarbageUpperBits(%d): got %v, want %v", tt.codePage, got, tt.want)
}
}
f, err := os.Open(filepath.Join(dir, "hook.c"))
if err != nil {
t.Fatal(err)
}
defer f.Close()
for _, tt := range []struct {
handle syscall.Handle
want error
}{
{syscall.Handle(f.Fd()), nil},
{0, windows.ERROR_INVALID_HANDLE},
} {
var flags uint32
if err := getHandleInformationWithGarbageUpperBits(tt.handle, &flags); err != tt.want {
t.Errorf("getHandleInformationWithGarbageUpperBits(%#x): got %#v, want %#v", tt.handle, err, tt.want)
}
}
}