Skip to content

Commit bef1230

Browse files
authored
Merge pull request #62 from stacklok/jaosorior/skip-home-chown
Skip recursive home chown when ownership is correct
2 parents 4f4f736 + a142334 commit bef1230

File tree

1 file changed

+69
-4
lines changed

1 file changed

+69
-4
lines changed

guest/boot/fixhome.go

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,82 @@ import (
1111
"os"
1212
"path/filepath"
1313
"strings"
14+
"syscall"
1415
)
1516

16-
// fixHomeOwnership recursively chowns the user's home directory so that
17-
// files injected by rootfs hooks (which may have been written by a non-root
18-
// host user) are owned by the sandbox user. It also enforces strict SSH
19-
// directory permissions (0700 for .ssh/, 0600 for files inside .ssh/).
17+
// fixHomeOwnership ensures the user's home directory and critical
18+
// subdirectories (.ssh/) have correct ownership and permissions.
19+
//
20+
// When the home directory is already owned by uid:gid (the common case
21+
// on Linux with user-namespace-backed virtiofs), only the .ssh/
22+
// subtree is walked to enforce strict SSH permissions. This avoids a
23+
// costly recursive chown of the entire home directory which can contain
24+
// hundreds of thousands of files from the OCI image.
25+
//
26+
// A full recursive chown is only performed when the home directory
27+
// itself has wrong ownership (e.g. macOS hosts without user namespaces
28+
// where rootfs hooks cannot chown to the sandbox UID).
2029
//
2130
// This runs as PID 1 (root) inside the guest, so chown always succeeds.
2231
func fixHomeOwnership(logger *slog.Logger, home string, uid, gid int) {
2332
logger.Info("fixing home directory ownership", "home", home, "uid", uid, "gid", gid)
2433

34+
if homeAlreadyOwned(home, uid, gid) {
35+
logger.Info("home directory already owned correctly, fixing .ssh only")
36+
fixSSHPermissions(logger, home, uid, gid)
37+
return
38+
}
39+
40+
logger.Info("home directory has wrong ownership, running full recursive chown")
41+
fullRecursiveChown(logger, home, uid, gid)
42+
}
43+
44+
// homeAlreadyOwned checks whether the home directory itself is owned by
45+
// the expected uid and gid.
46+
func homeAlreadyOwned(home string, uid, gid int) bool {
47+
info, err := os.Lstat(home)
48+
if err != nil {
49+
return false
50+
}
51+
stat, ok := info.Sys().(*syscall.Stat_t)
52+
if !ok {
53+
return false
54+
}
55+
return int(stat.Uid) == uid && int(stat.Gid) == gid
56+
}
57+
58+
// fixSSHPermissions walks only the .ssh/ subtree under home, chowning
59+
// and enforcing strict permissions (0700 dirs, 0600 files).
60+
func fixSSHPermissions(logger *slog.Logger, home string, uid, gid int) {
61+
sshDir := filepath.Join(home, ".ssh")
62+
if _, err := os.Stat(sshDir); os.IsNotExist(err) {
63+
return
64+
}
65+
66+
err := filepath.WalkDir(sshDir, func(path string, d fs.DirEntry, err error) error {
67+
if err != nil {
68+
return err
69+
}
70+
if d.Type()&fs.ModeSymlink != 0 {
71+
return nil
72+
}
73+
if chownErr := os.Lchown(path, uid, gid); chownErr != nil {
74+
logger.Warn("chown failed", "path", path, "error", chownErr)
75+
}
76+
perm := sshPermission(d.IsDir())
77+
if chmodErr := os.Chmod(path, perm); chmodErr != nil {
78+
logger.Warn("chmod failed", "path", path, "perm", perm, "error", chmodErr)
79+
}
80+
return nil
81+
})
82+
if err != nil {
83+
logger.Warn(".ssh permission fixup incomplete", "error", err)
84+
}
85+
}
86+
87+
// fullRecursiveChown walks the entire home tree, chowning every entry
88+
// and enforcing SSH permissions on .ssh/ paths.
89+
func fullRecursiveChown(logger *slog.Logger, home string, uid, gid int) {
2590
err := filepath.WalkDir(home, func(path string, d fs.DirEntry, err error) error {
2691
if err != nil {
2792
return err

0 commit comments

Comments
 (0)