diff --git a/cmd/oz-mount/main.go b/cmd/oz-mount/main.go new file mode 100644 index 0000000..e308727 --- /dev/null +++ b/cmd/oz-mount/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "runtime" + + "github.com/subgraph/oz/oz-mount" +) + +func init() { + runtime.LockOSThread() + runtime.GOMAXPROCS(1) +} + +func main() { + defer runtime.UnlockOSThread() + + mount.Main(mount.MOUNT) +} diff --git a/cmd/oz-umount/main.go b/cmd/oz-umount/main.go new file mode 100644 index 0000000..2a8a49a --- /dev/null +++ b/cmd/oz-umount/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "runtime" + + "github.com/subgraph/oz/oz-mount" +) + +func init() { + runtime.LockOSThread() + runtime.GOMAXPROCS(1) +} + +func main() { + defer runtime.UnlockOSThread() + + mount.Main(mount.UMOUNT) +} diff --git a/fs/fs.go b/fs/fs.go index 45e11d7..1210e8f 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -160,6 +160,23 @@ func (fs *Filesystem) bind(from string, to string, flags int, u *user.User) erro return bindMount(src, to, mntflags) } +func (fs *Filesystem) UnbindPath(to string) (error) { + to = path.Join(fs.Root(), to) + + _, err := os.Stat(to) + if err != nil { + fs.log.Warning("Target (%s) does not exist, ignoring", to) + return nil + } + + // XXX + if err := syscall.Unmount(to, syscall.MNT_DETACH/* | syscall.MNT_FORCE*/); err != nil { + return err + } + + return os.Remove(to) +} + func readSourceInfo(src string, cancreate bool, u *user.User) (os.FileInfo, error) { if fi, err := os.Stat(src); err == nil { return fi, nil diff --git a/oz-daemon/daemon.go b/oz-daemon/daemon.go index 7f04134..abeea25 100644 --- a/oz-daemon/daemon.go +++ b/oz-daemon/daemon.go @@ -174,7 +174,7 @@ func (d *daemonState) handleListProfiles(msg *ListProfilesMsg, m *ipc.Message) e } func (d *daemonState) handleLaunch(msg *LaunchMsg, m *ipc.Message) error { - d.Debug("Launch message received: %+v", msg) + d.Debug("Launch message received. Path: %s Name: %s Pwd: %s Args: %+v", msg.Path, msg.Name, msg.Pwd, msg.Args) p, err := d.getProfileFromLaunchMsg(msg) if err != nil { return m.Respond(&ErrorMsg{err.Error()}) @@ -187,7 +187,7 @@ func (d *daemonState) handleLaunch(msg *LaunchMsg, m *ipc.Message) error { return m.Respond(&ErrorMsg{errmsg}) } else { d.Info("Found running sandbox for `%s`, running program there", p.Name) - sbox.launchProgram(msg.Path, msg.Pwd, msg.Args, d.log) + sbox.launchProgram(d.config.PrefixPath, msg.Path, msg.Pwd, msg.Args, d.log) } } else { d.Debug("Would launch %s", p.Name) diff --git a/oz-daemon/launch.go b/oz-daemon/launch.go index 31dea3a..a2b3971 100644 --- a/oz-daemon/launch.go +++ b/oz-daemon/launch.go @@ -7,6 +7,9 @@ import ( "os" "os/exec" "path" + "path/filepath" + "strconv" + "strings" "sync" "syscall" @@ -179,7 +182,7 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log go func() { sbox.ready.Wait() wgNet.Wait() - go sbox.launchProgram(msg.Path, msg.Pwd, msg.Args, log) + go sbox.launchProgram(d.config.PrefixPath, msg.Path, msg.Pwd, msg.Args, log) }() } @@ -195,29 +198,42 @@ func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, uid, gid uint32, log return sbox, nil } -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) - } - } - } - } - */ - +func (sbox *Sandbox) launchProgram(binpath, cpath, pwd string, args []string, log *logging.Logger) { + if sbox.profile.AllowFiles { + sbox.whitelistArgumentFiles(binpath, pwd, args, log) + } err := ozinit.RunProgram(sbox.addr, cpath, pwd, args) if err != nil { log.Error("start shell command failed: %v", err) } } +func (sbox *Sandbox) whitelistArgumentFiles(binpath, pwd string, args []string, log *logging.Logger) { + var files []string + for _, fpath := range args { + if _, err := os.Stat(fpath); err == nil { + if filepath.IsAbs(fpath) == false { + fpath = path.Join(pwd, fpath) + } + if !strings.HasPrefix(fpath, "/home/") { + continue + } + log.Notice("Adding file `%s` to sandbox `%s`.", fpath, sbox.profile.Name) + files = append(files, fpath) + } + } + if len(files) > 0 { + pmnt := path.Join(binpath, "bin", "oz-mount") + cmnt := exec.Command(pmnt, files...) + cmnt.Env = []string{"_OZ_NSPID=" + strconv.Itoa(sbox.init.Process.Pid)} + pout, err := cmnt.CombinedOutput(); + if err != nil { + log.Warning("Unable to bind files to sandbox: %v", err) + log.Warning("%s", string(pout)) + } + } +} + func (sbox *Sandbox) remove(log *logging.Logger) { sboxes := []*Sandbox{} for _, sb := range sbox.daemon.sandboxes { diff --git a/oz-mount/mount.c b/oz-mount/mount.c new file mode 100644 index 0000000..19ff809 --- /dev/null +++ b/oz-mount/mount.c @@ -0,0 +1,128 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +int enter_mount_namespace(void) { + if (geteuid() != 0) { + fprintf(stderr, "E Must run as root\n"); + return -1; + } + // Do some minimal verification to check that oz-daemon is the parent + pid_t ppid = getppid(); + //ppid = 10252; + if (checkProcessName(ppid, "oz-daemon") != 0) { + fprintf(stderr, "E unable to verify that oz-daemon is parent\n"); + return -1; + } + + // Parse namespace pid from environment + char *envv, *envvend; + long nspid; + envv = getenv("_OZ_NSPID"); + if (envv == NULL) { + fprintf(stderr, "E unable to get namespace pid from environment\n"); + return -1; + } + errno = 0; + nspid = strtol(envv, &envvend, 10); + if ((errno == ERANGE && (nspid == LONG_MAX || nspid == LONG_MIN)) + || (errno != 0 && nspid == 0)) { + fprintf(stderr, "E unable to parse namespace pid from environment\n"); + return -1; + } + if (envvend == envv || nspid < 0) { + fprintf(stderr, "E unable to parse namespace pid from environment\n"); + return -1; + } + + // Verify that the target is an instance of oz-init + if (checkProcessName(nspid, "oz-init") != 0) { + fprintf(stderr, "E unable to verify that oz-init is the target\n"); + return -1; + } + + char nspath[PATH_MAX]; + if (snprintf(nspath, PATH_MAX-1, "/proc/%ld/ns", nspid) < 0) { + fprintf(stderr, "E unable to parse namespace path `/proc/%ld/ns`\n", nspid); + return -1; + } + printf("D Opening: %s\n", nspath); + + // Start opening the namespace + struct stat st; + int tfd, fd; + tfd = open(nspath, O_DIRECTORY | O_RDONLY); + if (tfd == -1) { + fprintf(stderr, "E failed to open child namespace\n"); + return -1; + } + // Symlinks on all namespaces exist for dead processes, but they can't be opened + if (fstatat(tfd, "mnt", &st, AT_SYMLINK_NOFOLLOW) == -1) { + if (errno == ENOENT) { + fprintf(stderr, "E failed to open child namespace\n"); + return -1; + } + } + fd = openat(tfd, "mnt", O_RDONLY); + if (fd == -1) { + fprintf(stderr, "E failed to open child mount namespace: %s\n", nspath); + return -1; + } + // Set the namespace. + if (setns(fd, 0) == -1) { + fprintf(stderr, "E failed to setns for: %s\n", nspath); + return -1; + } + close(fd); +} + +int checkProcessName(pid_t pid, char *pname) { + FILE *fp; + char *line = NULL; + char pproc[PATH_MAX]; + char cline[PATH_MAX]; + size_t len = 0; + ssize_t read; + + if (snprintf(cline, PATH_MAX-1, "Name: %s\n", pname) < 0) { + return -1; + } + + if (snprintf(pproc, PATH_MAX-1, "/proc/%ld/status", pid) < 0) { + return -1; + } + + fp = fopen(pproc, "r"); + if (fp == NULL) { + return -1; + } + + int retval = -1; + while (retval == -1 && (read = getline(&line, &len, fp)) != -1) { + if (hasPrefix(line, "Name:") >= 0) { + retval = strcmp(cline, line); + break; + } + } + + fclose(fp); + if (line) { + free(line); + } + + return retval; +} + +int hasPrefix(const char *str, const char *pre) { + size_t lenpre = strlen(pre), + lenstr = strlen(str); + return lenstr < lenpre ? -1 : strncmp(pre, str, lenpre); +} diff --git a/oz-mount/mount.go b/oz-mount/mount.go new file mode 100644 index 0000000..04cfb92 --- /dev/null +++ b/oz-mount/mount.go @@ -0,0 +1,102 @@ +// +build linux,!gccgo +package mount + +// extern int enter_mount_namespace(void); +/* +#include +__attribute__((constructor)) void init(void) { + if (enter_mount_namespace() < 0) { + exit(EXIT_FAILURE); + } +} +*/ +import "C" + +import ( + "os" + "path" + "strings" + + "github.com/subgraph/oz" + "github.com/subgraph/oz/fs" + + "github.com/op/go-logging" +) + +const ( + MOUNT = 1 << iota + UMOUNT +) + +func Main(mode int) { + log := createLogger() + config, err := loadConfig() + if err != nil { + log.Error("Could not load configuration: %s\n", oz.DefaultConfigPath, err) + os.Exit(1) + } + + fsys := fs.NewFilesystem(config, log) + for fii, fpath := range os.Args { + if fii == 0 { + continue + } + if !strings.HasPrefix(fpath, "/home/") { + log.Warning("Ignored `%s`, only files inside of home are permitted!", fpath) + continue + } + switch mode { + case MOUNT: + mount(fpath, fsys, log) + case UMOUNT: + unmount(fpath, fsys, log) + } + } + + os.Exit(0) +} + +func mount(fpath string, fsys *fs.Filesystem, log *logging.Logger) { + if _, err := os.Stat(fpath); err == nil { + //log.Notice("Adding file `%s`.", fpath) + if err := fsys.BindPath(fpath, fs.BindCanCreate, nil); err != nil { + log.Error("%v while adding `%s`!", err, fpath) + os.Exit(1) + } + } +} + +func unmount(fpath string, fsys *fs.Filesystem, log *logging.Logger) { + sbpath := path.Join(fsys.Root(), fpath) + if _, err := os.Stat(sbpath); err == nil { + //log.Notice("Removing file `%s`.", fpath) + if err := fsys.UnbindPath(fpath); err != nil { + log.Error("%v while removing `%s`!", err, fpath) + os.Exit(1) + } + } else { + log.Error("%v error while removing `%s`!", err, fpath) + } +} + +func createLogger() *logging.Logger { + l := logging.MustGetLogger("oz-init") + be := logging.NewLogBackend(os.Stderr, "", 0) + f := logging.MustStringFormatter("%{level:.1s} %{message}") + fbe := logging.NewBackendFormatter(be, f) + logging.SetBackend(fbe) + return l +} + +func loadConfig() (*oz.Config, error) { + config, err := oz.LoadConfig(oz.DefaultConfigPath) + if err != nil { + if os.IsNotExist(err) { + config = oz.NewDefaultConfig() + } else { + return nil, err + } + } + + return config, nil +}