From 81077e59d3872eebee4f773f8d5be991d6c126c8 Mon Sep 17 00:00:00 2001 From: mptyl Date: Tue, 18 Aug 2026 13:11:40 +0200 Subject: [PATCH] fix(safeio): normalize NT relative access masks --- .../internal/safeio/private_root_windows.go | 25 ++++++++++++++++--- .../internal/safeio/private_windows_test.go | 10 ++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/tools/tht/internal/safeio/private_root_windows.go b/tools/tht/internal/safeio/private_root_windows.go index 1d6d9af2..0a6f11e6 100644 --- a/tools/tht/internal/safeio/private_root_windows.go +++ b/tools/tht/internal/safeio/private_root_windows.go @@ -178,6 +178,7 @@ func openWindowsRelativeObjectWithShareMode( if parent == 0 || !validPrivateLeafName(name) { return 0, ErrUnsafeFile } + access = normalizeWindowsNTDesiredAccess(access) objectName, err := windows.NewNTUnicodeString(name) if err != nil { return 0, ErrUnsafeFile @@ -203,10 +204,8 @@ func openWindowsRelativeObjectWithShareMode( attributes, &status, &allocationSize, - // NtCreateFile expects zero here for the retained open/create contract. The - // Win32 FILE_ATTRIBUTE_NORMAL flag is not a valid NT FileAttributes value - // for this RootDirectory-relative call and produces STATUS_INVALID_PARAMETER - // on the Windows runner. + // NtCreateFile supplies the normal attribute default when this is zero; no + // explicit creation attribute is needed for these retained open/create calls. 0, shareMode, disposition, @@ -222,6 +221,24 @@ func openWindowsRelativeObjectWithShareMode( return handle, nil } +func normalizeWindowsNTDesiredAccess(access uint32) uint32 { + if access&uint32(windows.GENERIC_ALL) != 0 { + const fileSpecificAll = uint32(0x1ff) + access = access&^uint32(windows.GENERIC_ALL) | + uint32(windows.STANDARD_RIGHTS_REQUIRED|windows.SYNCHRONIZE) | fileSpecificAll + } + if access&uint32(windows.GENERIC_READ) != 0 { + access = access&^uint32(windows.GENERIC_READ) | uint32(windows.FILE_GENERIC_READ) + } + if access&uint32(windows.GENERIC_WRITE) != 0 { + access = access&^uint32(windows.GENERIC_WRITE) | uint32(windows.FILE_GENERIC_WRITE) + } + if access&uint32(windows.GENERIC_EXECUTE) != 0 { + access = access&^uint32(windows.GENERIC_EXECUTE) | uint32(windows.FILE_GENERIC_EXECUTE) + } + return access +} + func privateWindowsDirectoryInfo(handle windows.Handle) (windows.ByHandleFileInformation, error) { var info windows.ByHandleFileInformation if handle == 0 || windows.GetFileInformationByHandle(handle, &info) != nil || diff --git a/tools/tht/internal/safeio/private_windows_test.go b/tools/tht/internal/safeio/private_windows_test.go index 3cbf63c5..e18dbc1b 100644 --- a/tools/tht/internal/safeio/private_windows_test.go +++ b/tools/tht/internal/safeio/private_windows_test.go @@ -70,6 +70,16 @@ func TestOwnerOnlyDACLAcceptsWindowsFullControlMask(t *testing.T) { } } +func TestWindowsNTDesiredAccessMapsGenericBits(t *testing.T) { + if got, want := normalizeWindowsNTDesiredAccess(windows.GENERIC_READ), uint32(windows.FILE_GENERIC_READ); got != want { + t.Fatalf("normalized generic read access = %#x, want %#x", got, want) + } + want := uint32(windows.FILE_GENERIC_READ | windows.WRITE_DAC | windows.WRITE_OWNER) + if got := normalizeWindowsNTDesiredAccess(windows.GENERIC_READ | windows.WRITE_DAC | windows.WRITE_OWNER); got != want { + t.Fatalf("normalized protected read access = %#x, want %#x", got, want) + } +} + func TestOwnerOnlyDACLNativeShape(t *testing.T) { directory := filepath.Join(t.TempDir(), "auth") if err := os.Mkdir(directory, 0o700); err != nil {