package chrootarchive import ( "fmt" "io/ioutil" "os" "path/filepath" "syscall" ) // chroot on linux uses pivot_root instead of chroot // pivot_root takes a new root and an old root. // Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root. // New root is where the new rootfs is set to. // Old root is removed after the call to pivot_root so it is no longer available under the new root. // This is similar to how libcontainer sets up a container's rootfs func chroot(path string) (err error) { // Create new mount namespace so mounts don't leak if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil { return fmt.Errorf("Error creating mount namespace before pivot: %v", err) } // path must be a different fs for pivot_root, so bind-mount to itself to ensure this if err := syscall.Mount(path, path, "", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("Error mounting pivot dir before pivot: %v", err) } // setup oldRoot for pivot_root pivotDir, err := ioutil.TempDir(path, ".pivot_root") if err != nil { return fmt.Errorf("Error setting up pivot dir: %v", err) } var mounted bool defer func() { if mounted { // make sure pivotDir is not mounted before we try to remove it if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil { if err == nil { err = errCleanup } return } } errCleanup := os.Remove(pivotDir) if errCleanup != nil { errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup) if err == nil { err = errCleanup } } }() if err := syscall.PivotRoot(path, pivotDir); err != nil { // If pivot fails, fall back to the normal chroot return realChroot(path) } mounted = true // This is the new path for where the old root (prior to the pivot) has been moved to // This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction pivotDir = filepath.Join("/", filepath.Base(pivotDir)) if err := syscall.Chdir("/"); err != nil { return fmt.Errorf("Error changing to new root: %v", err) } // Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil { return fmt.Errorf("Error making old root private after pivot: %v", err) } // Now unmount the old root so it's no longer visible from the new root if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { return fmt.Errorf("Error while unmounting old root after pivot: %v", err) } mounted = false return nil } func realChroot(path string) error { if err := syscall.Chroot(path); err != nil { return fmt.Errorf("Error after fallback to chroot: %v", err) } if err := syscall.Chdir("/"); err != nil { return fmt.Errorf("Error chaning to new root after chroot: %v", err) } return nil }