mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-11-03 20:43:42 +00:00
Chown clears suid bits even for root on Linux. Also move a few functions to x/sys/unix from syscall, to be more arm64 friendly. Signed-off-by: Justin Cormack <justin.cormack@docker.com>
228 lines
5.8 KiB
Go
228 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// pivot_root, which runc uses normally will not work on a ramfs or tmpfs root filesystem
|
|
// You can work around this by forcing it to use move, but it is more convenient to create a
|
|
// new tmpfs root without this issue. Try to do this using as little RAM as possible by moving
|
|
// files one by one. Equivalent to a more memory efficient version of
|
|
// /bin/cp -a / /mnt 2>/dev/null
|
|
// exec /bin/busybox switch_root /mnt /sbin/init
|
|
|
|
// copies ownership and timestamps; assume you created with the right mode
|
|
func copyMetadata(info os.FileInfo, path string) error {
|
|
// would rather use fd than path but Go makes this very difficult at present
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if err := unix.Lchown(path, int(stat.Uid), int(stat.Gid)); err != nil {
|
|
return err
|
|
}
|
|
timespec := []unix.Timespec{unix.Timespec(stat.Atim), unix.Timespec(stat.Mtim)}
|
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, path, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
|
return err
|
|
}
|
|
// after chown suid bits may be dropped; re-set on non symlink files
|
|
if info.Mode()&os.ModeSymlink == 0 {
|
|
if err := os.Chmod(path, info.Mode()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func copyFS(newRoot string) error {
|
|
// find the device of the root filesystem so we can avoid changing filesystem
|
|
info, err := os.Stat("/")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
rootDev := stat.Dev
|
|
|
|
if err = unix.Mount("rootfs", newRoot, "tmpfs", 0, ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
// copy directory tree first
|
|
if err := filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// skip non directories
|
|
if !info.Mode().IsDir() {
|
|
return nil
|
|
}
|
|
dest := filepath.Join(newRoot, path)
|
|
// create the directory
|
|
if path == "/" {
|
|
// the mountpoint already exists but may have wrong mode, metadata
|
|
if err := os.Chmod(newRoot, info.Mode()); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := os.Mkdir(dest, info.Mode()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := copyMetadata(info, dest); err != nil {
|
|
return err
|
|
}
|
|
// skip recurse into other filesystems
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if rootDev != stat.Dev {
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
buf := make([]byte, 32768)
|
|
|
|
// now move files
|
|
if err := filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// skip other filesystems
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if rootDev != stat.Dev && info.Mode().IsDir() {
|
|
return filepath.SkipDir
|
|
}
|
|
dest := filepath.Join(newRoot, path)
|
|
switch {
|
|
case info.Mode().IsDir():
|
|
// already done the directories
|
|
return nil
|
|
case info.Mode().IsRegular():
|
|
// TODO support hard links (currently not handled well in initramfs)
|
|
new, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, info.Mode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
old, err := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.CopyBuffer(new, old, buf); err != nil {
|
|
return err
|
|
}
|
|
if err := old.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := new.Close(); err != nil {
|
|
return err
|
|
}
|
|
// it is ok if we do not remove all files now
|
|
_ = os.Remove(path)
|
|
case (info.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
|
link, err := os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.Symlink(link, dest); err != nil {
|
|
return err
|
|
}
|
|
case (info.Mode() & os.ModeDevice) == os.ModeDevice:
|
|
if err := unix.Mknod(dest, uint32(info.Mode()), int(stat.Rdev)); err != nil {
|
|
return err
|
|
}
|
|
case (info.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe:
|
|
// TODO support named pipes, although no real use case
|
|
return errors.New("Unsupported named pipe on rootfs")
|
|
case (info.Mode() & os.ModeSocket) == os.ModeSocket:
|
|
// TODO support sockets, although no real use case
|
|
return errors.New("Unsupported socket on rootfs")
|
|
default:
|
|
return errors.New("Unknown file type")
|
|
}
|
|
if err := copyMetadata(info, dest); err != nil {
|
|
return err
|
|
}
|
|
// TODO copy extended attributes if needed
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// chdir to the new root directory
|
|
if err := os.Chdir(newRoot); err != nil {
|
|
return err
|
|
}
|
|
// delete remaining directories in /
|
|
if err := filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// ignore root itself
|
|
if path == "/" {
|
|
return nil
|
|
}
|
|
switch {
|
|
case info.Mode().IsDir():
|
|
// skip other filesystems (ie newRoot)
|
|
stat := info.Sys().(*syscall.Stat_t)
|
|
if rootDev != stat.Dev {
|
|
return filepath.SkipDir
|
|
}
|
|
// do our best to delete
|
|
_ = os.RemoveAll(path)
|
|
return filepath.SkipDir
|
|
default:
|
|
// should not be any left now
|
|
_ = os.Remove(path)
|
|
return nil
|
|
}
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// mount --move cwd (/mnt) to /
|
|
if err := unix.Mount(".", "/", "", unix.MS_MOVE, ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
// chroot to .
|
|
if err := unix.Chroot("."); err != nil {
|
|
return err
|
|
}
|
|
|
|
// chdir to "/" to fix up . and ..
|
|
if err := os.Chdir("/"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
// test if we want to do this, ie if tmpfs or ramfs
|
|
// we could be booting off ISO, disk where we do not need this
|
|
var sfs unix.Statfs_t
|
|
if err := unix.Statfs("/", &sfs); err != nil {
|
|
log.Fatalf("Cannot statfs /: %v", err)
|
|
}
|
|
const ramfsMagic = 0x858458f6
|
|
const tmpfsMagic = 0x01021994
|
|
if sfs.Type == ramfsMagic || sfs.Type == tmpfsMagic {
|
|
const newRoot = "/mnt"
|
|
|
|
if err := copyFS(newRoot); err != nil {
|
|
log.Fatalf("Copy root failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// exec /sbin/init
|
|
if err := syscall.Exec("/sbin/init", []string{"/sbin/init"}, os.Environ()); err != nil {
|
|
log.Fatalf("Cannot exec /sbin/init")
|
|
}
|
|
}
|