diff --git a/tools/tht/internal/lifecycle/lock.go b/tools/tht/internal/lifecycle/lock.go index ed3e6c2e..5ab8adc6 100644 --- a/tools/tht/internal/lifecycle/lock.go +++ b/tools/tht/internal/lifecycle/lock.go @@ -50,14 +50,31 @@ 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() - // 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) + parent := filepath.Dir(directory) + // The lifecycle directory is also the restore staging parent. An existing Windows child + // must be protected before its parent: replacing the parent's inheritable ACL first can + // remove the child's inherited owner authority before the child receives its protected DACL. + // For a new installation the parent must instead exist before safe child creation. + controlInfo, controlErr := os.Lstat(directory) + if controlErr == nil { + if !controlInfo.IsDir() || controlInfo.Mode()&os.ModeSymlink != 0 { + return nil, errors.New("lifecycle control directory is not a regular directory") + } + if err := ensurePrivateLifecycleDirectory(directory); err != nil { + return nil, fmt.Errorf("protect lifecycle control directory: %w", err) + } + if err := ensurePrivateLifecycleDirectory(parent); err != nil { + return nil, fmt.Errorf("protect lifecycle control parent: %w", err) + } + } else if errors.Is(controlErr, os.ErrNotExist) { + if err := ensurePrivateLifecycleDirectory(parent); 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) + } + } else { + return nil, errors.New("lifecycle control directory is unavailable") } info, err := os.Lstat(directory) if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {