github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/chroot/chroot.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build !windows && !plan9 6 7 package main 8 9 import ( 10 "bytes" 11 "flag" 12 "fmt" 13 "io" 14 "log" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "strconv" 19 "strings" 20 "syscall" 21 ) 22 23 type userSpec struct { 24 uid uint32 25 gid uint32 26 } 27 28 var defaults = " -g value\n specify supplementary group ids as g1,g2,..,gN\n -s Use this option to not changethe working directory to / after changing the root directory to newroot, i.e., inside the chroot. This option is only permitted when newroot is the old / directory.\n -u value\n specify user and group (ID only) as USER:GROUP (default 1000:1000)" 29 30 func (u *userSpec) Set(s string) error { 31 var err error 32 userspecSplit := strings.Split(s, ":") 33 if len(userspecSplit) != 2 || userspecSplit[1] == "" { 34 return fmt.Errorf("expected user spec flag to be \":\" separated values received %s", s) 35 } 36 37 u.uid, err = stringToUint32(userspecSplit[0]) 38 if err != nil { 39 return err 40 } 41 42 u.gid, err = stringToUint32(userspecSplit[1]) 43 if err != nil { 44 return err 45 } 46 47 return nil 48 } 49 50 func (u *userSpec) Get() interface{} { 51 return *u 52 } 53 54 func (u *userSpec) String() string { 55 return fmt.Sprintf("%d:%d", u.uid, u.gid) 56 } 57 58 func defaultUser() userSpec { 59 return userSpec{ 60 uid: uint32(os.Getuid()), 61 gid: uint32(os.Getgid()), 62 } 63 } 64 65 type groupsSpec struct { 66 groups []uint32 67 } 68 69 func (g *groupsSpec) Set(s string) error { 70 groupStrs := strings.Split(s, ",") 71 g.groups = make([]uint32, len(groupStrs)) 72 73 for index, group := range groupStrs { 74 75 gid, err := stringToUint32(group) 76 if err != nil { 77 return err 78 } 79 80 g.groups[index] = gid 81 } 82 83 return nil 84 } 85 86 func (g *groupsSpec) Get() interface{} { 87 return *g 88 } 89 90 func (g *groupsSpec) String() string { 91 var buffer bytes.Buffer 92 93 for index, gid := range g.groups { 94 buffer.WriteString(fmt.Sprint(gid)) 95 if index < len(g.groups)-1 { 96 buffer.WriteString(",") 97 } 98 } 99 100 return buffer.String() 101 } 102 103 var ( 104 skipchdirFlag bool 105 user = defaultUser() 106 groups = groupsSpec{} 107 ) 108 109 func init() { 110 flag.Var(&user, "u", "specify user and group (ID only) as USER:GROUP") 111 flag.Var(&groups, "g", "specify supplementary group ids as g1,g2,..,gN") 112 flag.BoolVar(&skipchdirFlag, "s", false, fmt.Sprint("Use this option to not change", 113 "the working directory to / after changing the root directory to newroot, i.e., ", 114 "inside the chroot. This option is only permitted when newroot is the old / directory.")) 115 } 116 117 func stringToUint32(str string) (uint32, error) { 118 ret, err := strconv.ParseUint(str, 10, 32) 119 if err != nil { 120 return 0, err 121 } 122 return uint32(ret), nil 123 } 124 125 func parseCommand(args []string) []string { 126 if len(args) > 1 { 127 return args[1:] 128 } 129 return []string{"/bin/sh", "-i"} 130 } 131 132 func isRoot(dir string) (bool, error) { 133 realPath, err := filepath.EvalSymlinks(dir) 134 if err != nil { 135 return false, err 136 } 137 absolutePath, err := filepath.Abs(realPath) 138 if err != nil { 139 return false, err 140 } 141 if absolutePath == "/" { 142 return true, nil 143 } 144 return false, nil 145 } 146 147 func chroot(w io.Writer, args ...string) (err error) { 148 var ( 149 newRoot string 150 isOldroot bool 151 ) 152 if len(args) == 0 { 153 fmt.Fprint(w, defaults) 154 return nil 155 } 156 157 newRoot, err = filepath.Abs(args[0]) 158 if err != nil { 159 return err 160 } 161 isOldroot, err = isRoot(newRoot) 162 if err != nil { 163 return err 164 } 165 166 if !skipchdirFlag { 167 err = os.Chdir(newRoot) 168 if err != nil { 169 return err 170 } 171 } else if !isOldroot { 172 return fmt.Errorf("the -s option is only permitted when newroot is the old / directory") 173 } 174 175 argv := parseCommand(args) 176 177 cmd := exec.Command(argv[0], argv[1:]...) 178 179 cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr 180 cmd.SysProcAttr = &syscall.SysProcAttr{ 181 Credential: &syscall.Credential{ 182 Uid: user.uid, 183 Gid: user.gid, 184 Groups: groups.groups, 185 }, 186 Chroot: newRoot, 187 } 188 189 if err = cmd.Run(); err != nil { 190 return err 191 } 192 return nil 193 } 194 195 func main() { 196 flag.Parse() 197 if err := chroot(os.Stdout, flag.Args()...); err != nil { 198 log.Fatal(err) 199 } 200 }