mirror of https://github.com/xSmurf/oz.git
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							356 lines
						
					
					
						
							8.3 KiB
						
					
					
				
			
		
		
	
	
							356 lines
						
					
					
						
							8.3 KiB
						
					
					
				| package fs
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"github.com/op/go-logging"
 | |
| 	"github.com/subgraph/oz"
 | |
| 	"os/user"
 | |
| 	"path/filepath"
 | |
| )
 | |
| 
 | |
| type Filesystem struct {
 | |
| 	log    *logging.Logger
 | |
| 	base   string
 | |
| 	chroot bool
 | |
| }
 | |
| 
 | |
| func NewFilesystem(config *oz.Config, log *logging.Logger) *Filesystem {
 | |
| 	if log == nil {
 | |
| 		log = logging.MustGetLogger("oz")
 | |
| 	}
 | |
| 	return &Filesystem{
 | |
| 		base: config.SandboxPath,
 | |
| 		log:  log,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) Root() string {
 | |
| 	return path.Join(fs.base, "rootfs")
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) CreateEmptyDir(target string) error {
 | |
| 	fi, err := os.Stat(target)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if !fs.chroot {
 | |
| 		target = path.Join(fs.Root(), target)
 | |
| 	}
 | |
| 	if err := os.MkdirAll(target, fi.Mode().Perm()); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return copyFileInfo(fi, target)
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) CreateDevice(devpath string, dev int, mode, perm uint32) error {
 | |
| 	p := path.Join(fs.Root(), devpath)
 | |
| 	if err := syscall.Mknod(p, mode, dev); err != nil {
 | |
| 		return fmt.Errorf("failed to mknod device '%s': %v", p, err)
 | |
| 	}
 | |
| 	if err := os.Chmod(p, os.FileMode(perm)); err != nil {
 | |
| 		return fmt.Errorf("unable to set file permissions on device '%s': %v", p, err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) CreateSymlink(oldpath, newpath string) error {
 | |
| 	if !fs.chroot {
 | |
| 		newpath = path.Join(fs.Root(), newpath)
 | |
| 	}
 | |
| 	if err := syscall.Symlink(oldpath, newpath); err != nil {
 | |
| 		return fmt.Errorf("failed to symlink %s to %s: %v", newpath, oldpath, err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) BindPath(path, target string, readonly bool) error {
 | |
| 	return fs.BindOrCreate(path, target, readonly, nil)
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) BindOrCreate(p, target string, readonly bool, u *user.User) error {
 | |
| 	src, err := filepath.EvalSymlinks(p)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error resolving symlinks for path (%s): %v", p, err)
 | |
| 	}
 | |
| 	sinfo, err := readSourceInfo(src, u)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to bind path (%s): %v", src, err)
 | |
| 	}
 | |
| 
 | |
| 	target = path.Join(fs.Root(), target)
 | |
| 
 | |
| 	_, err = os.Stat(target)
 | |
| 	if err == nil || !os.IsNotExist(err) {
 | |
| 		fs.log.Warning("Target (%s > %s) already exists, ignoring", src, target)
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if sinfo.IsDir() {
 | |
| 		if err := os.MkdirAll(target, sinfo.Mode().Perm()); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	} else {
 | |
| 		if err := createEmptyFile(target, 0750); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err := copyPathPermissions(fs.Root(), src); err != nil {
 | |
| 		return fmt.Errorf("failed to copy path permissions for (%s): %v", src, err)
 | |
| 	}
 | |
| 	fs.log.Info("bind mounting %s -> %s", src, target)
 | |
| 	flags := syscall.MS_NOSUID | syscall.MS_NODEV
 | |
| 	if readonly {
 | |
| 		flags |= syscall.MS_RDONLY
 | |
| 	} else {
 | |
| 		flags |= syscall.MS_NOEXEC
 | |
| 	}
 | |
| 	return bindMount(src, target, flags)
 | |
| }
 | |
| 
 | |
| func readSourceInfo(src string, u *user.User) (os.FileInfo, error) {
 | |
| 	if fi, err := os.Stat(src); err == nil {
 | |
| 		return fi, nil
 | |
| 	} else if !os.IsNotExist(err) {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if u == nil {
 | |
| 		return nil, fmt.Errorf("source path (%s) does not exist", src)
 | |
| 	}
 | |
| 
 | |
| 	home := u.HomeDir
 | |
| 	if !strings.HasPrefix(src, home) {
 | |
| 		return nil, fmt.Errorf("mount item (%s) has flag MountCreateIfAbsent, but is not child of home directory (%s)", src, home)
 | |
| 	}
 | |
| 
 | |
| 	if err := os.MkdirAll(src, 0750); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	pinfo, err := os.Stat(path.Dir(src))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if err := copyFileInfo(pinfo, src); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return os.Stat(src)
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) BlacklistPath(target string) error {
 | |
| 	t, err := filepath.EvalSymlinks(target)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("symlink evaluation failed while blacklisting path %s: %v", target, err)
 | |
| 	}
 | |
| 	fi, err := os.Stat(t)
 | |
| 	if err != nil {
 | |
| 		if os.IsNotExist(err) {
 | |
| 			fs.log.Info("Blacklist path (%s) does not exist", t)
 | |
| 			return nil
 | |
| 		}
 | |
| 		return err
 | |
| 	}
 | |
| 	src := emptyFilePath
 | |
| 	if fi.IsDir() {
 | |
| 		src = emptyDirPath
 | |
| 	}
 | |
| 	if !fs.chroot {
 | |
| 		src = path.Join(fs.Root(), src)
 | |
| 		t = path.Join(fs.Root(), t)
 | |
| 	}
 | |
| 	if err := syscall.Mount(src, t, "", syscall.MS_BIND, "mode=400,gid=0"); err != nil {
 | |
| 		return fmt.Errorf("failed to bind %s -> %s for blacklist: %v", src, t, err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) Chroot() error {
 | |
| 	if fs.chroot {
 | |
| 		return fmt.Errorf("filesystem is already in chroot()")
 | |
| 	}
 | |
| 	fs.log.Debug("chroot to %s", fs.Root())
 | |
| 	if err := syscall.Chroot(fs.Root()); err != nil {
 | |
| 		return fmt.Errorf("chroot to %s failed: %v", fs.Root(), err)
 | |
| 	}
 | |
| 	if err := os.Chdir("/"); err != nil {
 | |
| 		return fmt.Errorf("chdir to / after chroot() failed: %v", err)
 | |
| 	}
 | |
| 	fs.chroot = true
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) MountProc() error {
 | |
| 	err := fs.mountSpecial("/proc", "proc", 0, "")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	roMounts := []string{
 | |
| 		"sysrq-trigger",
 | |
| 		"bus",
 | |
| 		"irq",
 | |
| 		"sys/kernel/hotplug",
 | |
| 	}
 | |
| 	for _, rom := range roMounts {
 | |
| 		if _, err := os.Stat(rom); err == nil {
 | |
| 			if err := bindMount(rom, rom, syscall.MS_RDONLY); err != nil {
 | |
| 				return fmt.Errorf("remount RO of %s failed: %v", rom, err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) MountFullDev() error {
 | |
| 	return fs.mountSpecial("/dev", "devtmpfs", 0, "")
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) MountSys() error {
 | |
| 	return fs.mountSpecial("/sys", "sysfs", syscall.MS_RDONLY, "")
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) MountTmp() error {
 | |
| 	return fs.mountSpecial("/tmp", "tmpfs", syscall.MS_NODEV, "")
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) MountPts() error {
 | |
| 	return fs.mountSpecial("/dev/pts", "devpts", 0, "newinstance,ptmxmode=0666")
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) MountShm() error {
 | |
| 	return fs.mountSpecial("/dev/shm", "tmpfs", syscall.MS_NODEV, "")
 | |
| }
 | |
| 
 | |
| func (fs *Filesystem) mountSpecial(path, mtype string, flags int, args string) error {
 | |
| 	if !fs.chroot {
 | |
| 		return fmt.Errorf("cannot mount %s (%s) until Chroot() is called.", path, mtype)
 | |
| 	}
 | |
| 	fs.log.Debug("Mounting %s [%s]", path, mtype)
 | |
| 	if err := os.MkdirAll(path, 0755); err != nil {
 | |
| 		return fmt.Errorf("failed to create mount point (%s): %v", path, err)
 | |
| 	}
 | |
| 	mountFlags := uintptr(flags | syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_REC)
 | |
| 	return syscall.Mount("", path, mtype, mountFlags, args)
 | |
| }
 | |
| 
 | |
| func bindMount(source, target string, flags int) error {
 | |
| 	if err := syscall.Mount(source, target, "", syscall.MS_BIND, ""); err != nil {
 | |
| 		return fmt.Errorf("bind mount of %s -> %s failed: %v", source, target, err)
 | |
| 	}
 | |
| 	if flags != 0 {
 | |
| 		return remount(target, flags)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func remount(target string, flags int) error {
 | |
| 	fl := uintptr(flags | syscall.MS_BIND | syscall.MS_REMOUNT)
 | |
| 	if err := syscall.Mount("", target, "", fl, ""); err != nil {
 | |
| 		return fmt.Errorf("failed to remount %s with flags %x: %v", target, flags, err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| const emptyFilePath = "/oz.ro.file"
 | |
| const emptyDirPath = "/oz.ro.dir"
 | |
| 
 | |
| func (fs *Filesystem) CreateBlacklistPaths() error {
 | |
| 	p := emptyDirPath
 | |
| 	if !fs.chroot {
 | |
| 		p = path.Join(fs.Root(), emptyDirPath)
 | |
| 	}
 | |
| 	if err := createBlacklistDir(p); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	p = emptyFilePath
 | |
| 	if !fs.chroot {
 | |
| 		p = path.Join(fs.Root(), emptyFilePath)
 | |
| 	}
 | |
| 	if err := createBlacklistFile(p); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createBlacklistDir(p string) error {
 | |
| 	if err := os.MkdirAll(p, 0000); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return setBlacklistPerms(p, 0500)
 | |
| }
 | |
| 
 | |
| func createBlacklistFile(path string) error {
 | |
| 	fd, err := os.Create(path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := fd.Close(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return setBlacklistPerms(path, 0400)
 | |
| }
 | |
| 
 | |
| func setBlacklistPerms(path string, mode os.FileMode) error {
 | |
| 	if err := os.Chown(path, 0, 0); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := os.Chmod(path, mode); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createEmptyFile(name string, mode os.FileMode) error {
 | |
| 	if err := os.MkdirAll(path.Dir(name), 0750); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	fd, err := os.Create(name)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := fd.Close(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := os.Chmod(name, mode); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func copyPathPermissions(root, src string) error {
 | |
| 	current := "/"
 | |
| 	for _, part := range strings.Split(src, "/") {
 | |
| 		if part == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		current = path.Join(current, part)
 | |
| 		target := path.Join(root, current)
 | |
| 		if err := copyFilePermissions(current, target); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func copyFilePermissions(src, target string) error {
 | |
| 	fi, err := os.Stat(src)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return copyFileInfo(fi, target)
 | |
| }
 | |
| 
 | |
| func copyFileInfo(info os.FileInfo, target string) error {
 | |
| 	st := info.Sys().(*syscall.Stat_t)
 | |
| 	os.Chown(target, int(st.Uid), int(st.Gid))
 | |
| 	os.Chmod(target, info.Mode().Perm())
 | |
| 	return nil
 | |
| }
 |