diff --git a/fs/cleanup.go b/fs/cleanup.go deleted file mode 100644 index ef377ae..0000000 --- a/fs/cleanup.go +++ /dev/null @@ -1,122 +0,0 @@ -package fs - -import ( - "errors" - "io/ioutil" - "os" - "sort" - "strings" - "syscall" - - // External - "github.com/op/go-logging" -) - -func (fs *Filesystem) Cleanup() error { - if fs.base == "" { - msg := "cannot Cleanup() filesystem, fs.base is empty" - fs.log.Warning(msg) - return errors.New(msg) - } - fs.log.Info("Cleanup() called on filesystem at root %s", fs.root) - - for { - mnts, err := getMountsBelow(fs.base) - if err != nil { - return err - } - if len(mnts) == 0 { - return nil - } - atLeastOne, lastErr := mnts.unmountAll(fs.log) - if !atLeastOne { - return lastErr - } - } -} - -func (mnts mountEntries) unmountAll(log *logging.Logger) (bool, error) { - reterr := error(nil) - atLeastOne := false - for _, m := range mnts { - log.Debug("Unmounting mountpoint: %s", m.dir) - if _, err := os.Stat(m.dir); os.IsNotExist(err) { - continue - } - if err := syscall.Unmount(m.dir, 0); err != nil { - log.Warning("Failed to unmount mountpoint %s: %v", m.dir, err) - reterr = err - } else { - atLeastOne = true - } - } - return atLeastOne, reterr -} - -type mountEntry struct { - src string - dir string - fs string - options string -} - -type mountEntries []*mountEntry - -func (m mountEntries) Len() int { return len(m) } -func (m mountEntries) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (m mountEntries) Less(i, j int) bool { return m[i].depth() > m[j].depth() } - -func (me mountEntry) depth() int { return strings.Count(me.dir, "/") } - -func getMountsBelow(base string) (mountEntries, error) { - mnts, err := getProcMounts() - if err != nil { - return nil, err - } - sort.Sort(mnts) - var filtered mountEntries - for _, m := range mnts { - if strings.HasPrefix(m.dir, base) { - filtered = append(filtered, m) - } - } - return filtered, nil -} - -func (m mountEntries) contains(dir string) bool { - for _, mnt := range m { - if dir == mnt.dir { - return true - } - } - return false -} - -func getProcMounts() (mountEntries, error) { - lines, err := readProcMounts() - if err != nil { - return nil, err - } - var entries mountEntries - for _, line := range lines { - parts := strings.Fields(line) - if len(parts) >= 4 { - entries = append(entries, &mountEntry{ - src: parts[0], - dir: parts[1], - fs: parts[2], - options: parts[3], - }) - } - - } - return entries, nil -} - -func readProcMounts() ([]string, error) { - content, err := ioutil.ReadFile("/proc/mounts") - if err != nil { - return nil, err - } - return strings.Split(string(content), "\n"), nil -} diff --git a/fs/fs.go b/fs/fs.go index 45e70c4..9a299f2 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -3,186 +3,306 @@ package fs import ( "fmt" "os" - "os/user" "path" "strings" "syscall" "github.com/op/go-logging" "github.com/subgraph/oz" + "os/user" + "path/filepath" ) -type directory struct { - path string - empty bool -} - type Filesystem struct { - log *logging.Logger - user *user.User - name string - base string - root string - xpra string - noDefaults bool - noSysAndProc bool - fullDevices bool - whitelist []*mountItem - blacklist []*mountItem + log *logging.Logger + base string + chroot bool } -func (fs *Filesystem) Root() string { - return fs.root +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) Xpra() string { - return fs.xpra +func (fs *Filesystem) Root() string { + return path.Join(fs.base, "rootfs") } -func (fs *Filesystem) AddBindWhitelist(path, target string, readonly bool) error { - for _, fsitem := range fs.whitelist { - if fsitem.path == path { - return nil - } - } - item, err := fs.newItem(path, target, readonly) +func (fs *Filesystem) CreateEmptyDir(target string) error { + fi, err := os.Stat(target) if err != nil { return err } - fs.whitelist = append(fs.whitelist, item) - return item.bindItem() + 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) addWhitelist(path, target string, readonly bool) error { - item, err := fs.newItem(path, target, readonly) - if err != nil { - return err +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) } - fs.whitelist = append(fs.whitelist, item) return nil } -func (fs *Filesystem) addBlacklist(path string) error { - item, err := fs.newItem(path, "", false) - if err != nil { - return err +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) } - fs.blacklist = append(fs.blacklist, item) return nil } -func (fs *Filesystem) newItem(path, target string, readonly bool) (*mountItem, error) { - p, err := fs.resolveVars(path) +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 } - t, err := fs.resolveVars(target) + + pinfo, err := os.Stat(path.Dir(src)) if err != nil { return nil, err } - return &mountItem{ - path: p, - target: t, - //readonly: readonly, - fs: fs, - }, nil + + 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 NewFromProfile(profile *oz.Profile, user *user.User, basePath string, UseFullDev bool, log *logging.Logger) *Filesystem { - fs := NewFilesystem(profile.Name, user, basePath, log) - for _, wl := range profile.Whitelist { - fs.addWhitelist(wl.Path, wl.Path, wl.ReadOnly) +func (fs *Filesystem) Chroot() error { + if fs.chroot { + return fmt.Errorf("filesystem is already in chroot()") } - for _, bl := range profile.Blacklist { - fs.addBlacklist(bl.Path) + 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) } - fs.noDefaults = profile.NoDefaults - fs.noSysAndProc = profile.NoSysProc - fs.fullDevices = UseFullDev - if profile.XServer.Enabled && user != nil { - fs.xpra = path.Join(user.HomeDir, ".Xoz", profile.Name) + if err := os.Chdir("/"); err != nil { + return fmt.Errorf("chdir to / after chroot() failed: %v", err) } - return fs + fs.chroot = true + return nil } -func NewFilesystem(name string, user *user.User, basePath string, log *logging.Logger) *Filesystem { - fs := new(Filesystem) - fs.log = log - fs.name = name - if log == nil { - fs.log = logging.MustGetLogger("oz") +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", } - fs.base = path.Join(basePath, name) - fs.root = path.Join(fs.base, "rootfs") - fs.user = user + 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 +} - return fs +func (fs *Filesystem) MountFullDev() error { + return fs.mountSpecial("/dev", "devtmpfs", 0, "") } -func (fs *Filesystem) GetHomeDir() (string, error) { - if fs.user == nil { - return "", fmt.Errorf("Home directory not set") +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) } - return fs.user.HomeDir, nil + 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 xcreateEmptyDirectories(base string, paths []string) error { - for _, p := range paths { - target := path.Join(base, p) - if err := createEmptyDir(p, target); err != nil { - return err - } +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 createEmptyDir(source, target string) error { +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 } -func createSubdirs(base string, subdirs []string) error { - for _, sdir := range subdirs { - path := path.Join(base, sdir) - if err := createDirTree(path); err != nil { - return err - } +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 createDirTree(path string) error { - st, err := os.Stat(path) - if err == nil { - if !st.IsDir() { - return fmt.Errorf("cannot create directory %s because path already exists and is not directory", path) - } - return nil +func createBlacklistDir(p string) error { + if err := os.MkdirAll(p, 0000); err != nil { + return err } - if !os.IsNotExist(err) { - return fmt.Errorf("unexpected error attempting Stat() on path %s: %v", path, err) + return setBlacklistPerms(p, 0500) +} + +func createBlacklistFile(path string) error { + fd, err := os.Create(path) + if err != nil { + return err } - if err := os.MkdirAll(path, 0755); err != nil { - return fmt.Errorf("error creating directory tree %s: %v", path, err) + if err := fd.Close(); err != nil { + return err } - return nil + return setBlacklistPerms(path, 0400) } -*/ -// bindMount performs a bind mount of the source path item so that it is visible -// at the target path. By default the mount is flagged MS_NOSUID and MS_NODEV -// but additional flags can be passed in extraFlags. If the readonly flag is -// set the bind mount is remounted as MS_RDONLY. -func bindMount(source, target string, readonly bool, extraFlags uintptr) error { - flags := syscall.MS_BIND | syscall.MS_NOSUID | syscall.MS_NODEV | extraFlags - if err := syscall.Mount(source, target, "", flags, ""); err != nil { - return fmt.Errorf("failed to bind mount %s to %s: %v", source, target, err) +func setBlacklistPerms(path string, mode os.FileMode) error { + if err := os.Chown(path, 0, 0); err != nil { + return err } - if readonly { - flags |= syscall.MS_RDONLY | syscall.MS_REMOUNT - if err := syscall.Mount("", target, "", flags, ""); err != nil { - return fmt.Errorf("failed to remount %s as RDONLY: %v", target, err) - } + if err := os.Chmod(path, mode); err != nil { + return err } return nil } @@ -233,17 +353,3 @@ func copyFileInfo(info os.FileInfo, target string) error { os.Chmod(target, info.Mode().Perm()) return nil } - -func createSubdirs(base string, uid, gid int, mode os.FileMode, subdirs ...string) error { - dir := base - for _, sd := range subdirs { - dir = path.Join(dir, sd) - if err := os.Mkdir(dir, mode); err != nil && !os.IsExist(err) { - return err - } - if err := os.Chown(dir, uid, gid); err != nil { - return err - } - } - return nil -} diff --git a/fs/item.go b/fs/item.go deleted file mode 100644 index 1b42967..0000000 --- a/fs/item.go +++ /dev/null @@ -1,184 +0,0 @@ -package fs - -import ( - "fmt" - "github.com/op/go-logging" - "os" - "path" - "path/filepath" - "strings" - "syscall" -) - -type MountFlag int - -func (mf MountFlag) isSet(f MountFlag) bool { - return mf&f == f -} - -const ( - MountReadOnly MountFlag = 1 << iota - MountCreateIfAbsent -) - -type mountItem struct { - path string - target string - flags MountFlag - fs *Filesystem -} - -func (mi *mountItem) targetPath() string { - root := mi.fs.root - if mi.target != "" { - return path.Join(root, mi.target) - } - return path.Join(root, mi.path) -} - -func (mi *mountItem) bind() error { - if strings.Contains(mi.path, "*") { - return mi.bindGlobbed() - } - return mi.bindItem() -} - -func (mi *mountItem) bindGlobbed() error { - if mi.target != "" { - mi.fs.log.Warning("Ignoring target directory (%s) for mount item containing glob character: (%s)", mi.target, mi.path) - mi.target = "" - } - globbed, err := filepath.Glob(mi.path) - if err != nil { - return err - } - savedPath := mi.path - for _, p := range globbed { - if strings.Contains(p, "*") { - // XXX - continue - } - mi.path = p - if err := mi.bind(); err != nil { - // XXX - mi.path = savedPath - return err - } - } - mi.path = savedPath - return nil -} - -func (mi *mountItem) readSourceInfo(src string) (os.FileInfo, error) { - if fi, err := os.Stat(src); err == nil { - return fi, nil - } else if !os.IsNotExist(err) { - return nil, err - } - - if !mi.flags.isSet(MountCreateIfAbsent) { - return nil, fmt.Errorf("source path (%s) does not exist", src) - } - - home := mi.fs.user.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 (mi *mountItem) bindItem() error { - src, err := filepath.EvalSymlinks(mi.path) - if err != nil { - return fmt.Errorf("error resolving symlinks for path (%s): %v", mi.path, err) - } - - sinfo, err := mi.readSourceInfo(src) - if err != nil { - // XXX - return err - } - - target := mi.targetPath() - _, err = os.Stat(target) - if err == nil || !os.IsNotExist(err) { - mi.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(mi.fs.root, src); err != nil { - return fmt.Errorf("failed to copy path permissions for (%s): %v", src, err) - } - return bindMount(src, target, mi.flags.isSet(MountReadOnly), 0) -} - -func (mi *mountItem) blacklist() error { - if strings.Contains(mi.path, "*") { - return mi.blacklistGlobbed() - } - return blacklistItem(mi.path, mi.fs.log) -} - -func (mi *mountItem) blacklistGlobbed() error { - globbed, err := filepath.Glob(mi.path) - if err != nil { - // XXX - } - for _, p := range globbed { - if err := blacklistItem(p, mi.fs.log); err != nil { - return err - } - } - return nil -} - -func blacklistItem(path string, log *logging.Logger) error { - p, err := filepath.EvalSymlinks(path) - if err != nil { - log.Warning("Symlink evaluation failed for path: %s", path) - return err - } - fi, err := os.Stat(p) - if err != nil { - if os.IsNotExist(err) { - log.Info("Blacklist item (%s) does not exist", p) - return nil - } - return err - } - - src := emptyFilePath - if fi.IsDir() { - src = emptyDirPath - } - if err := syscall.Mount(src, p, "none", syscall.MS_BIND, "mode=400,gid=0"); err != nil { - // XXX warning - return err - } - // XXX log success - - return nil -} diff --git a/fs/ozinit.go b/fs/ozinit.go deleted file mode 100644 index 8f0d63a..0000000 --- a/fs/ozinit.go +++ /dev/null @@ -1,155 +0,0 @@ -package fs - -import ( - "os" - "path" - "syscall" -) - -// OzInit is run from the oz-init process and performs post chroot filesystem initialization -func (fs *Filesystem) OzInit() error { - if err := fs.ozinitMountDev(); err != nil { - return err - } - if err := fs.ozinitMountSysProc(); err != nil { - return err - } - if err := fs.ozinitCreateSymlinks(); err != nil { - return err - } - if err := fs.ozinitBlacklistItems(); err != nil { - return err - } - return nil -} - -func (fs *Filesystem) ozinitMountDev() error { - if fs.fullDevices { - flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC) - if err := syscall.Mount("none", "/dev", "devtmpfs", flags, ""); err != nil { - fs.log.Warning("Failed to mount devtmpfs: %v", err) - return err - } - } - - if err := mountSpecial("/dev/shm", "tmpfs", true); err != nil { - fs.log.Warning("Failed to mount shm directory: %v", err) - return err - } - - if err := mountSpecial("/tmp", "tmpfs", true); err != nil { - fs.log.Warning("Failed to mount shm directory: %v", err) - return err - } - - if err := mountSpecial("/dev/pts", "devpts", false); err != nil { - fs.log.Warning("Failed to mount pts directory: %v", err) - return err - } - - return nil -} - -func mountSpecial(path, mtype string, nodevs bool) error { - flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC) - if nodevs { - flags = flags | syscall.MS_NODEV - } - if err := os.MkdirAll(path, 0755); err != nil { - return err - } - return syscall.Mount(path, path, mtype, flags, "") -} - -func (fs *Filesystem) ozinitMountSysProc() error { - if fs.noSysAndProc { - return nil - } - flags := uintptr(syscall.MS_NOSUID | syscall.MS_REC | syscall.MS_NOEXEC | syscall.MS_NODEV) - proc := "/proc" - if err := syscall.Mount("proc", proc, "proc", flags, ""); err != nil { - fs.log.Warning("Failed to mount /proc: %v", err) - return err - } - roMounts := []string{ - "sysrq-trigger", - "bus", - "irq", - "sys/kernel/hotplug", - } - for _, rom := range roMounts { - p := path.Join(proc, rom) - if err := bindMount(p, p, true, 0); err != nil { - fs.log.Warning("Failed to RO mount %s: %v", p, err) - return err - } - } - - if err := syscall.Mount("sysfs", "/sys", "sysfs", syscall.MS_RDONLY|flags, ""); err != nil { - fs.log.Warning("Failed to mount /sys: %v", err) - return err - } - - return nil -} - -func (fs *Filesystem) ozinitCreateSymlinks() error { - for _, sl := range basicSymlinks { - if err := syscall.Symlink(sl[0], sl[1]); err != nil { - return err - } - } - - if fs.fullDevices == false { - for _, sl := range deviceSymlinks { - if err := syscall.Symlink(sl[0], sl[1]); err != nil { - return err - } - } - } - - return nil -} - -func (fs *Filesystem) ozinitBlacklistItems() error { - if err := createBlacklistDir(emptyDirPath); err != nil { - return err - } - if err := createBlacklistFile(emptyFilePath); err != nil { - return err - } - for _, item := range fs.blacklist { - if err := item.blacklist(); err != nil { - return err - } - } - return nil -} - -func createBlacklistDir(path string) error { - if err := os.MkdirAll(path, 0000); err != nil { - return err - } - return setBlacklistPerms(path, 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 -} diff --git a/fs/resolve.go b/fs/resolve.go index e9f00cd..913ae9d 100644 --- a/fs/resolve.go +++ b/fs/resolve.go @@ -3,20 +3,21 @@ package fs import ( "fmt" "os/exec" + "os/user" "path" "path/filepath" "strings" ) -func (fs *Filesystem) resolvePath(p string) ([]string, error) { - p, err := fs.resolveVars(p) +func ResolvePath(p string, u *user.User) ([]string, error) { + p, err := resolveVars(p, u) if err != nil { return nil, err } - return fs.resolveGlob(p) + return resolveGlob(p) } -func (fs *Filesystem) resolveVars(p string) (string, error) { +func resolveVars(p string, u *user.User) (string, error) { const pathVar = "${PATH}/" const homeVar = "${HOME}" const uidVar = "${UID}" @@ -31,27 +32,27 @@ func (fs *Filesystem) resolveVars(p string) (string, error) { return resolved, nil case strings.HasPrefix(p, homeVar): - if fs.user == nil { + if u == nil { return p, nil } - return path.Join(fs.user.HomeDir, p[len(homeVar):]), nil + return path.Join(u.HomeDir, p[len(homeVar):]), nil case strings.Contains(p, uidVar): - if fs.user == nil { + if u == nil { return p, nil } - return strings.Replace(p, uidVar, fs.user.Uid, -1), nil + return strings.Replace(p, uidVar, u.Uid, -1), nil case strings.Contains(p, userVar): - if fs.user == nil { + if u == nil { return p, nil } - return strings.Replace(p, userVar, fs.user.Username, -1), nil + return strings.Replace(p, userVar, u.Username, -1), nil } return p, nil } -func (fs *Filesystem) resolveGlob(p string) ([]string, error) { +func resolveGlob(p string) ([]string, error) { if !strings.Contains(p, "*") { return []string{p}, nil } diff --git a/fs/setup.go b/fs/setup.go deleted file mode 100644 index 5442ef3..0000000 --- a/fs/setup.go +++ /dev/null @@ -1,267 +0,0 @@ -package fs - -import ( - "errors" - "fmt" - "os" - "os/user" - "path" - "strconv" - "syscall" -) - -var basicBindDirs = []string{ - "/bin", "/lib", "/lib64", "/usr", "/etc", -} - -var basicEmptyDirs = []string{ - "/sbin", "/var", "/var/lib", - "/var/cache", "/home", "/boot", - "/tmp", "/run", "/run/user", - "/run/lock", "/root", - "/opt", "/srv", "/dev", "/proc", - "/sys", "/mnt", "/media", - //"/run/shm", -} - -var basicBlacklist = []string{ - "/usr/sbin", "/sbin", "${PATH}/su", - "${PATH}/sudo", "${PATH}/fusermount", - "${PATH}/xinput", "${PATH}/strace", - "${PATH}/mount", "${PATH}/umount", -} - -const emptyFilePath = "/tmp/oz.ro.file" -const emptyDirPath = "/tmp/oz.ro.dir" - -var basicSymlinks = [][2]string{ - {"/run", "/var/run"}, - {"/tmp", "/var/tmp"}, - {"/run/lock", "/var/lock"}, - {"/dev/shm", "/run/shm"}, -} - -var deviceSymlinks = [][2]string{ - {"/proc/self/fd", "/dev/fd"}, - {"/proc/self/fd/2", "/dev/stderr"}, - {"/proc/self/fd/0", "/dev/stdin"}, - {"/proc/self/fd/1", "/dev/stdout"}, - {"/dev/pts/ptmx", "/dev/ptmx"}, -} - -type fsDeviceDefinition struct { - path string - mode uint32 - dev int - perm uint32 -} - -const ugorw = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IRGRP | syscall.S_IWGRP | syscall.S_IROTH | syscall.S_IWOTH -const urwgr = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IRGRP -const urw = syscall.S_IRUSR | syscall.S_IWUSR - -var basicDevices = []fsDeviceDefinition{ - {path: "/dev/full", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 7), perm: 0666}, - {path: "/dev/null", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 3), perm: 0666}, - {path: "/dev/random", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 8), perm: 0666}, - - {path: "/dev/console", mode: syscall.S_IFCHR | urw, dev: _makedev(5, 1), perm: 0600}, - {path: "/dev/tty", mode: syscall.S_IFCHR | ugorw, dev: _makedev(5, 0), perm: 0666}, - {path: "/dev/tty1", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, - {path: "/dev/tty2", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, - {path: "/dev/tty3", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, - {path: "/dev/tty4", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, - - {path: "/dev/urandom", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 9), perm: 0666}, - {path: "/dev/zero", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 5), perm: 0666}, -} - -func _makedev(x, y int) int { - return (((x) << 8) | (y)) -} - -func (fs *Filesystem) Setup(profilesPath string) error { - profilePathInBindDirs := false - for _, bd := range basicBindDirs { - if bd == profilesPath { - profilePathInBindDirs = true - break - } - } - - if profilePathInBindDirs == false { - basicBindDirs = append(basicBindDirs, profilesPath) - } - - if fs.xpra != "" { - if err := fs.createXpraDir(); err != nil { - return err - } - item, err := fs.newItem(fs.xpra, fs.xpra, false) - if err != nil { - return err - } - fs.whitelist = append(fs.whitelist, item) - } - if err := fs.setupRootfs(); err != nil { - return err - } - if err := fs.setupChroot(); err != nil { - return err - } - if fs.fullDevices == false { - if err := fs.setupDev(); err != nil { - return err - } - } - - return fs.setupMountItems() -} - -func (fs *Filesystem) createXpraDir() error { - uid, gid, err := userIds(fs.user) - if err != nil { - return err - } - dir := path.Join(fs.user.HomeDir, ".Xoz", fs.name) - if err := createSubdirs(fs.user.HomeDir, uid, gid, 0755, ".Xoz", fs.name); err != nil { - return fmt.Errorf("failed to create xpra directory (%s): %v", dir, err) - } - return nil -} - -func userIds(user *user.User) (int, int, error) { - uid, err := strconv.Atoi(user.Uid) - if err != nil { - return -1, -1, errors.New("failed to parse uid from user struct: " + err.Error()) - } - gid, err := strconv.Atoi(user.Gid) - if err != nil { - return -1, -1, errors.New("failed to parse gid from user struct: " + err.Error()) - } - return uid, gid, nil -} - -func (fs *Filesystem) setupRootfs() error { - if err := os.MkdirAll(fs.base, 0755); err != nil { - return fmt.Errorf("unable to create directory (%s): %v", fs.base, err) - } - flags := uintptr(syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV) - data := "mode=755,gid=0" - if err := syscall.Mount(fs.base, fs.base, "tmpfs", flags, data); err != nil { - return fmt.Errorf("failed to create base tmpfs at %s: %v", fs.base, err) - } - /* - // Currently unused - // create extra directories - extra := []string{"sockets", "dev"} - for _, sub := range extra { - d := path.Join(fs.base, sub) - if err := os.Mkdir(d, 0755); err != nil { - return fmt.Errorf("unable to create directory (%s): %v", d, err) - } - } - */ - return nil -} - -func (fs *Filesystem) setupChroot() error { - var err error - if fs.noDefaults { - err = createEmptyDirectories(fs.root, basicBindDirs) - } else { - err = bindBasicDirectories(fs.root, basicBindDirs) - } - if err != nil { - return err - } - err = createEmptyDirectories(fs.root, basicEmptyDirs) - if err != nil { - return err - } - return nil -} - -func (fs *Filesystem) setupDev() error { - devPath := path.Join(fs.root, "dev") - flags := uintptr(syscall.MS_NOSUID | syscall.MS_NOEXEC) - if err := syscall.Mount("none", devPath, "tmpfs", flags, ""); err != nil { - fs.log.Warning("Failed to mount new tmpfs: %s (%v)", devPath, err) - return err - } - if err := os.Chmod(devPath, 0755); err != nil { - return err - } - - for _, dev := range basicDevices { - path := path.Join(fs.root, dev.path) - if err := syscall.Mknod(path, dev.mode, dev.dev); err != nil { - return fmt.Errorf("Failed to mknod device %s: %+v", path, err) - } - if err := os.Chmod(path, os.FileMode(dev.perm)); err != nil { - return fmt.Errorf("Unable to set permissions for device %s: %+v", dev.path, err) - } - } - - return nil -} - -func bindBasicDirectories(root string, dirs []string) error { - for _, src := range dirs { - st, err := os.Lstat(src) - if err != nil { - return err - } - mode := st.Mode() - target := path.Join(root, src) - if err := os.MkdirAll(target, mode.Perm()); err != nil { - return err - } - if err := bindMount(src, target, true, 0); err != nil { - return err - } - } - return nil -} - -func createEmptyDirectories(root string, dirs []string) error { - for _, p := range dirs { - target := path.Join(root, p) - if err := createEmptyDirectory(p, target); err != nil { - return err - } - } - return nil -} - -func createEmptyDirectory(source, target string) error { - fi, err := os.Stat(source) - if err != nil { - return err - } - mode := fi.Mode() - if err := os.MkdirAll(target, mode.Perm()); err != nil { - return err - } - if err := copyFileInfo(fi, target); err != nil { - return err - } - return nil -} - -func setupTmp(root string) error { - target := path.Join(root, "tmp") - if err := os.Chmod(target, 0777); err != nil { - return err - } - return bindMount(target, target, false, syscall.MS_NOEXEC) -} - -func (fs *Filesystem) setupMountItems() error { - for _, item := range fs.whitelist { - if err := item.bind(); err != nil { - // XXX - } - } - return nil -} diff --git a/oz-daemon/daemon.go b/oz-daemon/daemon.go index 6123be2..95264c9 100644 --- a/oz-daemon/daemon.go +++ b/oz-daemon/daemon.go @@ -6,12 +6,13 @@ import ( "syscall" "github.com/subgraph/oz" - "github.com/subgraph/oz/fs" "github.com/subgraph/oz/ipc" "github.com/subgraph/oz/network" "github.com/op/go-logging" + "github.com/subgraph/oz/fs" "os" + "path" ) type daemonState struct { @@ -87,6 +88,17 @@ func initialize() *daemonState { } } + rootfs := path.Join(config.SandboxPath, "rootfs") + fs := fs.NewFilesystem(config, d.log) + d.log.Info("Creating root filesystem at %s", rootfs) + if err := setupRootfs(fs); err != nil { + d.log.Fatalf("Failed setting up root filesystem: %v", err) + } + sockets := path.Join(config.SandboxPath, "sockets") + if err := os.MkdirAll(sockets, 0755); err != nil { + d.log.Fatalf("Failed to create sockets directory: %v", err) + } + return d } @@ -153,7 +165,7 @@ func (d *daemonState) handleLaunch(msg *LaunchMsg, m *ipc.Message) error { return m.Respond(&OkMsg{}) } -func (d *daemonState) sanitizeEnvironment(p *oz.Profile, oldEnv []string) ([]string) { +func (d *daemonState) sanitizeEnvironment(p *oz.Profile, oldEnv []string) []string { newEnv := []string{} for _, EnvItem := range d.config.EnvironmentVars { @@ -283,10 +295,14 @@ func (d *daemonState) handleClean(clean *CleanMsg, msg *ipc.Message) error { return msg.Respond(&ErrorMsg{errmsg}) } } - fs := fs.NewFromProfile(p, nil, d.config.SandboxPath, d.config.UseFullDev, d.log) - if err := fs.Cleanup(); err != nil { - return msg.Respond(&ErrorMsg{err.Error()}) - } + // XXX + d.Warning("Clean no longer implemented") + /* + fs := fs.NewFromProfile(p, nil, d.config.SandboxPath, d.config.UseFullDev, d.log) + if err := fs.Cleanup(); err != nil { + return msg.Respond(&ErrorMsg{err.Error()}) + } + */ return msg.Respond(&OkMsg{}) } diff --git a/oz-daemon/launch.go b/oz-daemon/launch.go index 66523a6..4558852 100644 --- a/oz-daemon/launch.go +++ b/oz-daemon/launch.go @@ -4,21 +4,21 @@ import ( "bufio" "fmt" "io" - "os" "os/exec" - "os/user" "path" - "path/filepath" "sync" "syscall" "github.com/subgraph/oz" - "github.com/subgraph/oz/fs" "github.com/subgraph/oz/network" - "github.com/subgraph/oz/xpra" "github.com/subgraph/oz/oz-init" + "github.com/subgraph/oz/xpra" + "crypto/rand" + "encoding/hex" "github.com/op/go-logging" + "github.com/subgraph/oz/fs" + "os/user" ) type Sandbox struct { @@ -36,7 +36,17 @@ type Sandbox struct { network *network.SandboxNetwork } -func createInitCommand(initPath, name, chroot string, env []string, uid uint32, display int, stn *network.SandboxNetwork) *exec.Cmd { +func createSocketPath(base string) (string, error) { + bs := make([]byte, 8) + _, err := rand.Read(bs) + if err != nil { + return "", err + } + + return path.Join(base, fmt.Sprintf("oz-init-control-%s", hex.EncodeToString(bs))), nil +} + +func createInitCommand(initPath, name string, socketPath string, env []string, uid uint32, display int, stn *network.SandboxNetwork) *exec.Cmd { cmd := exec.Command(initPath) cmd.Dir = "/" @@ -50,11 +60,12 @@ func createInitCommand(initPath, name, chroot string, env []string, uid uint32, } cmd.SysProcAttr = &syscall.SysProcAttr{ - Chroot: chroot, + //Chroot: chroot, Cloneflags: cloneFlags, } cmd.Env = []string{ "INIT_PROFILE=" + name, + "INIT_SOCKET=" + socketPath, fmt.Sprintf("INIT_UID=%d", uid), } @@ -75,20 +86,27 @@ func createInitCommand(initPath, name, chroot string, env []string, uid uint32, } func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log *logging.Logger) (*Sandbox, error) { - u, err := user.LookupId(fmt.Sprintf("%d", uid)) - if err != nil { - return nil, fmt.Errorf("failed to lookup user for uid=%d: %v", uid, err) - } - fs := fs.NewFromProfile(p, u, d.config.SandboxPath, d.config.UseFullDev, d.log) - if err := fs.Setup(d.config.ProfileDir); err != nil { - return nil, err - } + + /* + u, err := user.LookupId(fmt.Sprintf("%d", uid)) + if err != nil { + return nil, fmt.Errorf("failed to lookup user for uid=%d: %v", uid, err) + } + + + fs := fs.NewFromProfile(p, u, d.config.SandboxPath, d.config.UseFullDev, d.log) + if err := fs.Setup(d.config.ProfileDir); err != nil { + return nil, err + } + */ + display := 0 if p.XServer.Enabled && p.Networking.Nettype == network.TYPE_HOST { display = d.nextDisplay d.nextDisplay += 1 } + var err error stn := new(network.SandboxNetwork) stn.Nettype = p.Networking.Nettype if p.Networking.Nettype == network.TYPE_BRIDGE { @@ -98,20 +116,25 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log } } - cmd := createInitCommand(d.config.InitPath, p.Name, fs.Root(), msg.Env, uid, display, stn) + socketPath, err := createSocketPath(path.Join(d.config.SandboxPath, "sockets")) + if err != nil { + return nil, fmt.Errorf("Failed to create random socket path: %v", err) + } + cmd := createInitCommand(d.config.InitPath, p.Name, socketPath, msg.Env, uid, display, stn) log.Debug("Command environment: %+v", cmd.Env) pp, err := cmd.StderrPipe() if err != nil { - fs.Cleanup() + //fs.Cleanup() return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err) } if err := cmd.Start(); err != nil { - fs.Cleanup() + //fs.Cleanup() return nil, fmt.Errorf("Unable to start process: %+v", err) } + //rootfs := path.Join(d.config.SandboxPath, "rootfs") sbox := &Sandbox{ daemon: d, id: d.nextSboxId, @@ -119,8 +142,9 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log profile: p, init: cmd, cred: &syscall.Credential{Uid: uid, Gid: gid}, - fs: fs, - addr: path.Join(fs.Root(), ozinit.SocketAddress), + fs: fs.NewFilesystem(d.config, log), + //addr: path.Join(rootfs, ozinit.SocketAddress), + addr: socketPath, stderr: pp, network: stn, } @@ -128,7 +152,7 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log if p.Networking.Nettype == network.TYPE_BRIDGE { if err := network.NetInit(stn, d.network, cmd.Process.Pid, log); err != nil { cmd.Process.Kill() - fs.Cleanup() + //fs.Cleanup() return nil, fmt.Errorf("Unable to create veth networking: %+v", err) } } @@ -150,7 +174,7 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log } if !msg.Noexec { - go func () { + go func() { sbox.ready.Wait() wgNet.Wait() go sbox.launchProgram(msg.Path, msg.Pwd, msg.Args, log) @@ -170,19 +194,21 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log } func (sbox *Sandbox) launchProgram(cpath, pwd string, args []string, log *logging.Logger) { - if sbox.profile.AllowFiles { - for _, fpath := range args { - if _, err := os.Stat(fpath); err == nil { - if filepath.IsAbs(fpath) == false { - fpath = path.Join(pwd, fpath) - } - log.Info("Adding file `%s` to sandbox `%s`.", fpath, sbox.profile.Name) - if err := sbox.fs.AddBindWhitelist(fpath, fpath, false); err != nil { - log.Warning("Error adding file `%s`!", fpath) + /* + if sbox.profile.AllowFiles { + for _, fpath := range args { + if _, err := os.Stat(fpath); err == nil { + if filepath.IsAbs(fpath) == false { + fpath = path.Join(pwd, fpath) + } + log.Info("Adding file `%s` to sandbox `%s`.", fpath, sbox.profile.Name) + if err := sbox.fs.AddBindWhitelist(fpath, fpath, false); err != nil { + log.Warning("Error adding file `%s`!", fpath) + } } } } - } + */ err := ozinit.RunProgram(sbox.addr, cpath, pwd, args) if err != nil { @@ -194,7 +220,7 @@ func (sbox *Sandbox) remove(log *logging.Logger) { sboxes := []*Sandbox{} for _, sb := range sbox.daemon.sandboxes { if sb == sbox { - sb.fs.Cleanup() + // sb.fs.Cleanup() if sb.profile.Networking.Nettype == network.TYPE_BRIDGE { sb.network.Cleanup(log) } @@ -254,11 +280,17 @@ func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) { } func (sbox *Sandbox) startXpraClient() { + u, err := user.LookupId(fmt.Sprintf("%d", sbox.cred.Uid)) + if err != nil { + sbox.daemon.Error("Failed to lookup user for uid=%d, cannot start xpra", sbox.cred.Uid) + return + } + xpraPath := path.Join(u.HomeDir, ".Xoz", sbox.profile.Name) sbox.xpra = xpra.NewClient( &sbox.profile.XServer, uint64(sbox.display), sbox.cred, - sbox.fs.Xpra(), + xpraPath, sbox.profile.Name, sbox.daemon.log) diff --git a/oz-daemon/rootfs.go b/oz-daemon/rootfs.go new file mode 100644 index 0000000..9ee4e6f --- /dev/null +++ b/oz-daemon/rootfs.go @@ -0,0 +1,149 @@ +package daemon + +import ( + "fmt" + "github.com/subgraph/oz/fs" + "os" + "path" + "runtime" + "syscall" +) + +var basicBindDirs = []string{ + "/bin", "/lib", "/lib64", "/usr", "/etc", +} + +var basicEmptyDirs = []string{ + "/sbin", "/var", "/var/lib", + "/var/cache", "/home", "/boot", + "/tmp", "/run", "/run/user", + "/run/lock", "/root", + "/opt", "/srv", "/dev", "/proc", + "/sys", "/mnt", "/media", + //"/run/shm", +} + +var basicSymlinks = [][2]string{ + {"/run", "/var/run"}, + {"/tmp", "/var/tmp"}, + {"/run/lock", "/var/lock"}, + {"/dev/shm", "/run/shm"}, +} + +var deviceSymlinks = [][2]string{ + {"/proc/self/fd", "/dev/fd"}, + {"/proc/self/fd/2", "/dev/stderr"}, + {"/proc/self/fd/0", "/dev/stdin"}, + {"/proc/self/fd/1", "/dev/stdout"}, + {"/dev/pts/ptmx", "/dev/ptmx"}, +} + +var basicBlacklist = []string{ + "/usr/sbin", "/sbin", "${PATH}/su", + "${PATH}/sudo", "${PATH}/fusermount", + "${PATH}/xinput", "${PATH}/strace", + "${PATH}/mount", "${PATH}/umount", +} + +type fsDeviceDefinition struct { + path string + mode uint32 + dev int + perm uint32 +} + +const ugorw = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IRGRP | syscall.S_IWGRP | syscall.S_IROTH | syscall.S_IWOTH +const urwgr = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IRGRP +const urw = syscall.S_IRUSR | syscall.S_IWUSR + +var basicDevices = []fsDeviceDefinition{ + {path: "/dev/full", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 7), perm: 0666}, + {path: "/dev/null", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 3), perm: 0666}, + {path: "/dev/random", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 8), perm: 0666}, + + {path: "/dev/console", mode: syscall.S_IFCHR | urw, dev: _makedev(5, 1), perm: 0600}, + {path: "/dev/tty", mode: syscall.S_IFCHR | ugorw, dev: _makedev(5, 0), perm: 0666}, + {path: "/dev/tty1", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, + {path: "/dev/tty2", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, + {path: "/dev/tty3", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, + {path: "/dev/tty4", mode: syscall.S_IFREG | urwgr, dev: 0, perm: 0640}, + + {path: "/dev/urandom", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 9), perm: 0666}, + {path: "/dev/zero", mode: syscall.S_IFCHR | ugorw, dev: _makedev(1, 5), perm: 0666}, +} + +func _makedev(x, y int) int { + return (((x) << 8) | (y)) +} + +func setupRootfs(fsys *fs.Filesystem) error { + if err := os.MkdirAll(fsys.Root(), 0755); err != nil { + return fmt.Errorf("could not create rootfs path '%s': %v", fsys.Root(), err) + } + // XXX It's possible this doesn't work. + // see: https://github.com/golang/go/issues/1954 + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil { + return fmt.Errorf("could not unshare mount ns: %v", err) + } + if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil { + return fmt.Errorf("failed to set MS_PRIVATE on '%s': %v", "/", err) + } + + flags := uintptr(syscall.MS_NOSUID | syscall.MS_NOEXEC | syscall.MS_NODEV) + if err := syscall.Mount("", fsys.Root(), "tmpfs", flags, "mode=755,gid=0"); err != nil { + return fmt.Errorf("failed to mount tmpfs on '%s': %v", fsys.Root(), err) + } + + if err := syscall.Mount("", fsys.Root(), "", syscall.MS_PRIVATE, ""); err != nil { + return fmt.Errorf("failed to set MS_PRIVATE on '%s': %v", fsys.Root(), err) + } + + for _, p := range basicBindDirs { + if err := fsys.BindPath(p, p, true); err != nil { + return fmt.Errorf("failed to bind directory '%s': %v", p, err) + } + } + + for _, p := range basicEmptyDirs { + if err := fsys.CreateEmptyDir(p); err != nil { + return fmt.Errorf("failed to create empty directory '%s': %v", p, err) + } + } + + dp := path.Join(fsys.Root(), "dev") + if err := syscall.Mount("", dp, "tmpfs", syscall.MS_NOSUID|syscall.MS_NOEXEC, "mode=755"); err != nil { + return err + + } + for _, d := range basicDevices { + if err := fsys.CreateDevice(d.path, d.dev, d.mode, d.perm); err != nil { + return err + } + } + + for _, sl := range append(basicSymlinks, deviceSymlinks...) { + if err := fsys.CreateSymlink(sl[0], sl[1]); err != nil { + return err + } + } + + if err := fsys.CreateBlacklistPaths(); err != nil { + return err + } + + for _, bl := range basicBlacklist { + ps, err := fs.ResolvePath(bl, nil) + if err != nil { + return err + } + for _, p := range ps { + if err := fsys.BlacklistPath(p); err != nil { + return err + } + } + } + return nil +} diff --git a/oz-init/init.go b/oz-init/init.go index da9d0c5..25a8d76 100644 --- a/oz-init/init.go +++ b/oz-init/init.go @@ -7,8 +7,8 @@ import ( "net" "os" "os/exec" - "os/user" "os/signal" + "os/user" "strconv" "strings" "sync" @@ -22,15 +22,16 @@ import ( "github.com/kr/pty" "github.com/op/go-logging" + "path" ) -const SocketAddress = "/tmp/oz-init-control" const EnvPrefix = "INIT_ENV_" type initState struct { log *logging.Logger profile *oz.Profile config *oz.Config + sockaddr string launchEnv []string lock sync.Mutex children map[int]*exec.Cmd @@ -70,10 +71,12 @@ func parseArgs() *initState { } // We should check that the file contains config.InitPath, but // since proc is not mounted in this state is still doesn't exist. - if _, err := os.Stat("/proc/1/cmdline"); !os.IsNotExist(err) { - log.Error("What are you doing? Oz-init cannot be launched manually") - os.Exit(1) - } + /* + if _, err := os.Stat("/proc/1/cmdline"); !os.IsNotExist(err) { + log.Error("What are you doing? Oz-init cannot be launched manually") + os.Exit(1) + } + */ getvar := func(name string) string { val := os.Getenv(name) @@ -84,6 +87,7 @@ func parseArgs() *initState { return val } pname := getvar("INIT_PROFILE") + sockaddr := getvar("INIT_SOCKET") uidval := getvar("INIT_UID") dispval := os.Getenv("INIT_DISPLAY") @@ -166,6 +170,7 @@ func parseArgs() *initState { return &initState{ log: log, config: config, + sockaddr: sockaddr, launchEnv: env, profile: p, children: make(map[int]*exec.Cmd), @@ -173,7 +178,7 @@ func parseArgs() *initState { gid: gid, user: u, display: display, - fs: fs.NewFromProfile(p, u, config.SandboxPath, config.UseFullDev, log), + fs: fs.NewFilesystem(config, log), network: stn, } } @@ -183,8 +188,27 @@ func (st *initState) runInit() { sigs := make(chan os.Signal) signal.Notify(sigs, syscall.SIGTERM, os.Interrupt) - if homedir, _ := st.fs.GetHomeDir(); homedir != "" { - st.launchEnv = append(st.launchEnv, "HOME="+homedir) + s, err := ipc.NewServer(st.sockaddr, messageFactory, st.log, + handlePing, + st.handleRunProgram, + st.handleRunShell, + ) + if err != nil { + st.log.Error("NewServer failed: %v", err) + os.Exit(1) + } + + if err := os.Chown(st.sockaddr, st.uid, st.gid); err != nil { + st.log.Warning("Failed to chown oz-init control socket: %v", err) + } + + if err := st.setupFilesystem(nil); err != nil { + st.log.Error("Failed to setup filesytem: %v", err) + os.Exit(1) + } + + if st.user != nil && st.user.HomeDir != "" { + st.launchEnv = append(st.launchEnv, "HOME="+st.user.HomeDir) } if st.profile.Networking.Nettype != network.TYPE_HOST { @@ -204,10 +228,6 @@ func (st *initState) runInit() { } st.log.Info("Hostname set to (%s.local)", st.profile.Name) - if err := st.fs.OzInit(); err != nil { - st.log.Error("Error: setting up filesystem failed: %v\n", err) - os.Exit(1) - } oz.ReapChildProcs(st.log, st.handleChildExit) if st.profile.XServer.Enabled { @@ -215,19 +235,8 @@ func (st *initState) runInit() { st.startXpraServer() } st.xpraReady.Wait() + st.log.Info("XPRA started") - s, err := ipc.NewServer(SocketAddress, messageFactory, st.log, - handlePing, - st.handleRunProgram, - st.handleRunShell, - ) - if err != nil { - st.log.Error("NewServer failed: %v", err) - os.Exit(1) - } - if err := os.Chown(SocketAddress, st.uid, st.gid); err != nil { - st.log.Warning("Failed to chown oz-init control socket: %v", err) - } os.Stderr.WriteString("OK\n") go st.processSignals(sigs, s) @@ -241,11 +250,12 @@ func (st *initState) runInit() { } func (st *initState) startXpraServer() { - workdir := st.fs.Xpra() - if workdir == "" { - st.log.Warning("Xpra work directory not set") + if st.user == nil { + st.log.Warning("Cannot start xpra server because no user is set") return } + workdir := path.Join(st.user.HomeDir, ".Xoz", st.profile.Name) + st.log.Info("xpra work dir is %s", workdir) xpra := xpra.NewServer(&st.profile.XServer, uint64(st.display), workdir) p, err := xpra.Process.StderrPipe() if err != nil { @@ -292,12 +302,12 @@ func (st *initState) readXpraOutput(r io.ReadCloser) { func (st *initState) launchApplication(cpath, pwd string, cmdArgs []string) (*exec.Cmd, error) { suffix := "" if st.config.DivertSuffix != "" { - suffix = "."+st.config.DivertSuffix + suffix = "." + st.config.DivertSuffix } if cpath == "" { cpath = st.profile.Path } - cmd := exec.Command(cpath+suffix) + cmd := exec.Command(cpath + suffix) stdout, err := cmd.StdoutPipe() if err != nil { st.log.Warning("Failed to create stdout pipe: %v", err) @@ -389,8 +399,8 @@ func (st *initState) handleRunShell(rs *RunShellMsg, msg *ipc.Message) error { cmd.Env = append(cmd.Env, "TERM="+rs.Term) } if msg.Ucred.Uid != 0 && msg.Ucred.Gid != 0 { - if homedir, _ := st.fs.GetHomeDir(); homedir != "" { - cmd.Dir = homedir + if st.user != nil && st.user.HomeDir != "" { + cmd.Dir = st.user.HomeDir } } cmd.Env = append(cmd.Env, fmt.Sprintf("PS1=[%s] $ ", st.profile.Name)) @@ -493,3 +503,96 @@ func (st *initState) childrenVector() []*exec.Cmd { } return cs } + +func (st *initState) setupFilesystem(extra []oz.WhitelistItem) error { + fs := fs.NewFilesystem(st.config, st.log) + + if err := st.bindWhitelist(fs, st.profile.Whitelist); err != nil { + return err + } + + if err := st.bindWhitelist(fs, extra); err != nil { + return err + } + + if err := st.applyBlacklist(fs, st.profile.Blacklist); err != nil { + return err + } + + if st.profile.XServer.Enabled { + xprapath, err := xpra.CreateDir(st.user, st.profile.Name) + if err != nil { + return err + } + if err := fs.BindPath(xprapath, xprapath, false); err != nil { + return err + } + } + + if err := fs.Chroot(); err != nil { + return err + } + + mo := &mountOps{} + if st.config.UseFullDev { + mo.add(fs.MountFullDev) + } + mo.add(fs.MountShm, fs.MountTmp, fs.MountPts) + if !st.profile.NoSysProc { + mo.add(fs.MountProc, fs.MountSys) + } + return mo.run() +} + +func (st *initState) bindWhitelist(fsys *fs.Filesystem, wlist []oz.WhitelistItem) error { + if wlist == nil { + return nil + } + for _, wl := range wlist { + paths, err := fs.ResolvePath(wl.Path, st.user) + if err != nil { + return err + } + for _, p := range paths { + if err := fsys.BindPath(p, p, wl.ReadOnly); err != nil { + return err + } + } + } + return nil +} + +func (st *initState) applyBlacklist(fsys *fs.Filesystem, blist []oz.BlacklistItem) error { + if blist == nil { + return nil + } + for _, bl := range blist { + paths, err := fs.ResolvePath(bl.Path, st.user) + if err != nil { + return err + } + for _, p := range paths { + if err := fsys.BlacklistPath(p); err != nil { + return err + } + } + } + return nil +} + +type mountOps struct { + ops []func() error +} + +func (mo *mountOps) add(f ...func() error) { + mo.ops = append(mo.ops, f...) +} + +func (mo *mountOps) run() error { + for _, f := range mo.ops { + if err := f(); err != nil { + return err + } + } + return nil +} diff --git a/xpra/xpra.go b/xpra/xpra.go index 26e34b8..022dd35 100644 --- a/xpra/xpra.go +++ b/xpra/xpra.go @@ -1,9 +1,14 @@ package xpra import ( + "errors" "fmt" "github.com/subgraph/oz" + "os" "os/exec" + "os/user" + "path" + "strconv" ) type Xpra struct { @@ -63,3 +68,45 @@ func (x *Xpra) Stop() ([]byte, error) { cmd.Env = []string{"TMPDIR=" + x.WorkDir} return cmd.Output() } + +func GetPath(u *user.User, name string) string { + return path.Join(u.HomeDir, ".Xoz", name) +} + +func CreateDir(u *user.User, name string) (string, error) { + uid, gid, err := userIds(u) + if err != nil { + return "", err + } + dir := GetPath(u, name) + if err := createSubdirs(u.HomeDir, uid, gid, 0755, ".Xoz", name); err != nil { + return "", fmt.Errorf("failed to create xpra directory (%s): %v", dir, err) + } + return dir, nil +} + +func createSubdirs(base string, uid, gid int, mode os.FileMode, subdirs ...string) error { + dir := base + for _, sd := range subdirs { + dir = path.Join(dir, sd) + if err := os.Mkdir(dir, mode); err != nil && !os.IsExist(err) { + return err + } + if err := os.Chown(dir, uid, gid); err != nil { + return err + } + } + return nil +} + +func userIds(user *user.User) (int, int, error) { + uid, err := strconv.Atoi(user.Uid) + if err != nil { + return -1, -1, errors.New("failed to parse uid from user struct: " + err.Error()) + } + gid, err := strconv.Atoi(user.Gid) + if err != nil { + return -1, -1, errors.New("failed to parse gid from user struct: " + err.Error()) + } + return uid, gid, nil +}