diff -uNr policycoreutils-2.0.85.orig/sandbox/sandbox policycoreutils-2.0.85/sandbox/sandbox --- policycoreutils-2.0.85.orig/sandbox/sandbox 2011-07-13 19:49:59.186002432 +0200 +++ policycoreutils-2.0.85/sandbox/sandbox 2011-07-13 23:19:06.323002791 +0200 @@ -19,16 +19,18 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -import os, sys, socket, random, fcntl, shutil, re, subprocess +import os, stat, sys, socket, random, fcntl, shutil, re, subprocess import selinux import signal from tempfile import mkdtemp import pwd +import commands +import gettext PROGNAME = "policycoreutils" -HOMEDIR=pwd.getpwuid(os.getuid()).pw_dir +SEUNSHARE = "/usr/sbin/seunshare" +SANDBOXSH = "/usr/share/sesandbox/sesandboxX.sh" -import gettext gettext.bindtextdomain(PROGNAME, "/usr/share/locale") gettext.textdomain(PROGNAME) @@ -41,6 +43,7 @@ import __builtin__ __builtin__.__dict__['_'] = unicode +DEFAULT_WINDOWSIZE = "1000x700" DEFAULT_TYPE = "sandbox_t" DEFAULT_X_TYPE = "sandbox_x_t" SAVE_FILES = {} @@ -63,15 +66,15 @@ sys.stderr.flush() sys.exit(1) -def copyfile(file, dir, dest): +def copyfile(file, srcdir, dest): import re - if file.startswith(dir): + if file.startswith(srcdir): dname = os.path.dirname(file) bname = os.path.basename(file) - if dname == dir: + if dname == srcdir: dest = dest + "/" + bname else: - newdir = re.sub(dir, dest, dname) + newdir = re.sub(srcdir, dest, dname) if not os.path.exists(newdir): os.makedirs(newdir) dest = newdir + "/" + bname @@ -81,9 +84,10 @@ shutil.copytree(file, dest) else: shutil.copy2(file, dest) + except shutil.Error, elist: - for e in elist: - sys.stderr.write(e[1]) + for e in elist.message: + sys.stderr.write(e[2]) SAVE_FILES[file] = (dest, os.path.getmtime(dest)) @@ -161,7 +165,7 @@ if not self.__options.homedir or not self.__options.tmpdir: self.usage(_("Homedir and tempdir required for level mounts")) - if not os.path.exists("/usr/sbin/seunshare"): + if not os.path.exists(SEUNSHARE): raise ValueError(_(""" /usr/sbin/seunshare is required for the action you want to perform. """)) @@ -194,6 +198,8 @@ self.__include(option, opt, i[:-1], parser) except IOError, e: sys.stderr.write(str(e)) + except TypeError, e: + sys.stderr.write(str(e)) fd.close() def __copyfiles(self): @@ -212,7 +218,9 @@ /etc/gdm/Xsession """) else: - command = " ".join(self.__paths) + command = self.__paths[0] + " " + for p in self.__paths[1:]: + command += "'%s' " % p fd.write("""#! /bin/sh #TITLE: %s /usr/bin/test -r ~/.xmodmap && /usr/bin/xmodmap ~/.xmodmap @@ -230,9 +238,9 @@ def __parse_options(self): from optparse import OptionParser usage = _(""" -sesandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [[-i file ] ...] [ -t type ] command +sesandbox [-h] [-l level ] [-[X|M] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [ -w windowsize ] [[-i file ] ...] [ -t type ] command -sesandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [[-i file ] ...] [ -t type ] -S +sesandbox [-h] [-l level ] [-[X|M] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [ -w windowsize ] [[-i file ] ...] [ -t type ] -S """) parser = OptionParser(version=self.VERSION, usage=usage) @@ -268,6 +276,10 @@ action="callback", callback=self.__validdir, help=_("alternate /tmp directory to use for mounting")) + parser.add_option("-w", "--windowsize", dest="windowsize", + type="string", default=DEFAULT_WINDOWSIZE, + help="size of the sandbox window") + parser.add_option("-W", "--windowmanager", dest="wm", type="string", default="/usr/bin/matchbox-window-manager -use_titlebar no", @@ -276,12 +288,18 @@ parser.add_option("-l", "--level", dest="level", help=_("MCS/MLS level for the sesandbox")) + parser.add_option("-C", "--capabilities", + action="store_true", dest="usecaps", default=False, + help="Allow apps requiring capabilities to run within the sandbox.") + + self.__parser=parser self.__options, cmds = parser.parse_args() if self.__options.X_ind: self.setype = DEFAULT_X_TYPE + self.dpi=commands.getoutput("xrdb -query | grep dpi | /bin/cut -f 2") if self.__options.setype: self.setype = self.__options.setype @@ -300,6 +318,10 @@ self.__homedir = self.__options.homedir self.__tmpdir = self.__options.tmpdir else: + if self.__options.level: + self.__homedir = self.__options.homedir + self.__tmpdir = self.__options.tmpdir + if len(cmds) == 0: self.usage(_("Command required")) cmds[0] = fullpath(cmds[0]) @@ -329,44 +351,45 @@ def __setup_dir(self): if self.__options.level or self.__options.session: return - sandboxdir = HOMEDIR + "/.sesandbox" - if not os.path.exists(sandboxdir): - os.mkdir(sandboxdir) if self.__options.homedir: selinux.chcon(self.__options.homedir, self.__filecon, recursive=True) self.__homedir = self.__options.homedir else: selinux.setfscreatecon(self.__filecon) - self.__homedir = mkdtemp(dir=sandboxdir, prefix=".sesandbox") + self.__homedir = mkdtemp(dir="/tmp", prefix=".sesandbox_home_") if self.__options.tmpdir: selinux.chcon(self.__options.tmpdir, self.__filecon, recursive=True) self.__tmpdir = self.__options.tmpdir else: selinux.setfscreatecon(self.__filecon) - self.__tmpdir = mkdtemp(dir="/tmp", prefix=".sesandbox") + self.__tmpdir = mkdtemp(dir="/tmp", prefix=".sesandbox_tmp_") selinux.setfscreatecon(None) self.__copyfiles() def __execute(self): try: - if self.__options.X_ind: - xmodmapfile = self.__homedir + "/.xmodmap" - xd = open(xmodmapfile,"w") - subprocess.Popen(["/usr/bin/xmodmap","-pke"],stdout=xd).wait() - xd.close() - - self.__setup_sandboxrc(self.__options.wm) - - cmds = [ '/usr/sbin/seunshare', "-t", self.__tmpdir, "-h", self.__homedir, "--", self.__execcon, "/usr/share/sesandbox/sesandboxX.sh" ] - rc = subprocess.Popen(cmds).wait() - return rc - + cmds = [ SEUNSHARE, "-Z", self.__execcon ] + if self.__options.usecaps: + cmds.append('-C') + if not self.__options.level: + cmds.append('-k') if self.__mount: - cmds = [ '/usr/sbin/seunshare', "-t", self.__tmpdir, "-h", self.__homedir, "--", self.__execcon ] + self.__paths - rc = subprocess.Popen(cmds).wait() - return rc + cmds += [ "-t", self.__tmpdir, "-h", self.__homedir ] + + if self.__options.X_ind: + xmodmapfile = self.__homedir + "/.xmodmap" + xd = open(xmodmapfile,"w") + subprocess.Popen(["/usr/bin/xmodmap","-pke"],stdout=xd).wait() + xd.close() + + self.__setup_sandboxrc(self.__options.wm) + + cmds += [ "--", SANDBOXSH, self.__options.windowsize, self.dpi ] + else: + cmds += [ "--" ] + self.__paths + return subprocess.Popen(cmds).wait() selinux.setexeccon(self.__execcon) rc = subprocess.Popen(self.__cmds).wait() @@ -404,7 +427,7 @@ sandbox = Sandbox() rc = sandbox.main() except OSError, error: - error_exit(error.args[1]) + error_exit(error) except ValueError, error: error_exit(error.args[0]) except KeyError, error: diff -uNr policycoreutils-2.0.85.orig/sandbox/seunshare.c policycoreutils-2.0.85/sandbox/seunshare.c --- policycoreutils-2.0.85.orig/sandbox/seunshare.c 2011-07-13 18:35:00.498002303 +0200 +++ policycoreutils-2.0.85/sandbox/seunshare.c 2011-07-13 21:33:33.601002609 +0200 @@ -1,10 +1,17 @@ +/* + * Authors: Dan Walsh + * Authors: Thomas Liu + * + * Does not include cgroups support (as opposed to seunshare in fedora) + */ + +#define _GNU_SOURCE #include #include #include #include #include #include -#define _GNU_SOURCE #include #include #include @@ -15,6 +22,10 @@ #include #include #include +#include +#include +#include +#include #include #include /* for context-mangling functions */ @@ -22,6 +33,8 @@ #include #include #include +#include +#include #ifdef USE_NLS #include /* for setlocale() */ @@ -39,26 +52,44 @@ #define MS_PRIVATE 1<<18 #endif +static int verbose = 0; +static int child = 0; + +static capng_select_t cap_set = CAPNG_SELECT_BOTH; + /** - * This function will drop all capabilities - * Returns zero on success, non-zero otherwise + * This function will drop all capabilities */ -static int drop_capabilities(uid_t uid) +static int drop_caps() { - capng_clear(CAPNG_SELECT_BOTH); - - if (capng_lock() < 0) + if (capng_have_capabilities(cap_set) == CAPNG_NONE) + return 0; + capng_clear(cap_set); + if (capng_lock() == -1 || capng_apply(cap_set) == -1) { + fprintf(stderr, _("Failed to drop all capabilities\n")); return -1; - /* Change uid */ - if (setresuid(uid, uid, uid)) { - fprintf(stderr, _("Error changing uid, aborting.\n")); + } + return 0; +} + +/** + * This function will drop all privileges. + */ +static int drop_privs(uid_t uid) { + if (drop_caps() == -1 || setresuid(uid, uid, uid) == -1) { + fprintf(stderr, _("Failed to drop privileges\n")); return -1; } - return capng_apply(CAPNG_SELECT_BOTH); + return 0; } -#define DEFAULT_PATH "/usr/bin:/bin" -static int verbose = 0; +/** + * If the user sends a siginto to seunshare, kill the child's session + */ +void handler(int sig) { + if (child > 0) + kill(-child, sig); +} /** * Take care of any signal setup @@ -81,24 +112,109 @@ return -1; } + if (signal(SIGINT, handler) == SIG_ERR) { + perror("Unable to set SIGHUP handler"); + return -1; + } + return 0; } +#define status_to_retval(status,retval) do { \ + if ((status) == -1) \ + retval = -1; \ + else if (WIFEXITED((status))) \ + retval = WEXITSTATUS((status)); \ + else if (WIFSIGNALED((status))) \ + retval = 128 + WTERMSIG((status)); \ + else \ + retval = -1; \ + } while(0) + + +/** + * Spawn external command using system() with dropped privileges. + * TODO: avoid system() and use exec*() instead. + */ +static int spawn_command(const char *cmd, uid_t uid) { + int child; + int status = -1; + + if (verbose > 1) + printf("spawn_command: %s\n", cmd); + + child = fork(); + if (child == -1) { + perror(_("Unable to fork")); + return status; + } + + if (child == 0) { + if (drop_privs(uid) != 0) + exit(-1); + + status = system(cmd); + status_to_retval(status, status); + exit(status); + } + + waitpid(child, &status, 0); + status_to_retval(status, status); + return status; +} + /** - * This function makes sure the mounted directory is owned by the user executing - * seunshare. - * If so, it returns 0. If it can not figure this out or they are different, it returns -1. + * Check file/directory ownership, struct stat * must be passed to the functions. */ -static int verify_mount(const char *mntdir, struct passwd *pwd) { +static int check_owner_uid(uid_t uid, const char *file, struct stat *st) { + if (S_ISLNK(st->st_mode)) { + fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file); + return -1; + } + if (st->st_uid != uid) { + fprintf(stderr, _("Error: %s not owned by UID %d\n"), file, uid); + return -1; + } + return 0; +} + +static int check_owner_gid(gid_t gid, const char *file, struct stat *st) { + if (S_ISLNK(st->st_mode)) { + fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file); + return -1; + } + if (st->st_gid != gid) { + fprintf(stderr, _("Error: %s not owned by GID %d\n"), file, gid); + return -1; + } + return 0; +} + +#define equal_stats(one,two) \ + ((one)->st_dev == (two)->st_dev && (one)->st_ino == (two)->st_ino && \ + (one)->st_uid == (two)->st_uid && (one)->st_gid == (two)->st_gid && \ + (one)->st_mode == (two)->st_mode) + +/** + * Sanity check specified directory. Store stat info for future comparison, or compare + * with previously saved info to detect replaced directories. + * Note: this function does not perform owner checks. + */ +static int verify_directory(const char *dir, struct stat *st_in, struct stat *st_out) { struct stat sb; - if (stat(mntdir, &sb) == -1) { - fprintf(stderr, _("Invalid mount point %s: %s\n"), mntdir, strerror(errno)); + + if (st_out == NULL) st_out = &sb; + + if (lstat(dir, st_out) == -1) { + fprintf(stderr, _("Failed to stat %s: %s\n"), dir, strerror(errno)); return -1; } - if (sb.st_uid != pwd->pw_uid) { - errno = EPERM; - syslog(LOG_AUTHPRIV | LOG_ALERT, "%s attempted to mount an invalid directory, %s", pwd->pw_name, mntdir); - perror(_("Invalid mount point, reporting to administrator")); + if (! S_ISDIR(st_out->st_mode)) { + fprintf(stderr, _("Error: %s is not a directory: %s\n"), dir, strerror(errno)); + return -1; + } + if (st_in && !equal_stats(st_in, st_out)) { + fprintf(stderr, _("Error: %s was replaced by a different directory\n"), dir); return -1; } return 0; @@ -123,7 +239,7 @@ /* check the shell skipping newline char */ if (!strcmp(shell_name, buf)) { - rc = 1; + rc = 0; break; } } @@ -131,45 +247,388 @@ return rc; } -static int seunshare_mount(const char *src, const char *dst, struct passwd *pwd) { +/* + * Mount directory and check that we mounted the right directory. + */ +static int seunshare_mount(const char *src, const char *dst, struct stat *src_st) { + int flags = MS_REC; + int is_tmp = 0; + if (verbose) - printf("Mount %s on %s\n", src, dst); - if (mount(dst, dst, NULL, MS_BIND | MS_REC, NULL) < 0) { + printf(_("Mounting %s on %s\n"), src, dst); + + if (strcmp("/tmp", dst) == 0) { + flags = flags | MS_NODEV | MS_NOSUID | MS_NOEXEC; + is_tmp = 1; + } + + /* mount directory */ + if (mount(dst, dst, NULL, MS_BIND | flags, NULL) < 0) { fprintf(stderr, _("Failed to mount %s on %s: %s\n"), dst, dst, strerror(errno)); return -1; } - if (mount(dst, dst, NULL, MS_PRIVATE | MS_REC, NULL) < 0) { + if (mount(dst, dst, NULL, MS_PRIVATE | flags, NULL) < 0) { fprintf(stderr, _("Failed to make %s private: %s\n"), dst, strerror(errno)); return -1; } - if (mount(src, dst, NULL, MS_BIND | MS_REC, NULL) < 0) { + if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) { fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); return -1; } - if (verify_mount(dst, pwd) < 0) + /* verify whether we mounted what we expected to mount */ + if (verify_directory(dst, src_st, NULL) < 0) return -1; + + /* bind mount /tmp on /var/tmp too */ + if (is_tmp) { + if (verbose) + printf(_("Mounting /tmp on /var/tmp\n")); + + if (mount("/var/tmp", "/var/tmp", NULL, MS_BIND | flags, NULL) < 0) { + fprintf(stderr, _("Failed to mount /var/tmp on /var/tmp: %s\n"), strerror(errno)); + return -1; + } + if (mount("/var/tmp", "/var/tmp", NULL, MS_PRIVATE | flags, NULL) < 0) { + fprintf(stderr, _("Failed to make /var/tmp private: %s\n"), strerror(errno)); + return -1; + } + if (mount("/tmp", "/var/tmp", NULL, MS_BIND | flags, NULL) < 0) { + fprintf(stderr, _("Failed to mount /tmp on /var/tmp: %s\n"), strerror(errno)); + return -1; + } + } + + return 0; } -#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -t tmpdir ] [ -h homedir ] -- CONTEXT executable [args] ") +/* + * If path is empty or ends with "/." or "/.." return -1 else return 0; + */ +static int bad_path(const char *path) { + const char *ptr; + ptr = path; + while (*ptr) ptr++; + if (ptr == path) return -1; // ptr null + ptr--; + if (ptr != path && *ptr == '.') { + ptr--; + if (*ptr == '/') return -1; // path ends in /. + if (*ptr == '.') { + if (ptr != path) { + ptr--; + if (*ptr == '/') return -1; // path ends in /.. + } + } + } + return 0; +} + +static int rsynccmd(const char *src, const char *dst, char **cmdbuf) { + char *buf = NULL; + char *newbuf = NULL; + glob_t fglob; + fglob.gl_offs = 0; + int flags = GLOB_PERIOD; + unsigned int i = 0; + int rc = -1; + + /* match glob for all files in src dir */ + if (asprintf(&buf, "%s/*", src) == -1) { + fprintf(stderr, "Out of memory\n"); + return -1; + } + + if (glob(buf, flags, NULL, &fglob) != 0) { + free(buf); + buf = NULL; + return -1; + } + + free(buf); + buf = NULL; + + for (i=0; i < fglob.gl_pathc; i++) { + const char * path = fglob.gl_pathv[i]; + + if (bad_path(path)) + continue; + + if (!buf) { + if (asprintf(&newbuf, "\'%s\'", path) == -1) { + fprintf(stderr, "Out of memory\n"); + goto err; + } + } else { + if (asprintf(&newbuf, "%s \'%s\'", buf, path) == -1) { + fprintf(stderr, "Out of memory\n"); + goto err; + } + } + + free(buf); buf = newbuf; + newbuf = NULL; + } + + if (buf) { + if (asprintf(&newbuf, "/usr/bin/rsync -trlHDq %s '%s'", buf, dst) == -1) { + fprintf(stderr, "Out of memory\n"); + goto err; + } + *cmdbuf = newbuf; + } else { + *cmdbuf = NULL; + } + rc = 0; + +err: + free(buf); + buf = NULL; + globfree(&fglob); + return rc; +} + +/** + * Clean up runtime temporary directory. Returns 0 if no problem was detected, + * >0 if some error was detected, but errors here are treated as non-fatal and + * left to tmpwatch to finish incomplete cleanup. + */ +static int cleanup_tmpdir(const char *tmpdir, const char *src, struct passwd *pwd, int copy_content) { + char *cmdbuf = NULL; + int rc = 0; + + /* rsync files back */ + if (copy_content) { + if (asprintf(&cmdbuf, "/usr/bin/rsync --exclude=.X11-unix -utrlHDq --delete '%s/' '%s/'", tmpdir, src) == -1) { + fprintf(stderr, _("Out of memory\n")); + cmdbuf = NULL; + rc++; + } + if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) { + fprintf(stderr, _("Failed to copy files from the runtime temporary directory\n")); + rc++; + } + free(cmdbuf); + cmdbuf = NULL; + } + + /* remove files from the runtime temporary directory */ + if (asprintf(&cmdbuf, "/bin/rm -r '%s/' 2>/dev/null", tmpdir) == -1) { + fprintf(stderr, _("Out of memory\n")); + cmdbuf = NULL; + rc++; + } + /* this may fail if there's root-owned file left in the runtime tmpdir */ + if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) + rc++; + free(cmdbuf); + cmdbuf = NULL; + + /* remove runtime temporary directory */ + setfsuid(0); + if (rmdir(tmpdir) == -1) + fprintf(stderr, _("Failed to remove directory %s: %s\n"), tmpdir, strerror(errno)); + setfsuid(pwd->pw_uid); + + return 0; +} + +/** + * seunshare will create a tmpdir in /tmp, with root ownership. The parent process + * waits for its child to exit to attempt to remove the directory. If it fails to remove + * the directory, we will need to rely on tmpreaper/tmpwatch to clean it up. + */ +static char *create_tmpdir(const char *src, struct stat *src_st, struct stat *out_st, struct passwd *pwd, security_context_t execcon) { + char *tmpdir = NULL; + char *cmdbuf = NULL; + int fd_t = -1, fd_s = -1; + struct stat tmp_st; + security_context_t con = NULL; + + /* get selinux context */ + if (execcon) { + setfsuid(pwd->pw_uid); + if ((fd_s = open(src, O_RDONLY)) < 0) { + fprintf(stderr, _("Failed to open directory %s: %s\n"), src, strerror(errno)); + goto err; + } + if (fstat(fd_s, &tmp_st) == -1) { + fprintf(stderr, _("Failed to stat directory %s: %s\n"), src, strerror(errno)); + goto err; + } + if (!equal_stats(src_st, &tmp_st)) { + fprintf(stderr, _("Error: %s was replaced by a different directory\n"), src); + goto err; + } + + /* ok to not reach this if there is an error */ + setfsuid(0); + } + + if (asprintf(&tmpdir, "/tmp/.sandbox-%s-XXXXXX", pwd->pw_name) == -1) { + fprintf(stderr, _("Out of memory\n")); + tmpdir = NULL; + goto err; + } + if (mkdtemp(tmpdir) == NULL) { + fprintf(stderr, _("Failed to create temporary directory: %s\n"), strerror(errno)); + goto err; + } + + /* temporary directory must be owned by root:user */ + if (verify_directory(tmpdir, NULL, out_st) < 0) { + goto err; + } + if (check_owner_uid(0, tmpdir, out_st) < 0) goto err; + if (check_owner_gid(getgid(), tmpdir, out_st) < 0) goto err; + + /* change permission of the temporary directory */ + if ((fd_t = open(tmpdir, O_RDONLY)) < 0) { + fprintf(stderr, _("Failed to open directory %s: %s\n"), tmpdir, strerror(errno)); + goto err; + } + if (fstat(fd_t, &tmp_st) == -1) { + fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno)); + goto err; + } + if (!equal_stats(out_st, &tmp_st)) { + fprintf(stderr, _("Error: %s was replaced by a different directory\n"), tmpdir); + goto err; + } + if (fchmod(fd_t, 01770) == -1) { + fprintf(stderr, _("Unable to change mode on %s: %s\n"), tmpdir, strerror(errno)); + goto err; + } + /* re-stat again to pick change mode */ + if (fstat(fd_t, out_st) == -1) { + fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno)); + goto err; + } + + /* copy selinux context */ + if (execcon) { + if (fsetfilecon(fd_t, con) == -1) { + fprintf(stderr, _("Failed to set context of the directory %s: %s\n"), tmpdir, strerror(errno)); + goto err; + } + } + + setfsuid(pwd->pw_uid); + + if (rsynccmd(src, tmpdir, &cmdbuf) < 0) { + goto err; + } + + /* ok to not reach this if there is an error */ + setfsuid(0); + + if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) { + fprintf(stderr, _("Failed to populate runtime temporary directory\n")); + cleanup_tmpdir(tmpdir, src, pwd, 0); + goto err; + } + + goto good; +err: + free(tmpdir); + tmpdir = NULL; +good: + free(cmdbuf); + cmdbuf = NULL; + freecon(con); + con = NULL; + if (fd_t >= 0) + close(fd_t); + if (fd_s >= 0) + close(fd_s); + return tmpdir; +} + +#define DEFAULT_PATH "/usr/bin:/bin" +#define USAGE_STRING _("USAGE: seunshare [ -v ] -C -t tmpdir -h homedir [-Z context] -- executable [args]") +#define PROC_BASE "/proc" + +static int killall (security_context_t execcon) { + DIR *dir; + security_context_t scon; + struct dirent *de; + pid_t *pid_table, pid, self; + int i; + int pids, max_pids; + int running = 0; + self = getpid(); + if (!(dir = opendir(PROC_BASE))) { + return -1; + } + max_pids = 256; + pid_table = malloc(max_pids * sizeof(pid_t)); + if (!pid_table) { + return -1; + } + pids = 0; + context_t con; + con = context_new(execcon); + const char *mcs = context_range_get(con); + printf("mcs=%s\n", mcs); + while ((de = readdir(dir)) != NULL) { + if (!(pid = (pid_t)atoi(de->d_name)) || pid == self) + continue; + + if (pids == max_pids) { + if(!(pid_table = realloc(pid_table, 2*pids*sizeof(pid_t)))) { + return -1; + } + max_pids *= 2; + } + pid_table[pids++] = pid; + } + + (void)closedir(dir); + + for (i = 0; i < pids; i++) { + pid_t id = pid_table[i]; + + if (getpidcon(id, &scon) == 0) { + context_t pidcon = context_new(scon); + /* Attempt to kill remaining processes */ + if (strcmp(context_range_get(pidcon), mcs) == 0) + kill(id, SIGKILL); + + context_free(pidcon); + freecon(scon); + } + running++; + } + + context_free(con); + free(pid_table); + return running; +} int main(int argc, char **argv) { - int rc; int status = -1; + security_context_t execcon = NULL; - security_context_t scontext; - - int flag_index; /* flag index in argv[] */ int clflag; /* holds codes for command line flags */ - char *tmpdir_s = NULL; /* tmpdir spec'd by user in argv[] */ + int kill_all = 0; + char *homedir_s = NULL; /* homedir spec'd by user in argv[] */ + char *tmpdir_s = NULL; /* tmpdir spec'd by user in argv[] */ + char * tmpdir_r = NULL; /* tmpdir created by seunshare */ + + struct stat st_homedir; + struct stat st_tmpdir_s; + struct stat st_tmpdir_r; const struct option long_options[] = { {"homedir", 1, 0, 'h'}, {"tmpdir", 1, 0, 't'}, + {"kill", 1, 0, 'k'}, {"verbose", 1, 0, 'v'}, + {"context", 1, 0, 'Z'}, + {"capabilities", 1, 0, 'C'}, {NULL, 0, 0, 0} }; @@ -187,34 +646,33 @@ } if (verify_shell(pwd->pw_shell) < 0) { - fprintf(stderr, _("Error! Shell is not valid.\n")); + fprintf(stderr, _("Error: User shell is not valid.\n")); return -1; } while (1) { - clflag = getopt_long(argc, argv, "h:t:", long_options, - &flag_index); + clflag = getopt_long(argc, argv, "Cvh:t:Z", long_options, NULL); if (clflag == -1) break; switch (clflag) { case 't': - if (!(tmpdir_s = realpath(optarg, NULL))) { - fprintf(stderr, _("Invalid mount point %s: %s\n"), optarg, strerror(errno)); - return -1; - } - if (verify_mount(tmpdir_s, pwd) < 0) return -1; + tmpdir_s = optarg; + break; + case 'k': + kill_all = 1; break; case 'h': - if (!(homedir_s = realpath(optarg, NULL))) { - fprintf(stderr, _("Invalid mount point %s: %s\n"), optarg, strerror(errno)); - return -1; - } - if (verify_mount(homedir_s, pwd) < 0) return -1; - if (verify_mount(pwd->pw_dir, pwd) < 0) return -1; + homedir_s = optarg; break; case 'v': - verbose = 1; + verbose++; + break; + case 'C': + cap_set = CAPNG_SELECT_CAPS; + break; + case 'Z': + execcon = optarg; break; default: fprintf(stderr, "%s\n", USAGE_STRING); @@ -223,74 +681,80 @@ } if (! homedir_s && ! tmpdir_s) { - fprintf(stderr, _("Error: tmpdir and/or homedir required \n"), - "%s\n", USAGE_STRING); + fprintf(stderr, _("Error: tmpdir and/or homedir required\n %s\n"), USAGE_STRING); return -1; } - if (argc - optind < 2) { - fprintf(stderr, _("Error: context and executable required \n"), - "%s\n", USAGE_STRING); + if (argc - optind < 1) { + fprintf(stderr, _("Error: executable required \n %s\n"), USAGE_STRING); return -1; } - scontext = argv[optind++]; - - if (set_signal_handles()) - return -1; - - if (unshare(CLONE_NEWNS) < 0) { - perror(_("Failed to unshare")); + if (execcon && is_selinux_enabled() != -1) { + fprintf(stderr, _("Error: execution context specified, but SELinux is not enabled\n")); return -1; } - if (homedir_s && tmpdir_s && (strncmp(pwd->pw_dir, tmpdir_s, strlen(pwd->pw_dir)) == 0)) { - if (seunshare_mount(tmpdir_s, "/tmp", pwd) < 0) - return -1; - if (seunshare_mount(homedir_s, pwd->pw_dir, pwd) < 0) - return -1; - } else { - if (homedir_s && seunshare_mount(homedir_s, pwd->pw_dir, pwd) < 0) - return -1; - - if (tmpdir_s && seunshare_mount(tmpdir_s, "/tmp", pwd) < 0) - return -1; - } + if (set_signal_handles()) return -1; + + /* set fsuid to ruid */ + /* Changing fsuid is usually required when user-specified directory is + * on an NFS mount. It's also desired to avoid leaking info about + * existence of the files not accessible to the user. + */ + setfsuid(uid); - if (drop_capabilities(uid)) { - perror(_("Failed to drop all capabilities")); + /* verify homedir and tmpdir */ + if (homedir_s && ( + verify_directory(homedir_s, NULL, &st_homedir) < 0 || + check_owner_uid(uid, homedir_s, &st_homedir))) return -1; + if (tmpdir_s && ( + verify_directory(tmpdir_s, NULL, &st_tmpdir_s) < 0 || + check_owner_uid(uid, tmpdir_s, &st_tmpdir_s))) return -1; + setfsuid(0); + + /* create runtime tmpdir */ + if (tmpdir_s && (tmpdir_r = create_tmpdir(tmpdir_s, &st_tmpdir_s, &st_tmpdir_r, pwd, execcon)) == NULL) { + fprintf(stderr, _("Failed to create runtime temporary directory\n")); return -1; } - int child = fork(); + /* spawn child process */ + child = fork(); if (child == -1) { perror(_("Unable to fork")); - return -1; + goto err; } - if (!child) { - char *display=NULL; - /* Construct a new environment */ - char *d = getenv("DISPLAY"); - if (d) { - display = strdup(d); - if (!display) { - perror(_("Out of memory")); - exit(-1); - } + if (child == 0) { + char *display = NULL; + int rc = -1; + + if (unshare(CLONE_NEWNS) < 0) { + perror(_("Failed to unshare")); + goto childerr; } - if ((rc = clearenv())) { - perror(_("Unable to clear environment")); - free(display); - exit(-1); + /* assume fsuid == ruid after this point */ + setfsuid(uid); + + /* mount homedir and tmpdir, in this order */ + if (homedir_s && seunshare_mount(homedir_s, pwd->pw_dir, &st_homedir) != 0) goto childerr; + if (tmpdir_s && seunshare_mount(tmpdir_r, "/tmp", &st_tmpdir_r) != 0) goto childerr; + + if (drop_privs(uid) != 0) goto childerr; + + /* construct a new environment */ + if ((display = getenv("DISPLAY")) != NULL) { + if ((display = strdup(display)) == NULL) { + perror(_("Out of memory")); + goto childerr; + } } - - if (setexeccon(scontext)) { - fprintf(stderr, _("Could not set exec context to %s.\n"), - scontext); - free(display); - exit(-1); + + if ((rc = clearenv()) != 0) { + perror(_("Failed to clear environment")); + goto childerr; } if (display) @@ -300,22 +764,46 @@ rc |= setenv("USER", pwd->pw_name, 1); rc |= setenv("LOGNAME", pwd->pw_name, 1); rc |= setenv("PATH", DEFAULT_PATH, 1); - + + if (rc != 0) { + fprintf(stderr, _("Failed to construct environment\n")); + goto childerr; + } + + /* selinux context */ + if (execcon && setexeccon(execcon) != 0) { + fprintf(stderr, _("Could not set exec context to %s.\n"), execcon); + goto childerr; + } + if (chdir(pwd->pw_dir)) { perror(_("Failed to change dir to homedir")); - exit(-1); + goto childerr; } + setsid(); + execv(argv[optind], argv + optind); + fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno)); +childerr: free(display); - perror("execv"); exit(-1); - } else { - waitpid(child, &status, 0); } - free(tmpdir_s); - free(homedir_s); + drop_caps(); + /* parent waits for child exit to do the cleanup */ + waitpid(child, &status, 0); + status_to_retval(status, status); + + /* Make sure all child processes exit */ + kill(-child, SIGTERM); + + if (execcon && kill_all) + killall(execcon); + + if (tmpdir_r) cleanup_tmpdir(tmpdir_r, tmpdir_s, pwd, 1); +err: + free(tmpdir_r); return status; }