fix(windows): protect lifecycle and backup state
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -49,16 +50,19 @@ type Transaction struct {
|
||||
// Acquire obtains the shared lock used by backup, restore, Pi lifecycle and product updates.
|
||||
func Acquire(installation config.Installation) (*Lock, error) {
|
||||
directory := installation.ControlDirectory()
|
||||
if err := os.MkdirAll(directory, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("create lifecycle control directory: %w", err)
|
||||
// The lifecycle directory is also the restore staging parent. Protect both the shared
|
||||
// .tht directory and this installation's child before any lock or staging artifact is
|
||||
// created; chmod alone does not install an owner-only DACL on Windows.
|
||||
if err := ensurePrivateLifecycleDirectory(filepath.Dir(directory)); err != nil {
|
||||
return nil, fmt.Errorf("protect lifecycle control parent: %w", err)
|
||||
}
|
||||
if err := ensurePrivateLifecycleDirectory(directory); err != nil {
|
||||
return nil, fmt.Errorf("protect lifecycle control directory: %w", err)
|
||||
}
|
||||
info, err := os.Lstat(directory)
|
||||
if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, errors.New("lifecycle control directory is not a regular directory")
|
||||
}
|
||||
if err := os.Chmod(directory, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("protect lifecycle control directory: %w", err)
|
||||
}
|
||||
|
||||
tokenBytes := make([]byte, 16)
|
||||
if _, err := rand.Read(tokenBytes); err != nil {
|
||||
@@ -66,11 +70,11 @@ func Acquire(installation config.Installation) (*Lock, error) {
|
||||
}
|
||||
token := hex.EncodeToString(tokenBytes)
|
||||
path := filepath.Join(directory, lockFileName)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
return nil, ErrLocked
|
||||
}
|
||||
file, err := safeio.CreateCanonicalNewPrivateFile(path)
|
||||
if err != nil {
|
||||
if _, statErr := os.Lstat(path); statErr == nil {
|
||||
return nil, ErrLocked
|
||||
}
|
||||
return nil, fmt.Errorf("acquire lifecycle lock: %w", err)
|
||||
}
|
||||
value := owner{Token: token, PID: os.Getpid(), CreatedAt: time.Now().UTC()}
|
||||
@@ -86,6 +90,25 @@ func Acquire(installation config.Installation) (*Lock, error) {
|
||||
return &Lock{path: path, token: token}, nil
|
||||
}
|
||||
|
||||
// ensurePrivateLifecycleDirectory repairs an existing directory's protection or creates the
|
||||
// final missing component with the platform's owner-only primitive. It deliberately does not
|
||||
// use os.MkdirAll for the security-sensitive path: safeio validates every canonical ancestor.
|
||||
func ensurePrivateLifecycleDirectory(path string) error {
|
||||
info, err := os.Lstat(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if err := safeio.EnsurePrivateDirectory(path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
||||
return safeio.ErrUnsafeFile
|
||||
} else {
|
||||
if err := safeio.ProtectPrivateDirectory(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return safeio.ValidatePrivateDirectory(path)
|
||||
}
|
||||
|
||||
// AcquireTransaction obtains a lifecycle lock and returns the capability required by callers
|
||||
// that perform nested work inside the same non-reentrant transaction.
|
||||
func AcquireTransaction(installation config.Installation) (*Transaction, error) {
|
||||
|
||||
Reference in New Issue
Block a user