Files
zero/internal/daemon/status_replace_windows.go
KRATOS 90014210d2 fix(daemon): publish status files atomically (#949)
* 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
2026-09-12 18:18:12 +05:30

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)
}