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