Browse code

Use pivot_root instead of chroot for chrootarchive

This fixes one issue with Docker running under a grsec kernel, which
denies chmod and mknod under chroot.

Note, if pivot_root fails it will still fallback to chroot.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2016/05/05 02:32:51
Showing 3 changed files
... ...
@@ -11,19 +11,11 @@ import (
11 11
 	"io/ioutil"
12 12
 	"os"
13 13
 	"runtime"
14
-	"syscall"
15 14
 
16 15
 	"github.com/docker/docker/pkg/archive"
17 16
 	"github.com/docker/docker/pkg/reexec"
18 17
 )
19 18
 
20
-func chroot(path string) error {
21
-	if err := syscall.Chroot(path); err != nil {
22
-		return err
23
-	}
24
-	return syscall.Chdir("/")
25
-}
26
-
27 19
 // untar is the entry-point for docker-untar on re-exec. This is not used on
28 20
 // Windows as it does not support chroot, hence no point sandboxing through
29 21
 // chroot and rexec.
30 22
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package chrootarchive
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"os"
6
+	"path/filepath"
7
+	"syscall"
8
+)
9
+
10
+// chroot on linux uses pivot_root instead of chroot
11
+// pivot_root takes a new root and an old root.
12
+// Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root.
13
+// New root is where the new rootfs is set to.
14
+// Old root is removed after the call to pivot_root so it is no longer available under the new root.
15
+// This is similar to how libcontainer sets up a container's rootfs
16
+func chroot(path string) (err error) {
17
+	// Create new mount namespace so mounts don't leak
18
+	if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
19
+		return fmt.Errorf("Error creating mount namespace before pivot: %v", err)
20
+	}
21
+	// path must be a different fs for pivot_root, so bind-mount to itself to ensure this
22
+	if err := syscall.Mount(path, path, "", syscall.MS_BIND, ""); err != nil {
23
+		return fmt.Errorf("Error mounting pivot dir before pivot: %v", err)
24
+	}
25
+
26
+	// setup oldRoot for pivot_root
27
+	pivotDir, err := ioutil.TempDir(path, ".pivot_root")
28
+	if err != nil {
29
+		return fmt.Errorf("Error setting up pivot dir: %v", err)
30
+	}
31
+
32
+	var mounted bool
33
+	defer func() {
34
+		if mounted {
35
+			// make sure pivotDir is not mounted before we try to remove it
36
+			if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil {
37
+				if err == nil {
38
+					err = errCleanup
39
+				}
40
+				return
41
+			}
42
+		}
43
+
44
+		errCleanup := os.Remove(pivotDir)
45
+		if errCleanup != nil {
46
+			errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
47
+			if err == nil {
48
+				err = errCleanup
49
+			}
50
+		}
51
+	}()
52
+
53
+	if err := syscall.PivotRoot(path, pivotDir); err != nil {
54
+		// If pivot fails, fall back to the normal chroot
55
+		return realChroot(path)
56
+	}
57
+	mounted = true
58
+
59
+	// This is the new path for where the old root (prior to the pivot) has been moved to
60
+	// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
61
+	pivotDir = filepath.Join("/", filepath.Base(pivotDir))
62
+
63
+	if err := syscall.Chdir("/"); err != nil {
64
+		return fmt.Errorf("Error changing to new root: %v", err)
65
+	}
66
+
67
+	// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
68
+	if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
69
+		return fmt.Errorf("Error making old root private after pivot: %v", err)
70
+	}
71
+
72
+	// Now unmount the old root so it's no longer visible from the new root
73
+	if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
74
+		return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
75
+	}
76
+	mounted = false
77
+
78
+	return nil
79
+}
80
+
81
+func realChroot(path string) error {
82
+	if err := syscall.Chroot(path); err != nil {
83
+		return fmt.Errorf("Error after fallback to chroot: %v", err)
84
+	}
85
+	if err := syscall.Chdir("/"); err != nil {
86
+		return fmt.Errorf("Error chaning to new root after chroot: %v", err)
87
+	}
88
+	return nil
89
+}
0 90
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+// +build !windows,!linux
1
+
2
+package chrootarchive
3
+
4
+import "syscall"
5
+
6
+func chroot(path string) error {
7
+	if err := syscall.Chroot(path); err != nil {
8
+		return err
9
+	}
10
+	return syscall.Chdir("/")
11
+}