@@ -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.
2231func 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