Go to:
Gentoo Home
Documentation
Forums
Lists
Bugs
Planet
Store
Wiki
Get Gentoo!
Gentoo's Bugzilla – Attachment 280025 Details for
Bug 374897
<sys-apps/policycoreutils-2.0.85: privilege escalation (CVE-2011-1011)
Home
|
New
–
[Ex]
|
Browse
|
Search
|
Privacy Policy
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Suggested patch on policycoreutils
policycoreutils-2.0.85-fix-seunshare-vuln.patch (text/plain), 31.47 KB, created by
Sven Vermeulen
on 2011-07-13 21:33:38 UTC
(
hide
)
Description:
Suggested patch on policycoreutils
Filename:
MIME Type:
Creator:
Sven Vermeulen
Created:
2011-07-13 21:33:38 UTC
Size:
31.47 KB
patch
obsolete
>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 <dwalsh@redhat.com> >+ * Authors: Thomas Liu <tliu@fedoraproject.org> >+ * >+ * Does not include cgroups support (as opposed to seunshare in fedora) >+ */ >+ >+#define _GNU_SOURCE > #include <signal.h> > #include <sys/types.h> > #include <sys/wait.h> > #include <syslog.h> > #include <sys/mount.h> > #include <pwd.h> >-#define _GNU_SOURCE > #include <sched.h> > #include <string.h> > #include <stdio.h> >@@ -15,6 +22,10 @@ > #include <limits.h> > #include <stdlib.h> > #include <errno.h> >+#include <regex.h> >+#include <sys/fsuid.h> >+#include <fcntl.h> >+#include <dirent.h> > > #include <selinux/selinux.h> > #include <selinux/context.h> /* for context-mangling functions */ >@@ -22,6 +33,8 @@ > #include <sys/types.h> > #include <sys/stat.h> > #include <unistd.h> >+#include <glob.h> >+#include <regex.h> > > #ifdef USE_NLS > #include <locale.h> /* 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; > }
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 374897
: 280025