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>
| ... | ... |
@@ -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 |
+} |