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