github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/contrib/cmd/remap-rootfs/remap-rootfs.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "syscall" 10 11 "github.com/urfave/cli" 12 13 "github.com/opencontainers/runtime-spec/specs-go" 14 ) 15 16 const usage = `contrib/cmd/remap-rootfs 17 18 remap-rootfs is a helper tool to remap the root filesystem of a Open Container 19 Initiative bundle using user namespaces such that the file owners are remapped 20 from "host" mappings to the user namespace's mappings. 21 22 Effectively, this is a slightly more complicated 'chown -R', and is primarily 23 used within runc's integration tests to remap the test filesystem to match the 24 test user namespace. Note that calling remap-rootfs multiple times, or changing 25 the mapping and then calling remap-rootfs will likely produce incorrect results 26 because we do not "un-map" any pre-applied mappings from previous remap-rootfs 27 calls. 28 29 Note that the bundle is assumed to be produced by a trusted source, and thus 30 malicious configuration files will likely not be handled safely. 31 32 To use remap-rootfs, simply pass it the path to an OCI bundle (a directory 33 containing a config.json): 34 35 $ sudo remap-rootfs ./bundle 36 ` 37 38 func toHostID(mappings []specs.LinuxIDMapping, id uint32) (int, bool) { 39 for _, m := range mappings { 40 if m.ContainerID <= id && id < m.ContainerID+m.Size { 41 return int(m.HostID + id), true 42 } 43 } 44 return -1, false 45 } 46 47 type inodeID struct { 48 Dev, Ino uint64 49 } 50 51 func toInodeID(st *syscall.Stat_t) inodeID { 52 return inodeID{Dev: st.Dev, Ino: st.Ino} 53 } 54 55 func remapRootfs(root string, uidMap, gidMap []specs.LinuxIDMapping) error { 56 seenInodes := make(map[inodeID]struct{}) 57 return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 58 if err != nil { 59 return err 60 } 61 62 mode := info.Mode() 63 st := info.Sys().(*syscall.Stat_t) 64 65 // Skip symlinks. 66 if mode.Type() == os.ModeSymlink { 67 return nil 68 } 69 // Skip hard-links to files we've already remapped. 70 id := toInodeID(st) 71 if _, seen := seenInodes[id]; seen { 72 return nil 73 } 74 seenInodes[id] = struct{}{} 75 76 // Calculate the new uid:gid. 77 uid := st.Uid 78 newUID, ok1 := toHostID(uidMap, uid) 79 gid := st.Gid 80 newGID, ok2 := toHostID(gidMap, gid) 81 82 // Skip files that cannot be mapped. 83 if !ok1 || !ok2 { 84 niceName := path 85 if relName, err := filepath.Rel(root, path); err == nil { 86 niceName = "/" + relName 87 } 88 fmt.Printf("skipping file %s: cannot remap user %d:%d -> %d:%d\n", niceName, uid, gid, newUID, newGID) 89 return nil 90 } 91 if err := os.Lchown(path, newUID, newGID); err != nil { 92 return err 93 } 94 // Re-apply any setid bits that would be cleared due to chown(2). 95 return os.Chmod(path, mode) 96 }) 97 } 98 99 func main() { 100 app := cli.NewApp() 101 app.Name = "remap-rootfs" 102 app.Usage = usage 103 104 app.Action = func(ctx *cli.Context) error { 105 args := ctx.Args() 106 if len(args) != 1 { 107 return errors.New("exactly one bundle argument must be provided") 108 } 109 bundle := args[0] 110 111 configFile, err := os.Open(filepath.Join(bundle, "config.json")) 112 if err != nil { 113 return err 114 } 115 defer configFile.Close() 116 117 var spec specs.Spec 118 if err := json.NewDecoder(configFile).Decode(&spec); err != nil { 119 return fmt.Errorf("parsing config.json: %w", err) 120 } 121 122 if spec.Root == nil { 123 return errors.New("invalid config.json: root section is null") 124 } 125 rootfs := filepath.Join(bundle, spec.Root.Path) 126 127 if spec.Linux == nil { 128 return errors.New("invalid config.json: linux section is null") 129 } 130 uidMap := spec.Linux.UIDMappings 131 gidMap := spec.Linux.GIDMappings 132 if len(uidMap) == 0 && len(gidMap) == 0 { 133 fmt.Println("skipping remapping -- no userns mappings specified") 134 return nil 135 } 136 137 return remapRootfs(rootfs, uidMap, gidMap) 138 } 139 if err := app.Run(os.Args); err != nil { 140 fmt.Fprintln(os.Stderr, "error:", err) 141 os.Exit(1) 142 } 143 }