Fix symlink ownership change on Darwin systems.

This commit is contained in:
Darren Dormer 2025-02-10 12:55:49 +01:00 committed by Jörg Thalheim
parent c00da36830
commit b33837ae3c
3 changed files with 37 additions and 61 deletions

View file

@ -9,8 +9,6 @@ import (
"os"
"os/exec"
"strings"
"golang.org/x/sys/unix"
)
func RuntimeDir() (string, error) {
@ -23,29 +21,6 @@ func RuntimeDir() (string, error) {
return strings.TrimSuffix(rundir, "/"), nil
}
func SecureSymlinkChown(symlinkToCheck string, expectedTarget string, owner, group int) error {
// not sure what O_PATH is needed for anyways
fd, err := unix.Open(symlinkToCheck, unix.O_CLOEXEC|unix.O_SYMLINK|unix.O_NOFOLLOW, 0)
if err != nil {
return fmt.Errorf("failed to open %s: %w", symlinkToCheck, err)
}
defer unix.Close(fd)
buf := make([]byte, len(expectedTarget)+1) // oversize by one to detect trunc
n, err := unix.Readlinkat(fd, "", buf)
if err != nil {
return fmt.Errorf("couldn't readlinkat %s", symlinkToCheck)
}
if n > len(expectedTarget) || string(buf[:n]) != expectedTarget {
return fmt.Errorf("symlink %s does not point to %s", symlinkToCheck, expectedTarget)
}
err = unix.Fchownat(fd, "", owner, group, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return fmt.Errorf("cannot change owner of '%s' to %d/%d: %w", symlinkToCheck, owner, group, err)
}
return nil
}
// Does:
// mkdir /tmp/mymount
// NUMSECTORS=128000 # a sector is 512 bytes

View file

@ -18,38 +18,6 @@ func RuntimeDir() (string, error) {
return rundir, nil
}
func SecureSymlinkChown(symlinkToCheck, expectedTarget string, owner, group int) error {
// fd, err := unix.Open(symlinkToCheck, unix.O_CLOEXEC|unix.O_PATH|unix.O_NOFOLLOW, 0)
fd, err := unix.Open(symlinkToCheck, unix.O_CLOEXEC|unix.O_PATH|unix.O_NOFOLLOW, 0)
if err != nil {
return fmt.Errorf("failed to open %s: %w", symlinkToCheck, err)
}
defer unix.Close(fd)
buf := make([]byte, len(expectedTarget)+1) // oversize by one to detect trunc
n, err := unix.Readlinkat(fd, "", buf)
if err != nil {
return fmt.Errorf("couldn't readlinkat %s", symlinkToCheck)
}
if n > len(expectedTarget) || string(buf[:n]) != expectedTarget {
return fmt.Errorf("symlink %s does not point to %s", symlinkToCheck, expectedTarget)
}
stat := unix.Stat_t{}
err = unix.Fstat(fd, &stat)
if err != nil {
return fmt.Errorf("cannot stat '%s': %w", symlinkToCheck, err)
}
if stat.Uid == uint32(owner) && stat.Gid == uint32(group) {
return nil // already correct
}
err = unix.Fchownat(fd, "", owner, group, unix.AT_EMPTY_PATH)
if err != nil {
return fmt.Errorf("cannot change owner of '%s' to %d/%d: %w", symlinkToCheck, owner, group, err)
}
return nil
}
func MountSecretFs(mountpoint string, keysGID int, useTmpfs bool, userMode bool) error {
if err := os.MkdirAll(mountpoint, 0o751); err != nil {
return fmt.Errorf("cannot create directory '%s': %w", mountpoint, err)

View file

@ -185,17 +185,50 @@ func linksAreEqual(linkTarget, targetFile string, info os.FileInfo, owner int, g
return linkTarget == targetFile && validUG
}
func SecureSymlinkChown(targetFile string, path string, owner int, group int) error {
// Create a temp directory to house the symlink while we change it's
// ownership. `os.MkdirTemp` creates a directory with the permissions 0700.
// The temp dir is created in the same parent directory of the final
// symlink, because the later `os.Rename` operation won't work across disk
// devices.
dir, err := os.MkdirTemp(filepath.Dir(path), "")
if err != nil {
return fmt.Errorf("cannot create temporary symlink directory: %w", err)
}
defer os.RemoveAll(dir)
// Create symlink to `targetFile` in the temp dir, before chowning it.
var tmpSymlink = filepath.Join(dir, filepath.Base(path))
if err = os.Symlink(targetFile, tmpSymlink); err != nil {
return fmt.Errorf(
"cannot create symlink '%s' (pointing to '%s'): %w", path, targetFile, err)
}
err = os.Lchown(tmpSymlink, owner, group)
if err != nil {
return fmt.Errorf(
"cannot change owner of symlink '%s' (pointing to '%s') to owner/group: %d/%d: %w",
tmpSymlink, targetFile, owner, group, err)
}
// Move the chowned symlink to it's final location.
err = os.Rename(tmpSymlink, path)
if err != nil {
return fmt.Errorf("cannot move symlink '%s' to '%s': %w", tmpSymlink, path, err)
}
return nil
}
func createSymlink(targetFile string, path string, owner int, group int, userMode bool) error {
for {
stat, err := os.Lstat(path)
if os.IsNotExist(err) {
if err = os.Symlink(targetFile, path); err != nil {
return fmt.Errorf("cannot create symlink '%s': %w", path, err)
}
if !userMode {
if err = SecureSymlinkChown(path, targetFile, owner, group); err != nil {
if err = SecureSymlinkChown(targetFile, path, owner, group); err != nil {
return fmt.Errorf("cannot chown symlink '%s': %w", path, err)
}
} else if err = os.Symlink(targetFile, path); err != nil {
return fmt.Errorf("cannot create symlink '%s': %w", path, err)
}
return nil
} else if err != nil {