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  }