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  }