big fs refactor to use a single rootfs

master
brl 10 years ago
parent f23045350f
commit 351cc883f0

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

@ -3,186 +3,306 @@ package fs
import ( import (
"fmt" "fmt"
"os" "os"
"os/user"
"path" "path"
"strings" "strings"
"syscall" "syscall"
"github.com/op/go-logging" "github.com/op/go-logging"
"github.com/subgraph/oz" "github.com/subgraph/oz"
"os/user"
"path/filepath"
) )
type directory struct {
path string
empty bool
}
type Filesystem struct { type Filesystem struct {
log *logging.Logger log *logging.Logger
user *user.User base string
name string chroot bool
base string
root string
xpra string
noDefaults bool
noSysAndProc bool
fullDevices bool
whitelist []*mountItem
blacklist []*mountItem
} }
func (fs *Filesystem) Root() string { func NewFilesystem(config *oz.Config, log *logging.Logger) *Filesystem {
return fs.root if log == nil {
log = logging.MustGetLogger("oz")
}
return &Filesystem{
base: config.SandboxPath,
log: log,
}
} }
func (fs *Filesystem) Xpra() string { func (fs *Filesystem) Root() string {
return fs.xpra return path.Join(fs.base, "rootfs")
} }
func (fs *Filesystem) AddBindWhitelist(path, target string, readonly bool) error { func (fs *Filesystem) CreateEmptyDir(target string) error {
for _, fsitem := range fs.whitelist { fi, err := os.Stat(target)
if fsitem.path == path {
return nil
}
}
item, err := fs.newItem(path, target, readonly)
if err != nil { if err != nil {
return err return err
} }
fs.whitelist = append(fs.whitelist, item) if !fs.chroot {
return item.bindItem() 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 { func (fs *Filesystem) CreateDevice(devpath string, dev int, mode, perm uint32) error {
item, err := fs.newItem(path, target, readonly) p := path.Join(fs.Root(), devpath)
if err != nil { if err := syscall.Mknod(p, mode, dev); err != nil {
return err 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 return nil
} }
func (fs *Filesystem) addBlacklist(path string) error { func (fs *Filesystem) CreateSymlink(oldpath, newpath string) error {
item, err := fs.newItem(path, "", false) if !fs.chroot {
if err != nil { newpath = path.Join(fs.Root(), newpath)
return err }
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 return nil
} }
func (fs *Filesystem) newItem(path, target string, readonly bool) (*mountItem, error) { func (fs *Filesystem) BindPath(path, target string, readonly bool) error {
p, err := fs.resolveVars(path) 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 { 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 return nil, err
} }
t, err := fs.resolveVars(target)
pinfo, err := os.Stat(path.Dir(src))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &mountItem{
path: p, if err := copyFileInfo(pinfo, src); err != nil {
target: t, return nil, err
//readonly: readonly, }
fs: fs,
}, nil 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 { func (fs *Filesystem) Chroot() error {
fs := NewFilesystem(profile.Name, user, basePath, log) if fs.chroot {
for _, wl := range profile.Whitelist { return fmt.Errorf("filesystem is already in chroot()")
fs.addWhitelist(wl.Path, wl.Path, wl.ReadOnly)
} }
for _, bl := range profile.Blacklist { fs.log.Debug("chroot to %s", fs.Root())
fs.addBlacklist(bl.Path) if err := syscall.Chroot(fs.Root()); err != nil {
return fmt.Errorf("chroot to %s failed: %v", fs.Root(), err)
} }
fs.noDefaults = profile.NoDefaults if err := os.Chdir("/"); err != nil {
fs.noSysAndProc = profile.NoSysProc return fmt.Errorf("chdir to / after chroot() failed: %v", err)
fs.fullDevices = UseFullDev
if profile.XServer.Enabled && user != nil {
fs.xpra = path.Join(user.HomeDir, ".Xoz", profile.Name)
} }
return fs fs.chroot = true
return nil
} }
func NewFilesystem(name string, user *user.User, basePath string, log *logging.Logger) *Filesystem { func (fs *Filesystem) MountProc() error {
fs := new(Filesystem) err := fs.mountSpecial("/proc", "proc", 0, "")
fs.log = log if err != nil {
fs.name = name return err
if log == nil { }
fs.log = logging.MustGetLogger("oz") roMounts := []string{
"sysrq-trigger",
"bus",
"irq",
"sys/kernel/hotplug",
} }
fs.base = path.Join(basePath, name) for _, rom := range roMounts {
fs.root = path.Join(fs.base, "rootfs") if _, err := os.Stat(rom); err == nil {
fs.user = user 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) { func (fs *Filesystem) MountSys() error {
if fs.user == nil { return fs.mountSpecial("/sys", "sysfs", syscall.MS_RDONLY, "")
return "", fmt.Errorf("Home directory not set") }
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 bindMount(source, target string, flags int) error {
func xcreateEmptyDirectories(base string, paths []string) error { if err := syscall.Mount(source, target, "", syscall.MS_BIND, ""); err != nil {
for _, p := range paths { return fmt.Errorf("bind mount of %s -> %s failed: %v", source, target, err)
target := path.Join(base, p) }
if err := createEmptyDir(p, target); err != nil { if flags != 0 {
return err return remount(target, flags)
}
} }
return nil 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 return nil
} }
func createSubdirs(base string, subdirs []string) error { const emptyFilePath = "/oz.ro.file"
for _, sdir := range subdirs { const emptyDirPath = "/oz.ro.dir"
path := path.Join(base, sdir)
if err := createDirTree(path); err != nil { func (fs *Filesystem) CreateBlacklistPaths() error {
return err 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 return nil
} }
func createDirTree(path string) error { func createBlacklistDir(p string) error {
st, err := os.Stat(path) if err := os.MkdirAll(p, 0000); err != nil {
if err == nil { return err
if !st.IsDir() {
return fmt.Errorf("cannot create directory %s because path already exists and is not directory", path)
}
return nil
} }
if !os.IsNotExist(err) { return setBlacklistPerms(p, 0500)
return fmt.Errorf("unexpected error attempting Stat() on path %s: %v", path, err) }
func createBlacklistFile(path string) error {
fd, err := os.Create(path)
if err != nil {
return err
} }
if err := os.MkdirAll(path, 0755); err != nil { if err := fd.Close(); err != nil {
return fmt.Errorf("error creating directory tree %s: %v", path, err) return err
} }
return nil return setBlacklistPerms(path, 0400)
} }
*/
// bindMount performs a bind mount of the source path item so that it is visible func setBlacklistPerms(path string, mode os.FileMode) error {
// at the target path. By default the mount is flagged MS_NOSUID and MS_NODEV if err := os.Chown(path, 0, 0); err != nil {
// but additional flags can be passed in extraFlags. If the readonly flag is return err
// 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)
} }
if readonly { if err := os.Chmod(path, mode); err != nil {
flags |= syscall.MS_RDONLY | syscall.MS_REMOUNT return err
if err := syscall.Mount("", target, "", flags, ""); err != nil {
return fmt.Errorf("failed to remount %s as RDONLY: %v", target, err)
}
} }
return nil return nil
} }
@ -233,17 +353,3 @@ func copyFileInfo(info os.FileInfo, target string) error {
os.Chmod(target, info.Mode().Perm()) os.Chmod(target, info.Mode().Perm())
return nil 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
}

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

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

@ -3,20 +3,21 @@ package fs
import ( import (
"fmt" "fmt"
"os/exec" "os/exec"
"os/user"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
) )
func (fs *Filesystem) resolvePath(p string) ([]string, error) { func ResolvePath(p string, u *user.User) ([]string, error) {
p, err := fs.resolveVars(p) p, err := resolveVars(p, u)
if err != nil { if err != nil {
return nil, err 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 pathVar = "${PATH}/"
const homeVar = "${HOME}" const homeVar = "${HOME}"
const uidVar = "${UID}" const uidVar = "${UID}"
@ -31,27 +32,27 @@ func (fs *Filesystem) resolveVars(p string) (string, error) {
return resolved, nil return resolved, nil
case strings.HasPrefix(p, homeVar): case strings.HasPrefix(p, homeVar):
if fs.user == nil { if u == nil {
return p, 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): case strings.Contains(p, uidVar):
if fs.user == nil { if u == nil {
return p, 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): case strings.Contains(p, userVar):
if fs.user == nil { if u == nil {
return p, 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 return p, nil
} }
func (fs *Filesystem) resolveGlob(p string) ([]string, error) { func resolveGlob(p string) ([]string, error) {
if !strings.Contains(p, "*") { if !strings.Contains(p, "*") {
return []string{p}, nil return []string{p}, nil
} }

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

@ -6,12 +6,13 @@ import (
"syscall" "syscall"
"github.com/subgraph/oz" "github.com/subgraph/oz"
"github.com/subgraph/oz/fs"
"github.com/subgraph/oz/ipc" "github.com/subgraph/oz/ipc"
"github.com/subgraph/oz/network" "github.com/subgraph/oz/network"
"github.com/op/go-logging" "github.com/op/go-logging"
"github.com/subgraph/oz/fs"
"os" "os"
"path"
) )
type daemonState struct { 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 return d
} }
@ -153,7 +165,7 @@ func (d *daemonState) handleLaunch(msg *LaunchMsg, m *ipc.Message) error {
return m.Respond(&OkMsg{}) 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{} newEnv := []string{}
for _, EnvItem := range d.config.EnvironmentVars { 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}) return msg.Respond(&ErrorMsg{errmsg})
} }
} }
fs := fs.NewFromProfile(p, nil, d.config.SandboxPath, d.config.UseFullDev, d.log) // XXX
if err := fs.Cleanup(); err != nil { d.Warning("Clean no longer implemented")
return msg.Respond(&ErrorMsg{err.Error()}) /*
} 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{}) return msg.Respond(&OkMsg{})
} }

@ -4,21 +4,21 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"os"
"os/exec" "os/exec"
"os/user"
"path" "path"
"path/filepath"
"sync" "sync"
"syscall" "syscall"
"github.com/subgraph/oz" "github.com/subgraph/oz"
"github.com/subgraph/oz/fs"
"github.com/subgraph/oz/network" "github.com/subgraph/oz/network"
"github.com/subgraph/oz/xpra"
"github.com/subgraph/oz/oz-init" "github.com/subgraph/oz/oz-init"
"github.com/subgraph/oz/xpra"
"crypto/rand"
"encoding/hex"
"github.com/op/go-logging" "github.com/op/go-logging"
"github.com/subgraph/oz/fs"
"os/user"
) )
type Sandbox struct { type Sandbox struct {
@ -36,7 +36,17 @@ type Sandbox struct {
network *network.SandboxNetwork 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 := exec.Command(initPath)
cmd.Dir = "/" cmd.Dir = "/"
@ -50,11 +60,12 @@ func createInitCommand(initPath, name, chroot string, env []string, uid uint32,
} }
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
Chroot: chroot, //Chroot: chroot,
Cloneflags: cloneFlags, Cloneflags: cloneFlags,
} }
cmd.Env = []string{ cmd.Env = []string{
"INIT_PROFILE=" + name, "INIT_PROFILE=" + name,
"INIT_SOCKET=" + socketPath,
fmt.Sprintf("INIT_UID=%d", uid), 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) { 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) u, err := user.LookupId(fmt.Sprintf("%d", uid))
} if err != nil {
fs := fs.NewFromProfile(p, u, d.config.SandboxPath, d.config.UseFullDev, d.log) return nil, fmt.Errorf("failed to lookup user for uid=%d: %v", uid, err)
if err := fs.Setup(d.config.ProfileDir); err != nil { }
return nil, 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 display := 0
if p.XServer.Enabled && p.Networking.Nettype == network.TYPE_HOST { if p.XServer.Enabled && p.Networking.Nettype == network.TYPE_HOST {
display = d.nextDisplay display = d.nextDisplay
d.nextDisplay += 1 d.nextDisplay += 1
} }
var err error
stn := new(network.SandboxNetwork) stn := new(network.SandboxNetwork)
stn.Nettype = p.Networking.Nettype stn.Nettype = p.Networking.Nettype
if p.Networking.Nettype == network.TYPE_BRIDGE { 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) log.Debug("Command environment: %+v", cmd.Env)
pp, err := cmd.StderrPipe() pp, err := cmd.StderrPipe()
if err != nil { if err != nil {
fs.Cleanup() //fs.Cleanup()
return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err) return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err)
} }
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
fs.Cleanup() //fs.Cleanup()
return nil, fmt.Errorf("Unable to start process: %+v", err) return nil, fmt.Errorf("Unable to start process: %+v", err)
} }
//rootfs := path.Join(d.config.SandboxPath, "rootfs")
sbox := &Sandbox{ sbox := &Sandbox{
daemon: d, daemon: d,
id: d.nextSboxId, id: d.nextSboxId,
@ -119,8 +142,9 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log
profile: p, profile: p,
init: cmd, init: cmd,
cred: &syscall.Credential{Uid: uid, Gid: gid}, cred: &syscall.Credential{Uid: uid, Gid: gid},
fs: fs, fs: fs.NewFilesystem(d.config, log),
addr: path.Join(fs.Root(), ozinit.SocketAddress), //addr: path.Join(rootfs, ozinit.SocketAddress),
addr: socketPath,
stderr: pp, stderr: pp,
network: stn, 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 p.Networking.Nettype == network.TYPE_BRIDGE {
if err := network.NetInit(stn, d.network, cmd.Process.Pid, log); err != nil { if err := network.NetInit(stn, d.network, cmd.Process.Pid, log); err != nil {
cmd.Process.Kill() cmd.Process.Kill()
fs.Cleanup() //fs.Cleanup()
return nil, fmt.Errorf("Unable to create veth networking: %+v", err) 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 { if !msg.Noexec {
go func () { go func() {
sbox.ready.Wait() sbox.ready.Wait()
wgNet.Wait() wgNet.Wait()
go sbox.launchProgram(msg.Path, msg.Pwd, msg.Args, log) 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) { func (sbox *Sandbox) launchProgram(cpath, pwd string, args []string, log *logging.Logger) {
if sbox.profile.AllowFiles { /*
for _, fpath := range args { if sbox.profile.AllowFiles {
if _, err := os.Stat(fpath); err == nil { for _, fpath := range args {
if filepath.IsAbs(fpath) == false { if _, err := os.Stat(fpath); err == nil {
fpath = path.Join(pwd, fpath) 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.Info("Adding file `%s` to sandbox `%s`.", fpath, sbox.profile.Name)
log.Warning("Error adding file `%s`!", fpath) 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) err := ozinit.RunProgram(sbox.addr, cpath, pwd, args)
if err != nil { if err != nil {
@ -194,7 +220,7 @@ func (sbox *Sandbox) remove(log *logging.Logger) {
sboxes := []*Sandbox{} sboxes := []*Sandbox{}
for _, sb := range sbox.daemon.sandboxes { for _, sb := range sbox.daemon.sandboxes {
if sb == sbox { if sb == sbox {
sb.fs.Cleanup() // sb.fs.Cleanup()
if sb.profile.Networking.Nettype == network.TYPE_BRIDGE { if sb.profile.Networking.Nettype == network.TYPE_BRIDGE {
sb.network.Cleanup(log) sb.network.Cleanup(log)
} }
@ -254,11 +280,17 @@ func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) {
} }
func (sbox *Sandbox) startXpraClient() { 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.xpra = xpra.NewClient(
&sbox.profile.XServer, &sbox.profile.XServer,
uint64(sbox.display), uint64(sbox.display),
sbox.cred, sbox.cred,
sbox.fs.Xpra(), xpraPath,
sbox.profile.Name, sbox.profile.Name,
sbox.daemon.log) sbox.daemon.log)

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

@ -7,8 +7,8 @@ import (
"net" "net"
"os" "os"
"os/exec" "os/exec"
"os/user"
"os/signal" "os/signal"
"os/user"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -22,15 +22,16 @@ import (
"github.com/kr/pty" "github.com/kr/pty"
"github.com/op/go-logging" "github.com/op/go-logging"
"path"
) )
const SocketAddress = "/tmp/oz-init-control"
const EnvPrefix = "INIT_ENV_" const EnvPrefix = "INIT_ENV_"
type initState struct { type initState struct {
log *logging.Logger log *logging.Logger
profile *oz.Profile profile *oz.Profile
config *oz.Config config *oz.Config
sockaddr string
launchEnv []string launchEnv []string
lock sync.Mutex lock sync.Mutex
children map[int]*exec.Cmd children map[int]*exec.Cmd
@ -70,10 +71,12 @@ func parseArgs() *initState {
} }
// We should check that the file contains config.InitPath, but // We should check that the file contains config.InitPath, but
// since proc is not mounted in this state is still doesn't exist. // 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") if _, err := os.Stat("/proc/1/cmdline"); !os.IsNotExist(err) {
os.Exit(1) log.Error("What are you doing? Oz-init cannot be launched manually")
} os.Exit(1)
}
*/
getvar := func(name string) string { getvar := func(name string) string {
val := os.Getenv(name) val := os.Getenv(name)
@ -84,6 +87,7 @@ func parseArgs() *initState {
return val return val
} }
pname := getvar("INIT_PROFILE") pname := getvar("INIT_PROFILE")
sockaddr := getvar("INIT_SOCKET")
uidval := getvar("INIT_UID") uidval := getvar("INIT_UID")
dispval := os.Getenv("INIT_DISPLAY") dispval := os.Getenv("INIT_DISPLAY")
@ -166,6 +170,7 @@ func parseArgs() *initState {
return &initState{ return &initState{
log: log, log: log,
config: config, config: config,
sockaddr: sockaddr,
launchEnv: env, launchEnv: env,
profile: p, profile: p,
children: make(map[int]*exec.Cmd), children: make(map[int]*exec.Cmd),
@ -173,7 +178,7 @@ func parseArgs() *initState {
gid: gid, gid: gid,
user: u, user: u,
display: display, display: display,
fs: fs.NewFromProfile(p, u, config.SandboxPath, config.UseFullDev, log), fs: fs.NewFilesystem(config, log),
network: stn, network: stn,
} }
} }
@ -183,8 +188,27 @@ func (st *initState) runInit() {
sigs := make(chan os.Signal) sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGTERM, os.Interrupt) signal.Notify(sigs, syscall.SIGTERM, os.Interrupt)
if homedir, _ := st.fs.GetHomeDir(); homedir != "" { s, err := ipc.NewServer(st.sockaddr, messageFactory, st.log,
st.launchEnv = append(st.launchEnv, "HOME="+homedir) 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 { 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) 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) oz.ReapChildProcs(st.log, st.handleChildExit)
if st.profile.XServer.Enabled { if st.profile.XServer.Enabled {
@ -215,19 +235,8 @@ func (st *initState) runInit() {
st.startXpraServer() st.startXpraServer()
} }
st.xpraReady.Wait() 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") os.Stderr.WriteString("OK\n")
go st.processSignals(sigs, s) go st.processSignals(sigs, s)
@ -241,11 +250,12 @@ func (st *initState) runInit() {
} }
func (st *initState) startXpraServer() { func (st *initState) startXpraServer() {
workdir := st.fs.Xpra() if st.user == nil {
if workdir == "" { st.log.Warning("Cannot start xpra server because no user is set")
st.log.Warning("Xpra work directory not set")
return 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) xpra := xpra.NewServer(&st.profile.XServer, uint64(st.display), workdir)
p, err := xpra.Process.StderrPipe() p, err := xpra.Process.StderrPipe()
if err != nil { 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) { func (st *initState) launchApplication(cpath, pwd string, cmdArgs []string) (*exec.Cmd, error) {
suffix := "" suffix := ""
if st.config.DivertSuffix != "" { if st.config.DivertSuffix != "" {
suffix = "."+st.config.DivertSuffix suffix = "." + st.config.DivertSuffix
} }
if cpath == "" { if cpath == "" {
cpath = st.profile.Path cpath = st.profile.Path
} }
cmd := exec.Command(cpath+suffix) cmd := exec.Command(cpath + suffix)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
st.log.Warning("Failed to create stdout pipe: %v", err) 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) cmd.Env = append(cmd.Env, "TERM="+rs.Term)
} }
if msg.Ucred.Uid != 0 && msg.Ucred.Gid != 0 { if msg.Ucred.Uid != 0 && msg.Ucred.Gid != 0 {
if homedir, _ := st.fs.GetHomeDir(); homedir != "" { if st.user != nil && st.user.HomeDir != "" {
cmd.Dir = homedir cmd.Dir = st.user.HomeDir
} }
} }
cmd.Env = append(cmd.Env, fmt.Sprintf("PS1=[%s] $ ", st.profile.Name)) cmd.Env = append(cmd.Env, fmt.Sprintf("PS1=[%s] $ ", st.profile.Name))
@ -493,3 +503,96 @@ func (st *initState) childrenVector() []*exec.Cmd {
} }
return cs 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
}

@ -1,9 +1,14 @@
package xpra package xpra
import ( import (
"errors"
"fmt" "fmt"
"github.com/subgraph/oz" "github.com/subgraph/oz"
"os"
"os/exec" "os/exec"
"os/user"
"path"
"strconv"
) )
type Xpra struct { type Xpra struct {
@ -63,3 +68,45 @@ func (x *Xpra) Stop() ([]byte, error) {
cmd.Env = []string{"TMPDIR=" + x.WorkDir} cmd.Env = []string{"TMPDIR=" + x.WorkDir}
return cmd.Output() 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
}

Loading…
Cancel
Save