mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-09 18:08:31 -10:00
* fix(daemon): publish status files atomically * fix(daemon): bind status publication to directory handle * fix(daemon): validate status directory access * fix(daemon): accept current token directory owner * fix(daemon): migrate owned runtime directories safely * fix(daemon): open Windows security handle relatively * fix(daemon): use current NT directory object * fix(observability): harden existing crash directories * fix(security): bind crash report creation to private root * fix(observability): atomically publish crash reports * fix(observability): preserve committed crash reports * fix(observability): revalidate committed crash path * fix(daemon): secure runtime lifecycle boundaries * fix(daemon): bind status ownership through cleanup * fix(daemon): preserve Windows status security * fix(daemon): retain trusted runtime root for lifecycle * fix(daemon): roll back redirected socket binds * fix(daemon): address status publication review
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
//go:build windows
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// prepareStatusReplacement copies the existing status document's DACL to the
|
|
// complete staging file before Root.Rename replaces it. Both files are opened
|
|
// relative to the already-bound Root directory handle, so preserving Windows
|
|
// security metadata does not reintroduce a pathname/ancestor-swap boundary.
|
|
func prepareStatusReplacement(root *os.Root, src, dst string) (returnErr error) {
|
|
if filepath.Base(src) != src || filepath.Base(dst) != dst {
|
|
return fmt.Errorf("status replacement names must be root-relative base names")
|
|
}
|
|
directory, err := root.Open(".")
|
|
if err != nil {
|
|
return fmt.Errorf("open bound status directory for DACL preservation: %w", err)
|
|
}
|
|
defer func() {
|
|
if err := directory.Close(); err != nil {
|
|
returnErr = errors.Join(returnErr, fmt.Errorf("close bound status directory for DACL preservation: %w", err))
|
|
}
|
|
}()
|
|
|
|
raw, err := directory.SyscallConn()
|
|
if err != nil {
|
|
return fmt.Errorf("access bound status directory handle: %w", err)
|
|
}
|
|
var destination, source windows.Handle
|
|
var openErr error
|
|
if err := raw.Control(func(rootHandle uintptr) {
|
|
destination, openErr = openStatusSecurityHandle(windows.Handle(rootHandle), dst, windows.READ_CONTROL|windows.FILE_READ_ATTRIBUTES)
|
|
if isMissingStatusObject(openErr) {
|
|
openErr = nil // no destination descriptor exists to preserve
|
|
return
|
|
}
|
|
if openErr != nil {
|
|
return
|
|
}
|
|
source, openErr = openStatusSecurityHandle(windows.Handle(rootHandle), src, windows.READ_CONTROL|windows.WRITE_DAC|windows.FILE_READ_ATTRIBUTES)
|
|
}); err != nil {
|
|
return fmt.Errorf("open status files through bound directory: %w", err)
|
|
}
|
|
if destination != 0 {
|
|
defer windows.CloseHandle(destination)
|
|
}
|
|
if source != 0 {
|
|
defer windows.CloseHandle(source)
|
|
}
|
|
if openErr != nil {
|
|
return fmt.Errorf("open status files for DACL preservation: %w", openErr)
|
|
}
|
|
if destination == 0 {
|
|
return nil
|
|
}
|
|
|
|
descriptor, err := windows.GetSecurityInfo(destination, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION)
|
|
if err != nil {
|
|
return fmt.Errorf("read existing status DACL: %w", err)
|
|
}
|
|
dacl, _, err := descriptor.DACL()
|
|
if err != nil {
|
|
return fmt.Errorf("decode existing status DACL: %w", err)
|
|
}
|
|
if dacl == nil {
|
|
return fmt.Errorf("existing status file has an unrestricted Windows DACL")
|
|
}
|
|
control, _, err := descriptor.Control()
|
|
if err != nil {
|
|
return fmt.Errorf("read existing status DACL control: %w", err)
|
|
}
|
|
securityInfo := windows.SECURITY_INFORMATION(windows.DACL_SECURITY_INFORMATION)
|
|
if control&windows.SE_DACL_PROTECTED != 0 {
|
|
securityInfo |= windows.SECURITY_INFORMATION(windows.PROTECTED_DACL_SECURITY_INFORMATION)
|
|
} else {
|
|
securityInfo |= windows.SECURITY_INFORMATION(windows.UNPROTECTED_DACL_SECURITY_INFORMATION)
|
|
}
|
|
if err := windows.SetSecurityInfo(source, windows.SE_FILE_OBJECT, securityInfo, nil, nil, dacl, nil); err != nil {
|
|
return fmt.Errorf("preserve existing status DACL on replacement: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func openStatusSecurityHandle(root windows.Handle, name string, access uint32) (windows.Handle, error) {
|
|
objectName, err := windows.NewNTUnicodeString(name)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
attributes := &windows.OBJECT_ATTRIBUTES{
|
|
Length: uint32(unsafe.Sizeof(windows.OBJECT_ATTRIBUTES{})),
|
|
RootDirectory: root,
|
|
ObjectName: objectName,
|
|
Attributes: windows.OBJ_CASE_INSENSITIVE | windows.OBJ_DONT_REPARSE,
|
|
}
|
|
var handle windows.Handle
|
|
var status windows.IO_STATUS_BLOCK
|
|
err = windows.NtCreateFile(
|
|
&handle,
|
|
access,
|
|
attributes,
|
|
&status,
|
|
nil,
|
|
0,
|
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
windows.FILE_OPEN,
|
|
windows.FILE_NON_DIRECTORY_FILE|windows.FILE_OPEN_REPARSE_POINT,
|
|
0,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var info windows.ByHandleFileInformation
|
|
if err := windows.GetFileInformationByHandle(handle, &info); err != nil {
|
|
windows.CloseHandle(handle)
|
|
return 0, err
|
|
}
|
|
if info.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
|
windows.CloseHandle(handle)
|
|
return 0, fmt.Errorf("refusing status DACL operation on reparse point %q", name)
|
|
}
|
|
return handle, nil
|
|
}
|
|
|
|
func isMissingStatusObject(err error) bool {
|
|
return errors.Is(err, windows.STATUS_OBJECT_NAME_NOT_FOUND) ||
|
|
errors.Is(err, windows.STATUS_OBJECT_PATH_NOT_FOUND)
|
|
}
|