Skip to content

Commit 781fdb8

Browse files
committed
chroot: on Linux, try to pivot_root before falling back to chroot
Unless --no-pivot or the equivalent API flag is set, try to pivot_root() to enter the rootfs during Run(). Fall back to using chroot() as before if that fails for any reason. Signed-off-by: Nalin Dahyabhai <[email protected]>
1 parent 79bb8ab commit 781fdb8

File tree

6 files changed

+76
-11
lines changed

6 files changed

+76
-11
lines changed

chroot/run_common.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ func init() {
4848
type runUsingChrootExecSubprocOptions struct {
4949
Spec *specs.Spec
5050
BundlePath string
51+
NoPivot bool
5152
}
5253

5354
// RunUsingChroot runs a chrooted process, using some of the settings from the
5455
// passed-in spec, and using the specified bundlePath to hold temporary files,
5556
// directories, and mountpoints.
56-
func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer) (err error) {
57+
func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reader, stdout, stderr io.Writer, noPivot bool) (err error) {
5758
var confwg sync.WaitGroup
5859
var homeFound bool
5960
for _, env := range spec.Process.Env {
@@ -97,6 +98,7 @@ func RunUsingChroot(spec *specs.Spec, bundlePath, homeDir string, stdin io.Reade
9798
config, conferr := json.Marshal(runUsingChrootSubprocOptions{
9899
Spec: spec,
99100
BundlePath: bundlePath,
101+
NoPivot: noPivot,
100102
})
101103
if conferr != nil {
102104
return fmt.Errorf("encoding configuration for %q: %w", runUsingChrootCommand, conferr)
@@ -196,6 +198,7 @@ func runUsingChrootMain() {
196198
fmt.Fprintf(os.Stderr, "invalid options spec in runUsingChrootMain\n")
197199
os.Exit(1)
198200
}
201+
noPivot := options.NoPivot
199202

200203
// Prepare to shuttle stdio back and forth.
201204
rootUID32, rootGID32, err := util.GetHostRootIDs(options.Spec)
@@ -442,7 +445,7 @@ func runUsingChrootMain() {
442445
}()
443446

444447
// Set up mounts and namespaces, and run the parent subprocess.
445-
status, err := runUsingChroot(options.Spec, options.BundlePath, ctty, stdin, stdout, stderr, closeOnceRunning)
448+
status, err := runUsingChroot(options.Spec, options.BundlePath, ctty, stdin, stdout, stderr, noPivot, closeOnceRunning)
446449
if err != nil {
447450
fmt.Fprintf(os.Stderr, "error running subprocess: %v\n", err)
448451
os.Exit(1)
@@ -463,7 +466,7 @@ func runUsingChrootMain() {
463466
// runUsingChroot, still in the grandparent process, sets up various bind
464467
// mounts and then runs the parent process in its own user namespace with the
465468
// necessary ID mappings.
466-
func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io.Reader, stdout, stderr io.Writer, closeOnceRunning []*os.File) (wstatus unix.WaitStatus, err error) {
469+
func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io.Reader, stdout, stderr io.Writer, noPivot bool, closeOnceRunning []*os.File) (wstatus unix.WaitStatus, err error) {
467470
var confwg sync.WaitGroup
468471

469472
// Create a new mount namespace for ourselves and bind mount everything to a new location.
@@ -496,6 +499,7 @@ func runUsingChroot(spec *specs.Spec, bundlePath string, ctty *os.File, stdin io
496499
config, conferr := json.Marshal(runUsingChrootExecSubprocOptions{
497500
Spec: spec,
498501
BundlePath: bundlePath,
502+
NoPivot: noPivot,
499503
})
500504
if conferr != nil {
501505
fmt.Fprintf(os.Stderr, "error re-encoding configuration for %q\n", runUsingChrootExecCommand)
@@ -619,8 +623,10 @@ func runUsingChrootExecMain() {
619623
// Try to chroot into the root. Do this before we potentially
620624
// block the syscall via the seccomp profile. Allow the
621625
// platform to override this - on FreeBSD, we use a simple
622-
// jail to set the hostname in the container
626+
// jail to set the hostname in the container, and on Linux
627+
// we attempt to pivot_root.
623628
if err := createPlatformContainer(options); err != nil {
629+
logrus.Debugf("createPlatformContainer: %v", err)
624630
var oldst, newst unix.Stat_t
625631
if err := unix.Stat(options.Spec.Root.Path, &oldst); err != nil {
626632
fmt.Fprintf(os.Stderr, "error stat()ing intended root directory %q: %v\n", options.Spec.Root.Path, err)

chroot/run_freebsd.go

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var (
4141
type runUsingChrootSubprocOptions struct {
4242
Spec *specs.Spec
4343
BundlePath string
44+
NoPivot bool
4445
}
4546

4647
func setPlatformUnshareOptions(spec *specs.Spec, cmd *unshare.Cmd) error {

chroot/run_linux.go

+52-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var (
4747
type runUsingChrootSubprocOptions struct {
4848
Spec *specs.Spec
4949
BundlePath string
50+
NoPivot bool
5051
UIDMappings []syscall.SysProcIDMap
5152
GIDMappings []syscall.SysProcIDMap
5253
}
@@ -224,8 +225,57 @@ func makeRlimit(limit specs.POSIXRlimit) unix.Rlimit {
224225
return unix.Rlimit{Cur: limit.Soft, Max: limit.Hard}
225226
}
226227

227-
func createPlatformContainer(_ runUsingChrootExecSubprocOptions) error {
228-
return errors.New("unsupported createPlatformContainer")
228+
func createPlatformContainer(options runUsingChrootExecSubprocOptions) error {
229+
if options.NoPivot {
230+
return errors.New("not using pivot_root()")
231+
}
232+
// borrowing a technique from runc, who credit the LXC maintainers for this
233+
// open descriptors for the old and new root directories so that we can use fchdir()
234+
oldRootFd, err := unix.Open("/", unix.O_DIRECTORY, 0)
235+
if err != nil {
236+
return fmt.Errorf("opening host root directory: %w", err)
237+
}
238+
defer func() {
239+
if err := unix.Close(oldRootFd); err != nil {
240+
logrus.Warnf("closing host root directory: %v", err)
241+
}
242+
}()
243+
newRootFd, err := unix.Open(options.Spec.Root.Path, unix.O_DIRECTORY, 0)
244+
if err != nil {
245+
return fmt.Errorf("opening container root directory: %w", err)
246+
}
247+
defer func() {
248+
if err := unix.Close(newRootFd); err != nil {
249+
logrus.Warnf("closing container root directory: %v", err)
250+
}
251+
}()
252+
// change to the new root directory
253+
if err := unix.Fchdir(newRootFd); err != nil {
254+
return fmt.Errorf("changing to container root directory: %w", err)
255+
}
256+
// this makes the current directory the root directory. not actually
257+
// sure what happens to the other one
258+
if err := unix.PivotRoot(".", "."); err != nil {
259+
return fmt.Errorf("pivot_root: %w", err)
260+
}
261+
// go back and clean up the old one
262+
if err := unix.Fchdir(oldRootFd); err != nil {
263+
return fmt.Errorf("changing to host root directory: %w", err)
264+
}
265+
// make sure we only unmount things under this tree
266+
if err := unix.Mount(".", ".", "bind", unix.MS_BIND|unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
267+
return fmt.Errorf("tweaking mount flags on host root directory before unmounting from mount namespace: %w", err)
268+
}
269+
// detach this (unnamed?) old directory
270+
if err := unix.Unmount(".", unix.MNT_DETACH); err != nil {
271+
return fmt.Errorf("unmounting host root directory in mount namespace: %w", err)
272+
}
273+
// go back to a named root directory
274+
if err := unix.Fchdir(newRootFd); err != nil {
275+
return fmt.Errorf("changing to container root directory at last: %w", err)
276+
}
277+
logrus.Debugf("pivot_root()ed into %q", options.Spec.Root.Path)
278+
return nil
229279
}
230280

231281
func mountFlagsForFSFlags(fsFlags uintptr) uintptr {

chroot/run_test.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
3636
os.Exit(m.Run())
3737
}
3838

39-
func testMinimal(t *testing.T, modify func(g *generate.Generator, rootDir, bundleDir string), verify func(t *testing.T, report *types.TestReport)) {
39+
func testMinimalWithPivot(t *testing.T, noPivot bool, modify func(g *generate.Generator, rootDir, bundleDir string), verify func(t *testing.T, report *types.TestReport)) {
4040
t.Helper()
4141
g, err := generate.New("linux")
4242
if err != nil {
@@ -100,8 +100,8 @@ func testMinimal(t *testing.T, modify func(g *generate.Generator, rootDir, bundl
100100
}
101101

102102
output := new(bytes.Buffer)
103-
if err := RunUsingChroot(g.Config, bundleDir, "/", new(bytes.Buffer), output, output); err != nil {
104-
t.Fatalf("run: %v: %s", err, output.String())
103+
if err := RunUsingChroot(g.Config, bundleDir, "/", new(bytes.Buffer), output, output, noPivot); err != nil {
104+
t.Fatalf("run(noPivot=false): %v: %s", err, output.String())
105105
}
106106

107107
var report types.TestReport
@@ -114,6 +114,14 @@ func testMinimal(t *testing.T, modify func(g *generate.Generator, rootDir, bundl
114114
}
115115
}
116116

117+
func testMinimal(t *testing.T, modify func(g *generate.Generator, rootDir, bundleDir string), verify func(t *testing.T, report *types.TestReport)) {
118+
for _, noPivot := range []bool{false, true} {
119+
t.Run(fmt.Sprintf("noPivot=%v", noPivot), func(t *testing.T) {
120+
testMinimalWithPivot(t, noPivot, modify, verify)
121+
})
122+
}
123+
}
124+
117125
func TestNoop(t *testing.T) {
118126
if unix.Getuid() != 0 {
119127
t.Skip("tests need to be run as root")

run_freebsd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
328328
}
329329
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, networkString, moreCreateArgs, spec, mountPoint, path, containerName, b.Container, hostsFile, resolvFile)
330330
case IsolationChroot:
331-
err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr)
331+
err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr, options.NoPivot)
332332
default:
333333
err = errors.New("don't know how to run this command")
334334
}

run_linux.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ rootless=%d
531531
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, networkString, moreCreateArgs, spec,
532532
mountPoint, path, define.Package+"-"+filepath.Base(path), b.Container, hostsFile, resolvFile)
533533
case IsolationChroot:
534-
err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr)
534+
err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr, options.NoPivot)
535535
case IsolationOCIRootless:
536536
moreCreateArgs := []string{"--no-new-keyring"}
537537
if options.NoPivot {

0 commit comments

Comments
 (0)