e2etest/newe2e_resource_managers_local_windows.go (137 lines of code) (raw):
//go:build windows
package e2etest
import (
"fmt"
"github.com/Azure/azure-storage-azcopy/v10/common"
"github.com/Azure/azure-storage-azcopy/v10/sddl"
"github.com/Azure/azure-storage-azcopy/v10/ste"
"github.com/hillu/go-ntdll"
"golang.org/x/sys/windows"
"strings"
"sync"
"syscall"
"unsafe"
)
// getHandle obtains a windows file handle with generic read permissions & backup semantics
func (l LocalObjectResourceManager) getHandle(path string, a Asserter) ntdll.Handle {
srcPtr, err := syscall.UTF16PtrFromString(path)
a.NoError("Get UTF16 pointer", err)
// custom open call, because must specify FILE_FLAG_BACKUP_SEMANTICS to make --backup mode work properly (i.e. our use of SeBackupPrivilege)
fd, err := windows.CreateFile(srcPtr,
windows.GENERIC_READ, windows.FILE_SHARE_READ, nil,
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
a.NoError("Get file descriptor", err)
return ntdll.Handle(fd)
}
func (l LocalObjectResourceManager) GetSDDL(a Asserter) string {
filePath := l.getWorkingPath()
fd := l.getHandle(filePath, a)
buf := make([]byte, 512)
bufLen := uint32(len(buf))
needValidate := false
status := ntdll.CallWithExpandingBuffer(func() ntdll.NtStatus {
status := ntdll.NtQuerySecurityObject(
fd,
windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
(*ntdll.SecurityDescriptor)(unsafe.Pointer(&buf[0])),
uint32(len(buf)),
&bufLen)
if status == ntdll.STATUS_SUCCESS {
sd := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&buf[0]))
bufLen = sd.Length()
needValidate = true
}
return status
}, &buf, &bufLen)
if status != ntdll.STATUS_SUCCESS {
a.Error(fmt.Sprintf("failed to query security object %s (ntstatus: %s)", filePath, status.String()))
}
sd := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&buf[0])) // ntdll.SecurityDescriptor is equivalent
if needValidate && !sd.IsValid() {
a.Error(fmt.Sprintf("failed to query security object %s (invalid descriptor returned with a successful status)", filePath))
}
fSDDL, err := sddl.ParseSDDL(sd.String())
if err != nil {
a.NoError(fmt.Sprintf("failed to parse SDDL for security object %s", filePath), err)
}
a.AssertNow("SDDL sanity check", Equal{}, fSDDL.String(), sd.String())
return fSDDL.PortableString()
}
func (l LocalObjectResourceManager) GetSMBProperties(a Asserter) ste.TypedSMBPropertyHolder {
info, err := common.GetFileInformation(l.getWorkingPath())
a.NoError("get file SMB props", err)
return ste.HandleInfo{ByHandleFileInformation: info}
}
func (l LocalObjectResourceManager) PutSMBProperties(a Asserter, properties FileProperties) {
filePath := l.getWorkingPath()
pathPtr, err := syscall.UTF16PtrFromString(filePath)
a.NoError("get UTF16 pointer for path", err)
if properties.FileAttributes != nil {
attr, err := ParseNTFSAttributes(*properties.FileAttributes)
a.NoError("Parse attributes", err)
err = windows.SetFileAttributes(pathPtr, uint32(attr))
a.NoError("Set file attributes", err)
}
if properties.hasCustomTimes() {
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
var creation, lastWrite *windows.Filetime
if properties.FileCreationTime != nil {
c := windows.NsecToFiletime(properties.FileCreationTime.UnixNano())
creation = &c
}
if properties.FileLastWriteTime != nil {
lmt := windows.NsecToFiletime(properties.FileLastWriteTime.UnixNano())
lastWrite = &lmt
}
// need custom CreateFile call because need FILE_WRITE_ATTRIBUTES
fd, err := windows.CreateFile(pathPtr,
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, &sa,
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
a.NoError(fmt.Sprintf("Get file descriptor %s", filePath), err)
defer a.NoError("close handle", windows.Close(fd))
err = windows.SetFileTime(fd, creation, nil, lastWrite)
a.NoError("Set file times", err)
}
}
// These syscalls are unfortunately, thread-sensitive.
var globalSetAclMu = &sync.Mutex{}
// PutSDDL sets SDDLs like AzCopy does for downloads
func (l LocalObjectResourceManager) PutSDDL(sddlstr string, a Asserter) {
filePath := l.getWorkingPath()
sd, err := windows.SecurityDescriptorFromString(sddlstr)
a.NoError("parse security descriptor", err)
parsed, err := sddl.ParseSDDL(sddlstr)
a.NoError("parse security descriptor (internal lib)", err)
ctl, _, err := sd.Control()
a.NoError("Get control bits", err)
var securityInfoFlags windows.SECURITY_INFORMATION = windows.DACL_SECURITY_INFORMATION
parsedSDDL, err := sddl.ParseSDDL(sddlstr)
isProtectedAtSource := (ctl & windows.SE_DACL_PROTECTED) != 0
// treat rawpath like it's always the root
isAtTransferRoot := l.rawPath != "" || l.objectPath == ""
/*
via Jason Shay:
One exception is related to the "AI" flag.
If you provide a descriptor to NtSetSecurityObject with just AI (SE_DACL_AUTO_INHERITED), it will not be stored.
If you provide it with SE_DACL_AUTO_INHERITED AND SE_DACL_AUTO_INHERIT_REQ, then SE_DACL_AUTO_INHERITED will be stored (note the _REQ flag is never stored)
The REST API for Azure Files will see the "AI" in the SDDL, and will do the _REQ flag work in the background for you.
*/
if strings.Contains(parsedSDDL.DACL.Flags, "AI") {
// set the DACL auto-inherit flag, since Windows didn't pick it up for some reason...
err := sd.SetControl(windows.SE_DACL_AUTO_INHERITED|windows.SE_DACL_AUTO_INHERIT_REQ, windows.SE_DACL_AUTO_INHERITED|windows.SE_DACL_AUTO_INHERIT_REQ)
a.NoError("tried to persist auto-inherit bit", err)
}
// Protect the root so that we don't have parent ACLs affect the rest of the transfer
if isProtectedAtSource || isAtTransferRoot {
securityInfoFlags |= windows.PROTECTED_DACL_SECURITY_INFORMATION
}
if parsed.GroupSID != "" {
securityInfoFlags |= windows.GROUP_SECURITY_INFORMATION
}
if parsed.OwnerSID != "" {
securityInfoFlags |= windows.OWNER_SECURITY_INFORMATION
}
globalSetAclMu.Lock()
defer globalSetAclMu.Unlock()
destPtr, err := syscall.UTF16PtrFromString(filePath)
a.NoError("Get UTF16 ptr", err)
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
// need WRITE_DAC and WRITE_OWNER for SDDL preservation, no need for ACCESS_SYSTEM_SECURITY, since we don't back up SACLs.
fd, err := windows.CreateFile(destPtr, windows.FILE_GENERIC_WRITE|windows.WRITE_DAC|windows.WRITE_OWNER, windows.FILE_SHARE_WRITE, &sa,
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
a.NoError("Open file descriptor", err)
defer a.NoError("Close file descriptor", windows.Close(fd))
status := ntdll.NtSetSecurityObject(
ntdll.Handle(fd),
ntdll.SecurityInformationT(securityInfoFlags),
// unsafe but actually safe conversion
(*ntdll.SecurityDescriptor)(unsafe.Pointer(sd)),
)
a.Assert("Set ACLs status must be successful", Equal{}, status, ntdll.STATUS_SUCCESS)
}