From a7564fd482feb903f9562a135f1317fd3b480739 Mon Sep 17 00:00:00 2001 From: Amit Barve Date: Tue, 16 Nov 2021 11:03:06 -0800 Subject: [PATCH] Add Getter/Setters for Extended Attributes of a file and reparse points. Signed-off-by: Amit Barve --- ea.go | 47 ++++++++++++++++++ ea_test.go | 56 +++++++++++++++++++++ fileinfo.go | 17 +++++-- pipe.go | 1 + reparse.go | 80 ++++++++++++++++++++++++++++++ reparse_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++++ sd.go | 19 ++++++++ sd_test.go | 24 ++++++++- syscall.go | 2 +- zsyscall_windows.go | 22 +++++++++ 10 files changed, 378 insertions(+), 6 deletions(-) create mode 100644 reparse_test.go diff --git a/ea.go b/ea.go index 4051c1b3..faeb5652 100644 --- a/ea.go +++ b/ea.go @@ -1,9 +1,15 @@ +//go:build windows +// +build windows + package winio import ( "bytes" "encoding/binary" "errors" + "fmt" + + "golang.org/x/sys/windows" ) type fileFullEaInformation struct { @@ -13,6 +19,9 @@ type fileFullEaInformation struct { ValueLength uint16 } +//sys getFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen uint32, returnSingleEntry bool, eaList uintptr, eaListLen uint32, eaIndex *uint32, restartScan bool) (status ntstatus) = ntdll.NtQueryEaFile +//sys setFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen uint32) (status ntstatus) = ntdll.NtSetEaFile + var ( fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) @@ -28,6 +37,44 @@ type ExtendedAttribute struct { Flags uint8 } +// GetFileEA retrieves the extended attributes for the file represented by `handle`. The +// `handle` must have been opened with file access flag FILE_READ_EA (0x8). +func GetFileEA(handle windows.Handle) ([]ExtendedAttribute, error) { + // default buffer size to start with + bufLen := 1024 + buf := make([]byte, bufLen) + var iosb ioStatusBlock + // keep increasing the buffer size until it is large enough + for { + status := getFileEA(handle, &iosb, &buf[0], uint32(bufLen), false, 0, 0, nil, true) + if status.Err() != nil { + // convert ntstatus code to windows error + if status.Err() == windows.ERROR_INSUFFICIENT_BUFFER || status.Err() == windows.ERROR_MORE_DATA { + bufLen *= 2 + buf = make([]byte, bufLen) + } else { + return nil, fmt.Errorf("get file EA failed with: %w", status.Err()) + } + } else { + break + } + } + return DecodeExtendedAttributes(buf) +} + +// SetFileEA sets the extended attributes for the file represented by `handle`. The +// handle must have been opened with the file access flag FILE_WRITE_EA(0x10). +func SetFileEA(handle windows.Handle, attrs []ExtendedAttribute) error { + encodedEA, err := EncodeExtendedAttributes(attrs) + if err != nil { + return fmt.Errorf("failed to encoded extended attributes: %w", err) + } + + var iosb ioStatusBlock + + return setFileEA(handle, &iosb, &encodedEA[0], uint32(len(encodedEA))).Err() +} + func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { var info fileFullEaInformation err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) diff --git a/ea_test.go b/ea_test.go index 3e32076f..c300f675 100644 --- a/ea_test.go +++ b/ea_test.go @@ -4,12 +4,19 @@ package winio import ( + "fmt" "io/ioutil" + "math" + "math/rand" "os" + "path/filepath" "reflect" "syscall" "testing" + "time" "unsafe" + + "golang.org/x/sys/windows" ) var ( @@ -23,6 +30,10 @@ var ( testEasTruncated = testEasEncoded[0:20] ) +func init() { + rand.Seed(time.Now().Unix()) +} + func Test_RoundTripEas(t *testing.T) { b, err := EncodeExtendedAttributes(testEas) if err != nil { @@ -90,3 +101,48 @@ func Test_SetFileEa(t *testing.T) { t.Fatalf("NtSetEaFile failed with %08x", r) } } + +func Test_SetGetFileEA(t *testing.T) { + tempDir := t.TempDir() + testfilePath := filepath.Join(tempDir, "testfile.txt") + // create temp file + testfile, err := os.Create(testfilePath) + if err != nil { + t.Fatalf("failed to create temporary file: %s", err) + } + testfile.Close() + + nAttrs := 3 + testEAs := make([]ExtendedAttribute, 3) + // generate random extended attributes for test + for i := 0; i < nAttrs; i++ { + // EA name is automatically converted to upper case before storing, so + // when reading it back it returns the upper case name. To avoid test + // failures because of that keep the name upper cased. + testEAs[i].Name = fmt.Sprintf("TESTEA%d", i+1) + testEAs[i].Value = make([]byte, rand.Int31n(math.MaxUint8)) + rand.Read(testEAs[i].Value) + } + + utf16Path := windows.StringToUTF16Ptr(testfilePath) + fileAccessRightReadWriteEA := (0x8 | 0x10) + fileHandle, err := windows.CreateFile(utf16Path, uint32(fileAccessRightReadWriteEA), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0) + if err != nil { + t.Fatalf("open file failed with: %s", err) + } + defer windows.Close(fileHandle) + + if err := SetFileEA(fileHandle, testEAs); err != nil { + t.Fatalf("set EA for file failed: %s", err) + } + + var readEAs []ExtendedAttribute + if readEAs, err = GetFileEA(fileHandle); err != nil { + t.Fatalf("get EA for file failed: %s", err) + } + + if !reflect.DeepEqual(readEAs, testEAs) { + t.Logf("expected: %+v, found: %+v\n", testEAs, readEAs) + t.Fatalf("EAs read from testfile don't match") + } +} diff --git a/fileinfo.go b/fileinfo.go index 350c1096..86a3fd5b 100644 --- a/fileinfo.go +++ b/fileinfo.go @@ -22,7 +22,7 @@ type FileBasicInfo struct { func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { bi := &FileBasicInfo{} if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { - return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + return nil, &os.SyscallError{Syscall: "GetFileInformationByHandleEx", Err: err} } runtime.KeepAlive(f) return bi, nil @@ -31,12 +31,21 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { // SetFileBasicInfo sets times and attributes for a file. func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { - return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} + return &os.SyscallError{Syscall: "SetFileInformationByHandle", Err: err} } runtime.KeepAlive(f) return nil } +// SetFileBasicInfoForHandle sets times and attributes for a file represented by +// the given handle +func SetFileBasicInfoForHandle(f windows.Handle, bi *FileBasicInfo) error { + if err := windows.SetFileInformationByHandle(f, windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + return &os.SyscallError{Syscall: "SetFileInformationByHandle", Err: err} + } + return nil +} + // FileStandardInfo contains extended information for the file. // FILE_STANDARD_INFO in WinBase.h // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info @@ -50,7 +59,7 @@ type FileStandardInfo struct { func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) { si := &FileStandardInfo{} if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil { - return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + return nil, &os.SyscallError{Syscall: "GetFileInformationByHandleEx", Err: err} } runtime.KeepAlive(f) return si, nil @@ -67,7 +76,7 @@ type FileIDInfo struct { func GetFileID(f *os.File) (*FileIDInfo, error) { fileID := &FileIDInfo{} if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { - return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + return nil, &os.SyscallError{Syscall: "GetFileInformationByHandleEx", Err: err} } runtime.KeepAlive(f) return fileID, nil diff --git a/pipe.go b/pipe.go index 1acb2014..cbd4ebd6 100644 --- a/pipe.go +++ b/pipe.go @@ -27,6 +27,7 @@ import ( //sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U //sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl +// ioStatusBlock represents the IO_STATUS_BLOCK struct defined here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_status_block type ioStatusBlock struct { Status, Information uintptr } diff --git a/reparse.go b/reparse.go index 5f2f3a47..47ce6e60 100644 --- a/reparse.go +++ b/reparse.go @@ -10,13 +10,21 @@ import ( "strings" "unicode/utf16" "unsafe" + + "golang.org/x/sys/windows" ) const ( reparseTagMountPoint = 0xA0000003 reparseTagSymlink = 0xA000000C + + _FSCTL_SET_REPARSE_POINT uint32 = ((0x9 << 16) | (41 << 2)) + _FSCTL_GET_REPARSE_POINT uint32 = ((0x9 << 16) | (42 << 2)) + _MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 ) +// reparseDataBuffer is only kept for compatibility purposes. When adding new +// code prefer ReparseDataBuffer instead. type reparseDataBuffer struct { ReparseTag uint32 ReparseDataLength uint16 @@ -28,6 +36,9 @@ type reparseDataBuffer struct { } // ReparsePoint describes a Win32 symlink or mount point. +// However, there can be other types of reparse points that are specific to +// third party applications. To work with such reparse points use +// ReparseDataBuffer instead. type ReparsePoint struct { Target string IsMountPoint bool @@ -129,3 +140,72 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte { binary.Write(&b, binary.LittleEndian, target16) return b.Bytes() } + +// ReparseDataBuffer corresponds to REPARSE_DATA_BUFFER struct defined here: +// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer. +// ReparesPoint struct (and corresponding Encode/Decode functions) provided above only +// handle symlink or mountpoint type of reparse points. To work with other reparse points +// use this struct. When calling SetReparsePoint, if it is a symlink or mountpoint +// reparse point pass in the buffer generated by EncodeReparsePointData. If it is some +// other kind of reparse point pass in the buffer generated by +// ReparseDataBuffer.Encode. (same goes for GetReparsePoints) +type ReparseDataBuffer struct { + ReparseTag uint32 + ReparseDataLength uint16 + Reserved uint16 + // a generic data buffer + DataBuffer []byte +} + +// Encode encodes the current ReparseDataBuffer struct that can be passed +// to the SetReparsePoint. +func (r *ReparseDataBuffer) Encode() []byte { + var buf bytes.Buffer + binary.Write(&buf, binary.LittleEndian, r.ReparseTag) + binary.Write(&buf, binary.LittleEndian, r.ReparseDataLength) + binary.Write(&buf, binary.LittleEndian, r.Reserved) + buf.Write(r.DataBuffer) + return buf.Bytes() +} + +// Decode decodes the byte array (returned by GetReparsePoint) into +// the current ReparseDataBuffer struct. +func (r *ReparseDataBuffer) Decode(data []byte) error { + rdr := bytes.NewReader(data) + err := binary.Read(rdr, binary.LittleEndian, r) + if err != nil { + fmt.Errorf("failed to decode reparse data buffer: %w", err) + } + return nil +} + +// GetReparsePoint returns a byte array that represents a ReparseDataBuffer if +// the file at `path` is a valid reparse point. +func GetReparsePoint(path string) ([]byte, error) { + utf16DestPath, err := windows.UTF16FromString(path) + if err != nil { + return nil, err + } + // We need to open the file with windows specific permissions so just using + // os.Open won't work. + fileHandle, err := windows.CreateFile(&utf16DestPath[0], windows.GENERIC_READ, 0, nil, windows.OPEN_EXISTING, (windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OPEN_REPARSE_POINT | windows.FILE_FLAG_BACKUP_SEMANTICS), 0) + if err != nil { + return nil, fmt.Errorf("open file failed with: %w", err) + } + defer windows.Close(fileHandle) + + outBuf := make([]byte, _MAXIMUM_REPARSE_DATA_BUFFER_SIZE) + var outBufLen uint32 + err = windows.DeviceIoControl(fileHandle, _FSCTL_GET_REPARSE_POINT, nil, 0, &outBuf[0], _MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &outBufLen, nil) + if err != nil { + return nil, fmt.Errorf("failed to get reparse point for file %s: %w", path, err) + } + + return outBuf[:outBufLen], nil +} + +// SetReparsePoint sets a reparse point with `data` on the file/directory represented by +// `handle` . `data` should be an encoded ReparseDataBuffer struct. +func SetReparsePoint(handle windows.Handle, data []byte) error { + return windows.DeviceIoControl(handle, _FSCTL_SET_REPARSE_POINT, &data[0], uint32(len(data)), nil, 0, nil, nil) +} diff --git a/reparse_test.go b/reparse_test.go new file mode 100644 index 00000000..022697f0 --- /dev/null +++ b/reparse_test.go @@ -0,0 +1,116 @@ +//go:build windows +// +build windows + +package winio + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + + "golang.org/x/sys/windows" +) + +func TestGetSymlinkReparsePoint(t *testing.T) { + testDir := t.TempDir() + testfilePath := filepath.Join(testDir, "test.txt") + testfile, err := os.Create(testfilePath) + if err != nil { + t.Fatalf("failed to created test file: %s", err) + } + testfile.Close() + + linkDir := t.TempDir() + linkfilePath := filepath.Join(linkDir, "link.txt") + + if err := os.Symlink(testfilePath, linkfilePath); err != nil { + t.Fatalf("failed to create link: %s", err) + } + + // retrieve reparse buffer for the link + reparseBuffer, err := GetReparsePoint(linkfilePath) + if err != nil { + t.Fatalf("failed to get reparse point: %s", err) + } + + rp, err := DecodeReparsePoint(reparseBuffer) + if err != nil { + t.Fatalf("failed to decode reparse point: %s", err) + } + + if rp.IsMountPoint || !strings.EqualFold(rp.Target, testfilePath) { + t.Fatalf("Reparse point doesn't match: %+v", rp) + } +} + +func TestGetDirectorySymlinkReparsePoint(t *testing.T) { + testDir := t.TempDir() + targetDirPath := filepath.Join(testDir, "test") + if err := os.Mkdir(targetDirPath, 0777); err != nil { + t.Fatalf("failed to created test directory: %s", err) + } + + linkDir := t.TempDir() + linkPath := filepath.Join(linkDir, "link") + if err := os.Symlink(targetDirPath, linkPath); err != nil { + t.Fatalf("failed to create link: %s", err) + } + + // retrieve reparse buffer for the link + reparseBuffer, err := GetReparsePoint(linkPath) + if err != nil { + t.Fatalf("failed to get reparse point: %s", err) + } + + rp, err := DecodeReparsePoint(reparseBuffer) + if err != nil { + t.Fatalf("failed to decode reparse point: %s", err) + } + + if rp.IsMountPoint || !strings.EqualFold(rp.Target, targetDirPath) { + t.Fatalf("Reparse point doesn't match: %+v", rp) + } +} + +func TestSetReparsePoint(t *testing.T) { + testDir := t.TempDir() + testfilePath := filepath.Join(testDir, "test.txt") + + // If we use reparseTagMountPoint or reparseTagSymlink we need to + // generate a fully valid ReparseDataBuffer struct. Instead we use + // reparseTagWcifs and put some random data in the ReparesDataBuffer. + reparseTagWcifs := 0x90001018 + r := ReparseDataBuffer{ + ReparseTag: uint32(reparseTagWcifs), + ReparseDataLength: 4, + Reserved: 0, + DataBuffer: []byte{0xde, 0xad, 0xbe, 0xef}, + } + + utf16Path, err := windows.UTF16PtrFromString(testfilePath) + if err != nil { + t.Fatalf("failed to convert path into utf16: %s", err) + } + + testfileHandle, err := windows.CreateFile(utf16Path, windows.GENERIC_WRITE|windows.GENERIC_READ, 0, nil, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL, 0) + if err != nil { + t.Fatalf("failed to open file handle: %s", err) + } + defer windows.Close(testfileHandle) + + if err := SetReparsePoint(testfileHandle, r.Encode()); err != nil { + t.Fatalf("failed to set reparse point: %s", err) + } + windows.Close(testfileHandle) + + reparseBuffer, err := GetReparsePoint(testfilePath) + if err != nil { + t.Fatalf("failed to get reparse point: %s", err) + } + + if bytes.Compare(reparseBuffer, r.Encode()) != 0 { + t.Fatalf("retrieved reparse point buffer doesn't match") + } +} diff --git a/sd.go b/sd.go index 8edbc843..63b7332b 100644 --- a/sd.go +++ b/sd.go @@ -4,8 +4,11 @@ package winio import ( + "fmt" "syscall" "unsafe" + + "golang.org/x/sys/windows" ) //sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW @@ -97,3 +100,19 @@ func SecurityDescriptorToSddl(sd []byte) (string, error) { defer localFree(uintptr(unsafe.Pointer(sddl))) return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil } + +func GetFileSecurityDescriptor(path string) (*windows.SECURITY_DESCRIPTOR, error) { + utf16Path, err := windows.UTF16FromString(path) + if err != nil { + return nil, err + } + fileHandle, err := windows.CreateFile(&utf16Path[0], (windows.READ_CONTROL | windows.ACCESS_SYSTEM_SECURITY), 0, nil, windows.OPEN_EXISTING, (windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_BACKUP_SEMANTICS), 0) + if err != nil { + return nil, fmt.Errorf("open file failed with: %w", err) + } + sd, err := windows.GetSecurityInfo(fileHandle, windows.SE_FILE_OBJECT, (windows.ATTRIBUTE_SECURITY_INFORMATION | windows.DACL_SECURITY_INFORMATION | windows.GROUP_SECURITY_INFORMATION | windows.LABEL_SECURITY_INFORMATION | windows.OWNER_SECURITY_INFORMATION | windows.SACL_SECURITY_INFORMATION | windows.SCOPE_SECURITY_INFORMATION | windows.BACKUP_SECURITY_INFORMATION)) + if err != nil { + return nil, fmt.Errorf("get security info failed: %w", err) + } + return sd, nil +} diff --git a/sd_test.go b/sd_test.go index cb1291bc..ec336eda 100644 --- a/sd_test.go +++ b/sd_test.go @@ -3,7 +3,9 @@ package winio -import "testing" +import ( + "testing" +) func TestLookupInvalidSid(t *testing.T) { _, err := LookupSidByName(".\\weoifjdsklfj") @@ -27,3 +29,23 @@ func TestLookupEmptyNameFails(t *testing.T) { t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err) } } + +func TestGetFileSDDL(t *testing.T) { + win32 := `C:\Windows\System32` + kern := `C:\Windows\System32\kernel32.dll` + + err := EnableProcessPrivileges([]string{SeBackupPrivilege}) + if err != nil { + t.Fatalf("failed to gain privileges: %s", err) + } + + _, err = GetFileSecurityDescriptor(win32) + if err != nil { + t.Fatalf("failed to get SD for %s: %s", win32, err) + } + + _, err = GetFileSecurityDescriptor(kern) + if err != nil { + t.Fatalf("failed to get SD for %s: %s", kern, err) + } +} diff --git a/syscall.go b/syscall.go index 5955c99f..8ad5f6e8 100644 --- a/syscall.go +++ b/syscall.go @@ -1,3 +1,3 @@ package winio -//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go ea.go diff --git a/zsyscall_windows.go b/zsyscall_windows.go index 176ff75e..6162e761 100644 --- a/zsyscall_windows.go +++ b/zsyscall_windows.go @@ -70,6 +70,8 @@ var ( procLocalFree = modkernel32.NewProc("LocalFree") procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") + procNtQueryEaFile = modntdll.NewProc("NtQueryEaFile") + procNtSetEaFile = modntdll.NewProc("NtSetEaFile") procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") @@ -386,6 +388,26 @@ func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttrib return } +func getFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen uint32, returnSingleEntry bool, eaList uintptr, eaListLen uint32, eaIndex *uint32, restartScan bool) (status ntstatus) { + var _p0 uint32 + if returnSingleEntry { + _p0 = 1 + } + var _p1 uint32 + if restartScan { + _p1 = 1 + } + r0, _, _ := syscall.Syscall9(procNtQueryEaFile.Addr(), 9, uintptr(handle), uintptr(unsafe.Pointer(iosb)), uintptr(unsafe.Pointer(buf)), uintptr(bufLen), uintptr(_p0), uintptr(eaList), uintptr(eaListLen), uintptr(unsafe.Pointer(eaIndex)), uintptr(_p1)) + status = ntstatus(r0) + return +} + +func setFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen uint32) (status ntstatus) { + r0, _, _ := syscall.Syscall6(procNtSetEaFile.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(iosb)), uintptr(unsafe.Pointer(buf)), uintptr(bufLen), 0, 0) + status = ntstatus(r0) + return +} + func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) { r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0) status = ntstatus(r0)