github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/pox/pox.go (about) 1 // Copyright 2012-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 // pox builds a portable executable as a squashfs image. 6 // It is intended to create files compatible with tinycore 7 // tcz files. One of more of the files can be programs 8 // but that is not required. 9 // This could have been a simple program but mksquashfs does not 10 // preserve path information. 11 // Yeah. 12 // 13 // Synopsis: 14 // pox [-[-debug]|d] [-[-file]|f tcz-file] -[-create]|c FILE [...FILE] 15 // pox [-[-debug]|d] [-[-file]|f tcz-file] -[-run|r] PROGRAM -- [...ARGS] 16 // pox [-[-debug]|d] [-[-file]|f tcz-file] -[-create]|c -[-run|r] PROGRAM -- [...ARGS] 17 // 18 // Description: 19 // pox makes portable executables in squashfs format compatible with 20 // tcz format. We don't build in the execution code, rather, we set it 21 // up so we can use the command itself. You can either create the TCZ image 22 // or run a command within an image that was previously created. 23 // 24 // Options: 25 // debug|d: verbose 26 // file|f file: file name (default /tmp/pox.tcz) 27 // run|r: Runs the first non-flag argument to pox. Remaining arguments will 28 // be passed to the program. Use '--' before any flag-like arguments 29 // to prevent pox from interpretting the flags. 30 // create|c: create the TCZ file. 31 // zip|z: Use zip and unzip instead of a loopback mounted squashfs. Be sure 32 // to use -z for both creation and running, or not at all. 33 // For convenience and testing, you can create and run a pox in one command. 34 // 35 // Example: 36 // $ pox -c /bin/bash /bin/cat /bin/ls /etc/hosts 37 // Will build a squashfs, which will be /tmp/pox.tcz 38 // 39 // $ sudo pox -r /bin/bash 40 // Will drop you into the /tmp/pox.tcz running bash 41 // You can use ls and cat on /etc/hosts. 42 // 43 // Simpler example, with arguments: 44 // $ sudo pox -r /bin/ls -- -la 45 // will run `ls -la` and exit. 46 // 47 // $ sudo pox -r -- /bin/ls -la 48 // Syntactically easier: the program name can come after '--' 49 // 50 // $ sudo pox -c -r /bin/bash 51 // Create a pox with a bash and run it. 52 // 53 // Notes: 54 // - When running a pox, you likely need sudo to chroot 55 // 56 // - Binaries run out of a chroot often need files you are unaware of. For 57 // instance, if bash can't find terminfo files, it won't know to handle 58 // backspaces properly. (They occur, but are not shown). To fix this, pass pox 59 // all of the files you need. For bash: `find /lib/terminfo -type f`. 60 // 61 // Other programs rely on help functions, such as '/bin/man'. If your program 62 // has built-in help commands that trigger man pages, e.g. "git help foo", 63 // you'll want to include /bin/man too. But you'll also need everything that 64 // man uses, such as /etc/manpath.config. My advice: skip it. 65 // 66 // - When adding all files in a directory, the easiest thing to do is: 67 // `find $DIR -type f` (Note the ticks: this is a bash command execution). 68 // 69 // - When creating a pox with an executable with shared libraries that are not 70 // installed on your system, such as for a project installed in your home 71 // directory, run pox from the installation prefix directory, such that the 72 // lib/ and bin/ are in pox's working directory. Pox will strip its working 73 // directory from the paths of the files it builds. Having bin/ in the root of 74 // the pox file helps with PATH lookups, and not having the full path from your 75 // machine in the pox file makes it easier to extract a pox file to /usr/local/. 76 // 77 // - Consider adding a --extract | -x option to install to the host. One issue 78 // would be how to handle collisions, e.g. libc. Your app may not like the libc 79 // on the system you run on. 80 // 81 // - pox is not a security boundary. chroot is well known to have holes. Pox is about 82 // enabling execution. Don't expect it to "wall things off". In fact, we mount 83 // /dev, /proc, and /sys; and you can add more things. Commands run under pox 84 // are just as dangerous as anything else. 85 // 86 package main 87 88 import ( 89 "fmt" 90 "io" 91 "io/ioutil" 92 "log" 93 "os" 94 "os/exec" 95 "path/filepath" 96 "strings" 97 "syscall" 98 99 flag "github.com/spf13/pflag" 100 "github.com/u-root/u-root/pkg/ldd" 101 "github.com/u-root/u-root/pkg/mount" 102 "github.com/u-root/u-root/pkg/mount/loop" 103 "github.com/u-root/u-root/pkg/uzip" 104 ) 105 106 const usage = "pox [-[-debug]|d] -[-run|r] | -[-create]|c [-[-file]|f tcz-file] file [...file]" 107 108 type mp struct { 109 source string 110 target string 111 fstype string 112 flags uintptr 113 data string 114 perm os.FileMode // for target in the chroot 115 } 116 117 var ( 118 debug = flag.BoolP("debug", "d", false, "enable debug prints") 119 run = flag.BoolP("run", "r", false, "Run the first file argument") 120 create = flag.BoolP("create", "c", false, "create it") 121 zip = flag.BoolP("zip", "z", false, "use zip instead of squashfs") 122 file = flag.StringP("output", "f", "/tmp/pox.tcz", "Output file") 123 extra = flag.StringP("extra", "e", "", `comma-separated list of extra directories to add (on create) and binds to do (on run). 124 You can specify what directories to add, and when you run, specify what directories are bound over them, e.g.: 125 pox -c -e /tmp,/etc commands .... 126 pox -r -e /a/b/c/tmp:/tmp,/etc:/etc commands ... 127 `) 128 v = func(string, ...interface{}) {} 129 ) 130 131 // When chrooting, programs often want to access various system directories: 132 var chrootMounts = []mp{ 133 // mount --bind /sys /chroot/sys 134 {"/sys", "/sys", "", mount.MS_BIND, "", 0555}, 135 // mount -t proc /proc /chroot/proc 136 {"/proc", "/proc", "proc", 0, "", 0555}, 137 // mount --bind /dev /chroot/dev 138 {"/dev", "/dev", "", mount.MS_BIND, "", 0755}} 139 140 func poxCreate(bin ...string) error { 141 if len(bin) == 0 { 142 return fmt.Errorf(usage) 143 } 144 l, err := ldd.Ldd(bin) 145 if err != nil { 146 var stderr []byte 147 if eerr, ok := err.(*exec.ExitError); ok { 148 stderr = eerr.Stderr 149 } 150 return fmt.Errorf("Running ldd on %v: %v %s", bin, err, stderr) 151 } 152 153 var names []string 154 for _, dep := range l { 155 v("%s", dep.FullName) 156 names = append(names, dep.FullName) 157 } 158 // Now we need to make a template file hierarchy and put 159 // the stuff we want in there. 160 dir, err := ioutil.TempDir("", "pox") 161 if err != nil { 162 return err 163 } 164 if !*debug { 165 defer os.RemoveAll(dir) 166 } 167 pwd, err := os.Getwd() 168 if err != nil { 169 return err 170 } 171 // We don't use defer() here to close files as 172 // that can cause open failures with a large enough number. 173 for _, f := range names { 174 v("Process %v", f) 175 fi, err := os.Stat(f) 176 if err != nil { 177 return err 178 } 179 in, err := os.Open(f) 180 if err != nil { 181 return err 182 } 183 f = strings.TrimPrefix(f, pwd) 184 dfile := filepath.Join(dir, f) 185 d := filepath.Dir(dfile) 186 if err := os.MkdirAll(d, 0755); err != nil { 187 in.Close() 188 return err 189 } 190 out, err := os.OpenFile(dfile, os.O_WRONLY|os.O_CREATE, 191 fi.Mode().Perm()) 192 if err != nil { 193 in.Close() 194 return err 195 } 196 _, err = io.Copy(out, in) 197 in.Close() 198 out.Close() 199 if err != nil { 200 return err 201 } 202 203 } 204 for _, m := range chrootMounts { 205 d := filepath.Join(dir, m.target) 206 v("Mounts: create %q, perm %s", d, m.perm.String()) 207 if err := os.MkdirAll(d, m.perm); err != nil { 208 return err 209 } 210 } 211 err = os.Remove(*file) 212 if err != nil && !os.IsNotExist(err) { 213 return err 214 } 215 216 if *zip { 217 err = uzip.ToZip(dir, *file) 218 } else { 219 c := exec.Command("mksquashfs", dir, *file, "-noappend") 220 o, cerr := c.CombinedOutput() 221 v("%v", string(o)) 222 if cerr != nil { 223 err = fmt.Errorf("%v: %v: %v", c.Args, string(o), cerr) 224 } 225 } 226 227 if err == nil { 228 v("Done, your pox is in %v", *file) 229 } 230 231 return err 232 } 233 234 func poxRun(args ...string) error { 235 if len(args) == 0 { 236 return fmt.Errorf(usage) 237 } 238 dir, err := ioutil.TempDir("", "pox") 239 if err != nil { 240 return err 241 } 242 if !*debug { 243 defer os.RemoveAll(dir) 244 } 245 246 if *zip { 247 if err := uzip.FromZip(*file, dir); err != nil { 248 return err 249 } 250 } else { 251 lo, err := loop.New(*file, "squashfs", "") 252 if err != nil { 253 return err 254 } 255 defer lo.Free() //nolint:errcheck 256 257 mountPoint, err := lo.Mount(dir, 0) 258 if err != nil { 259 return err 260 } 261 defer mountPoint.Unmount(0) //nolint:errcheck 262 } 263 for _, m := range chrootMounts { 264 v("mount(%q, %q, %q, %q, %#x)", m.source, filepath.Join(dir, m.target), m.fstype, m.data, m.flags) 265 mp, err := mount.Mount(m.source, filepath.Join(dir, m.target), m.fstype, m.data, m.flags) 266 if err != nil { 267 return err 268 } 269 defer mp.Unmount(0) //nolint:errcheck 270 } 271 272 // If you pass Command a path with no slashes, it'll use PATH from the 273 // parent to resolve the path to exec. Once we chroot, whatever path we 274 // picked is undoubtably wrong. Let's help them out: if they give us a 275 // program with no /, let's look in /bin/. If they want the root of the 276 // chroot, they can use "./" 277 if filepath.Base(args[0]) == args[0] { 278 args[0] = filepath.Join(string(os.PathSeparator), "bin", args[0]) 279 } 280 c := exec.Command(args[0], args[1:]...) 281 c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr 282 c.SysProcAttr = &syscall.SysProcAttr{ 283 Chroot: dir, 284 } 285 c.Env = append(os.Environ(), "PWD=.") 286 287 if err = c.Run(); err != nil { 288 v("pox command exited with: %v", err) 289 } 290 291 return nil 292 } 293 294 func extraMounts() error { 295 if *extra == "" { 296 return nil 297 } 298 v("Extra: %q", *extra) 299 // We have to specify the extra directories and do the create here b/c it is a squashfs. Sorry. 300 for _, e := range strings.Split(*extra, ",") { 301 m := mp{flags: mount.MS_BIND, perm: 0755} 302 mp := strings.Split(e, ":") 303 switch len(mp) { 304 case 1: 305 m.source, m.target = mp[0], mp[0] 306 case 2: 307 m.source, m.target = mp[0], mp[1] 308 default: 309 return fmt.Errorf("-extra: argument (%v) is not in the form src:target", mp) 310 } 311 v("Extra: append %q to chrootMounts", m) 312 chrootMounts = append(chrootMounts, m) 313 } 314 return nil 315 } 316 317 func pox() error { 318 flag.Parse() 319 if *debug { 320 v = log.Printf 321 } 322 if err := extraMounts(); err != nil { 323 return err 324 } 325 if !*create && !*run { 326 return fmt.Errorf(usage) 327 } 328 if *create { 329 if err := poxCreate(flag.Args()...); err != nil { 330 return err 331 } 332 } 333 if *run { 334 return poxRun(flag.Args()...) 335 } 336 return nil 337 } 338 339 func main() { 340 if err := pox(); err != nil { 341 log.Fatal(err) 342 } 343 }