-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 This patch looks good to me. acked. I agree that this is almost an entire rewrite. Most of the sandbox code that went into upstream has changed drastically. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAk44GakACgkQrlYvE4MpobObAQCZAay+sKzeKgObo07Qf6RIyxcG UT4Anitd00nZGNMjInNOsDfhxH8iAbER =yn41 -----END PGP SIGNATURE-----
>From 3a904ea749b3d02bf039ddf0d622626a0a7ed3e7 Mon Sep 17 00:00:00 2001 From: Eric Paris <eparis@xxxxxxxxxx> Date: Wed, 20 Jul 2011 14:54:47 -0400 Subject: [PATCH 91/92] policycoreutils: sandbox: FIXME update sandbox Should I break this down more? Can I make a better commit message? This patch updates the sandbox code to be in line with Fedora. The current code is way behind and this is almost a rewrite. Sandbox has new features, configuration, and all sorts of new changes. NOT-Signed-off-by: Eric Paris <eparis@xxxxxxxxxx> --- policycoreutils/sandbox/Makefile | 12 +- policycoreutils/sandbox/sandbox | 112 +++-- policycoreutils/sandbox/sandbox.8 | 29 +- policycoreutils/sandbox/sandbox.conf | 7 + policycoreutils/sandbox/sandbox.conf.5 | 40 ++ policycoreutils/sandbox/sandbox.init | 26 +- policycoreutils/sandbox/sandboxX.sh | 16 +- policycoreutils/sandbox/seunshare.8 | 43 ++ policycoreutils/sandbox/seunshare.c | 936 ++++++++++++++++++++++++++++---- policycoreutils/sandbox/start | 9 + 10 files changed, 1052 insertions(+), 178 deletions(-) create mode 100644 policycoreutils/sandbox/sandbox.conf create mode 100644 policycoreutils/sandbox/sandbox.conf.5 create mode 100644 policycoreutils/sandbox/seunshare.8 create mode 100644 policycoreutils/sandbox/start diff --git a/policycoreutils/sandbox/Makefile b/policycoreutils/sandbox/Makefile index ff0ee7c..0c8a085 100644 --- a/policycoreutils/sandbox/Makefile +++ b/policycoreutils/sandbox/Makefile @@ -7,10 +7,10 @@ SBINDIR ?= $(PREFIX)/sbin MANDIR ?= $(PREFIX)/share/man LOCALEDIR ?= /usr/share/locale SHAREDIR ?= $(PREFIX)/share/sandbox -override CFLAGS += $(LDFLAGS) -I$(PREFIX)/include -DPACKAGE="\"policycoreutils\"" -LDLIBS += -lselinux -lcap-ng +override CFLAGS += $(LDFLAGS) -I$(PREFIX)/include -DPACKAGE="\"policycoreutils\"" -Wall -Werror -Wextra +LDLIBS += -lcgroup -lselinux -lcap-ng -all: sandbox seunshare sandboxX.sh +all: sandbox seunshare sandboxX.sh start seunshare: seunshare.o $(EXTRA_OBJS) $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) @@ -20,14 +20,18 @@ install: all install -m 755 sandbox $(BINDIR) -mkdir -p $(MANDIR)/man8 install -m 644 sandbox.8 $(MANDIR)/man8/ + install -m 644 seunshare.8 $(MANDIR)/man8/ + -mkdir -p $(MANDIR)/man5 + install -m 644 sandbox.conf.5 $(MANDIR)/man5/ -mkdir -p $(SBINDIR) install -m 4755 seunshare $(SBINDIR)/ -mkdir -p $(SHAREDIR) install -m 755 sandboxX.sh $(SHAREDIR) + install -m 755 start $(SHAREDIR) -mkdir -p $(INITDIR) install -m 755 sandbox.init $(INITDIR)/sandbox -mkdir -p $(SYSCONFDIR) - install -m 644 sandbox.config $(SYSCONFDIR)/sandbox + install -m 644 sandbox.conf $(SYSCONFDIR)/sandbox test: @python test_sandbox.py -v diff --git a/policycoreutils/sandbox/sandbox b/policycoreutils/sandbox/sandbox index 0b89e9a..85a1fce 100644 --- a/policycoreutils/sandbox/sandbox +++ b/policycoreutils/sandbox/sandbox @@ -1,5 +1,6 @@ #! /usr/bin/python -Es # Authors: Dan Walsh <dwalsh@xxxxxxxxxx> +# Authors: Thomas Liu <tliu@xxxxxxxxxxxxxxxxx> # Authors: Josh Cogliati # # Copyright (C) 2009,2010 Red Hat @@ -19,15 +20,17 @@ # 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 setools PROGNAME = "policycoreutils" -HOMEDIR=pwd.getpwuid(os.getuid()).pw_dir - +SEUNSHARE = "/usr/sbin/seunshare" +SANDBOXSH = "/usr/share/sandbox/sandboxX.sh" import gettext gettext.bindtextdomain(PROGNAME, "/usr/share/locale") gettext.textdomain(PROGNAME) @@ -41,6 +44,7 @@ except IOError: import __builtin__ __builtin__.__dict__['_'] = unicode +DEFAULT_WINDOWSIZE = "1000x700" DEFAULT_TYPE = "sandbox_t" DEFAULT_X_TYPE = "sandbox_x_t" SAVE_FILES = {} @@ -63,15 +67,15 @@ def error_exit(msg): 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 +85,10 @@ def copyfile(file, dir, dest): 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,10 +166,10 @@ class Sandbox: 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. -""")) +%s is required for the action you want to perform. +""") % SEUNSHARE) def __mount_callback(self, option, opt, value, parser): self.__mount = True @@ -172,6 +177,15 @@ class Sandbox: def __x_callback(self, option, opt, value, parser): self.__mount = True setattr(parser.values, option.dest, True) + if not os.path.exists(SEUNSHARE): + raise ValueError(_(""" +%s is required for the action you want to perform. +""") % SEUNSHARE) + + if not os.path.exists(SANDBOXSH): + raise ValueError(_(""" +%s is required for the action you want to perform. +""") % SANDBOXSH) def __validdir(self, option, opt, value, parser): if not os.path.isdir(value): @@ -194,6 +208,8 @@ class Sandbox: 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,13 +228,15 @@ class Sandbox: /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 %s & WM_PID=$! -%s +dbus-launch --exit-with-session %s kill -TERM $WM_PID 2> /dev/null """ % (command, wm, command)) fd.close() @@ -229,11 +247,22 @@ kill -TERM $WM_PID 2> /dev/null def __parse_options(self): from optparse import OptionParser + types = "" + try: + types = _(""" +Policy defines the following types for use with the -t: +\t%s +""") % "\n\t".join(setools.seinfo(setools.ATTRIBUTE, "sandbox_type")[0]['types']) + except RuntimeError: + pass + usage = _(""" -sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [[-i file ] ...] [ -t type ] command +sandbox [-h] [-l level ] [-[X|M] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [ -w windowsize ] [[-i file ] ...] [ -t type ] command + +sandbox [-h] [-l level ] [-[X|M] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [ -w windowsize ] [[-i file ] ...] [ -t type ] -S +%s +""") % types -sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [[-i file ] ...] [ -t type ] -S -""") parser = OptionParser(version=self.VERSION, usage=usage) parser.disable_interspersed_args() @@ -268,6 +297,10 @@ sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [- 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,13 +309,21 @@ sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [- parser.add_option("-l", "--level", dest="level", help=_("MCS/MLS level for the sandbox")) + parser.add_option("-c", "--cgroups", + action="store_true", dest="usecgroup", default=False, + help="Use cgroups to limit this sandbox.") + + 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 +341,10 @@ sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [- 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,28 +374,35 @@ sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [- def __setup_dir(self): if self.__options.level or self.__options.session: return - sandboxdir = HOMEDIR + "/.sandbox" - 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=".sandbox") + self.__homedir = mkdtemp(dir="/tmp", prefix=".sandbox_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=".sandbox") + self.__tmpdir = mkdtemp(dir="/tmp", prefix=".sandbox_tmp_") selinux.setfscreatecon(None) self.__copyfiles() def __execute(self): try: + cmds = [ SEUNSHARE, "-Z", self.__execcon ] + if self.__options.usecgroup: + cmds.append('-c') + if self.__options.usecaps: + cmds.append('-C') + if not self.__options.level: + cmds.append('-k') + if self.__mount: + cmds += [ "-t", self.__tmpdir, "-h", self.__homedir ] + if self.__options.X_ind: xmodmapfile = self.__homedir + "/.xmodmap" xd = open(xmodmapfile,"w") @@ -359,14 +411,10 @@ sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [- self.__setup_sandboxrc(self.__options.wm) - cmds = [ '/usr/sbin/seunshare', "-t", self.__tmpdir, "-h", self.__homedir, "--", self.__execcon, "/usr/share/sandbox/sandboxX.sh" ] - rc = subprocess.Popen(cmds).wait() - return rc - - 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 += [ "--", 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 +452,7 @@ if __name__ == '__main__': 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 --git a/policycoreutils/sandbox/sandbox.8 b/policycoreutils/sandbox/sandbox.8 index 1479364..3deb4b2 100644 --- a/policycoreutils/sandbox/sandbox.8 +++ b/policycoreutils/sandbox/sandbox.8 @@ -1,10 +1,13 @@ -.TH SANDBOX "8" "May 2009" "chcat" "User Commands" +.TH SANDBOX "8" "May 2010" "sandbox" "User Commands" .SH NAME sandbox \- Run cmd under an SELinux sandbox .SH SYNOPSIS .B sandbox -[-l level ] [[-M | -X] -H homedir -T tempdir ] [-I includefile ] [ -W windowmanager ] [[-i file ]...] [ -t type ] cmd -[-l level ] [[-M | -X] -H homedir -T tempdir ] [-I includefile ] [ -W windowmanager ] [[-i file ]...] [ -t type ] -S +[-C] [-c] [-l level ] [[-M | -X] -H homedir -T tempdir ] [-I includefile ] [ -W windowmanager ] [ -w windowsize ] [[-i file ]...] [ -t type ] cmd + +.br +.B sandbox +[-C] [-c] [-l level ] [[-M | -X] -H homedir -T tempdir ] [-I includefile ] [ -W windowmanager ] [ -w windowsize ] [[-i file ]...] [ -t type ] -S .br .SH DESCRIPTION .PP @@ -42,6 +45,12 @@ Use alternate sandbox type, defaults to sandbox_t or sandbox_x_t for -X. \fB\-T\ tmpdir Use alternate tempory directory to mount on /tmp. Defaults to tmpfs. Requires -X or -M. .TP +\fB\-S +Run a full desktop session, Requires level, and home and tmpdir. +.TP +\fB\-w windowsize\fR +Specifies the windowsize when creating an X based Sandbox. The default windowsize is 1000x700. +.TP \fB\-W windowmanager\fR Select alternative window manager to run within .B sandbox -X. @@ -50,8 +59,20 @@ Default to /usr/bin/matchbox-window-manager. \fB\-X\fR Create an X based Sandbox for gui apps, temporary files for $HOME and /tmp, secondary Xserver, defaults to sandbox_x_t +.TP +\fB\-c\fR +Use control groups to control this copy of sandbox. Specify parameters in /etc/sysconfig/sandbox. Max memory usage and cpu usage are to be specified in percent. You can specify which CPUs to use by numbering them 0,1,2... etc. +.TP +\fB\-C\fR +Use capabilities within the sandbox. By default applications executed within the sandbox will not be allowed to use capabilities (setuid apps), with the -C flag, you can use programs requiring capabilities. .PP .SH "SEE ALSO" .TP -runcon(1) +runcon(1), seunshare(8), selinux(8) .PP + +.SH AUTHOR +This manual page was written by +.I Dan Walsh <dwalsh@xxxxxxxxxx> +and +.I Thomas Liu <tliu@xxxxxxxxxxxxxxxxx> diff --git a/policycoreutils/sandbox/sandbox.conf b/policycoreutils/sandbox/sandbox.conf new file mode 100644 index 0000000..7c35808 --- /dev/null +++ b/policycoreutils/sandbox/sandbox.conf @@ -0,0 +1,7 @@ +# Space separate list of homedirs +HOMEDIRS="/home" +# Control group configuration +NAME=sandbox +CPUAFFINITY=ALL +MEMUSAGE=80% +CPUUSAGE=80% diff --git a/policycoreutils/sandbox/sandbox.conf.5 b/policycoreutils/sandbox/sandbox.conf.5 new file mode 100644 index 0000000..ee97e10 --- /dev/null +++ b/policycoreutils/sandbox/sandbox.conf.5 @@ -0,0 +1,40 @@ +.TH sandbox.conf "5" "June 2010" "sandbox.conf" "Linux System Administration" +.SH NAME +sandbox.conf \- user config file for the SELinux sandbox +.SH DESCRIPTION +.PP +When running sandbox with the -C argument, it will be confined using control groups and a system administrator can specify how the sandbox is confined. + +.PP +Everything after "#" is ignored, as are empty lines. All arguments should be separated by and equals sign ("="). + +.PP +These keywords are allowed. + +.RS +.TP +.B NAME +The name of the sandbox control group. Default is "sandbox". + +.TP +.B CPUAFFINITY +Which cpus to assign sandbox to. The default is ALL, but users can specify a comma-separated list with dashes ("-") to represent ranges. Ex: 0-2,5 + +.TP +.B MEMUSAGE +How much memory to allow sandbox to use. The default is 80%. Users can specify either a percentage or a value in the form of a number followed by one of the suffixes K, M, G to denote kilobytes, megabytes or gigabytes respectively. Ex: 50% or 100M + +.TP +.B CPUUSAGE +Percentage of cpu sandbox should be allowed to use. The default is 80%. Specify a value followed by a percent sign ("%"). Ex: 50% + + + +.SH "SEE ALSO" +.TP +sandbox(8) +.PP + +.SH AUTHOR +This manual page was written by +.I Thomas Liu <tliu@xxxxxxxxxxxxxxxxx> diff --git a/policycoreutils/sandbox/sandbox.init b/policycoreutils/sandbox/sandbox.init index ff8b3ef..66aadfd 100644 --- a/policycoreutils/sandbox/sandbox.init +++ b/policycoreutils/sandbox/sandbox.init @@ -10,17 +10,12 @@ # # chkconfig: 345 1 99 # -# Description: sandbox and other apps that want to use pam_namespace -# on /var/tmp, /tmp and home directories, requires this script -# to be run at boot time. -# This script sets up the / mount point and all of its -# subdirectories as shared. The script sets up -# /tmp, /var/tmp, /home and any homedirs listed in -# /etc/sysconfig/sandbox and all of their subdirectories -# as unshared. -# All processes that use pam_namespace will see -# modifications to the global mountspace, except for the -# unshared directories. +# description: sandbox, xguest and other apps that want to use pam_namespace \ +# require this script be run at boot. This service script does \ +# not actually run any service but sets up: \ +# /var/tmp, /tmp and home directories to be used by these tools.\ +# If you do not use sandbox, xguest or pam_namespace you can turn \ +# this service off.\ # # Source function library. @@ -41,15 +36,6 @@ start() { touch $LOCKFILE mount --make-rshared / || return $? - mount --rbind /tmp /tmp || return $? - mount --rbind /var/tmp /var/tmp || return $? - mount --make-private /tmp || return $? - mount --make-private /var/tmp || return $? - for h in $HOMEDIRS; do - mount --rbind $h $h || return $? - mount --make-private $h || return $? - done - return 0 } diff --git a/policycoreutils/sandbox/sandboxX.sh b/policycoreutils/sandbox/sandboxX.sh index 8338203..0b0239c 100644 --- a/policycoreutils/sandbox/sandboxX.sh +++ b/policycoreutils/sandbox/sandboxX.sh @@ -1,15 +1,21 @@ #!/bin/bash +trap "" TERM context=`id -Z | secon -t -l -P` export TITLE="Sandbox $context -- `grep ^#TITLE: ~/.sandboxrc | /usr/bin/cut -b8-80`" -export SCREENSIZE="1000x700" -#export SCREENSIZE=`xdpyinfo | awk '/dimensions/ { print $2 }'` +[ -z $1 ] && export SCREENSIZE="1000x700" || export SCREENSIZE="$1" +[ -z $2 ] && export DPI="96" || export DPI="$2" trap "exit 0" HUP -(/usr/bin/Xephyr -title "$TITLE" -terminate -screen $SCREENSIZE -displayfd 5 5>&1 2>/dev/null) | while read D; do +(/usr/bin/Xephyr -title "$TITLE" -terminate -screen $SCREENSIZE -dpi $DPI -displayfd 5 5>&1 2>/dev/null) | while read D; do export DISPLAY=:$D - python -c 'import gtk, os, commands; commands.getstatusoutput("%s/.sandboxrc" % os.environ["HOME"])' + cat > ~/seremote << __EOF +#!/bin/sh +DISPLAY=$DISPLAY "\$@" +__EOF + chmod +x ~/seremote + /usr/share/sandbox/start $HOME/.sandboxrc export EXITCODE=$? - kill -HUP 0 + kill -TERM 0 break done exit 0 diff --git a/policycoreutils/sandbox/seunshare.8 b/policycoreutils/sandbox/seunshare.8 new file mode 100644 index 0000000..c69ceda --- /dev/null +++ b/policycoreutils/sandbox/seunshare.8 @@ -0,0 +1,43 @@ +.TH SEUNSHARE "8" "May 2010" "seunshare" "User Commands" +.SH NAME +seunshare \- Run cmd with alternate homedir, tmpdir and/or SELinux context +.SH SYNOPSIS +.B seunshare +[-v] [-c] [-C] [-k] [ -t tmpdir ] [ -h homedir ] [ -Z context ] -- executable [args] +.br +.SH DESCRIPTION +.PP +Run the +.I executable +within the specified context, using the alternate home directory and /tmp directory. The seunshare command unshares from the default namespace, then mounts the specified homedir and tmpdir over the default homedir and /tmp. Finally it tells the kernel to execute the application under the specified SELinux context. + +.TP +\fB\-h homedir\fR +Alternate homedir to be used by the application. Homedir must be owned by the user. +.TP +\fB\-t\ tmpdir +Use alternate tempory directory to mount on /tmp. tmpdir must be owned by the user. +.TP +\fB\-c --cgroups\fR +Use cgroups to control this copy of seunshare. Specify parameters in /etc/sysconfig/sandbox. Max memory usage and cpu usage are to be specified in percent. You can specify which CPUs to use by numbering them 0,1,2... etc. +.TP +\fB\-C --capabilities\fR +Allow apps executed within the namespace to use capabilities. Default is no capabilities. +.TP +\fB\-k --kill\fR +Kill all processes with matching MCS level. +.TP +\fB\-Z\ context +Use alternate SELinux context while runing the executable. +.TP +\fB\-v\fR +Verbose output +.SH "SEE ALSO" +.TP +runcon(1), sandbox(8), selinux(8) +.PP +.SH AUTHOR +This manual page was written by +.I Dan Walsh <dwalsh@xxxxxxxxxx> +and +.I Thomas Liu <tliu@xxxxxxxxxxxxxxxxx> diff --git a/policycoreutils/sandbox/seunshare.c b/policycoreutils/sandbox/seunshare.c index ec692e7..d1a1e47 100644 --- a/policycoreutils/sandbox/seunshare.c +++ b/policycoreutils/sandbox/seunshare.c @@ -1,27 +1,35 @@ +/* + * Authors: Dan Walsh <dwalsh@xxxxxxxxxx> + * Authors: Thomas Liu <tliu@xxxxxxxxxxxxxxxxx> + */ + +#define _GNU_SOURCE #include <signal.h> #include <sys/types.h> +#include <sys/stat.h> #include <sys/wait.h> #include <syslog.h> #include <sys/mount.h> +#include <glob.h> #include <pwd.h> -#define _GNU_SOURCE #include <sched.h> +#include <libcgroup.h> #include <string.h> #include <stdio.h> +#include <regex.h> #include <unistd.h> +#include <sys/fsuid.h> #include <stdlib.h> #include <cap-ng.h> #include <getopt.h> /* for getopt_long() form of getopt() */ #include <limits.h> #include <stdlib.h> #include <errno.h> +#include <fcntl.h> #include <selinux/selinux.h> #include <selinux/context.h> /* for context-mangling functions */ - -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> +#include <dirent.h> #ifdef USE_NLS #include <locale.h> /* for setlocale() */ @@ -39,29 +47,56 @@ #define MS_PRIVATE 1<<18 #endif +#ifndef PACKAGE +#define PACKAGE "policycoreutils" /* the name of this package lang translation */ +#endif + +#define BUF_SIZE 1024 +#define DEFAULT_PATH "/usr/bin:/bin" + +#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -c ] -C -t tmpdir -h homedir [-Z context] -- executable [args]") + +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 + * Take care of any signal setup. */ static int set_signal_handles(void) { @@ -75,32 +110,117 @@ static int set_signal_handles(void) (void)sigprocmask(SIG_SETMASK, &empty, NULL); - /* Terminate on SIGHUP. */ + /* Terminate on SIGHUP */ if (signal(SIGHUP, SIG_DFL) == SIG_ERR) { perror("Unable to set SIGHUP handler"); 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) + /** - * 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. + * Spawn external command using system() with dropped privileges. + * TODO: avoid system() and use exec*() instead */ -static int verify_mount(const char *mntdir, struct passwd *pwd) { +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; +} + +/** + * Check file/directory ownership, struct stat * must be passed to the + * functions. + */ +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 +243,7 @@ static int verify_shell(const char *shell_name) /* check the shell skipping newline char */ if (!strcmp(shell_name, buf)) { - rc = 1; + rc = 0; break; } } @@ -131,45 +251,594 @@ static int verify_shell(const char *shell_name) 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; + +} + +/** + * Error logging used by cgroups code. + */ +static int sandbox_error(const char *string) +{ + fprintf(stderr, string); + syslog(LOG_AUTHPRIV | LOG_ALERT, string); + exit(-1); +} + +/** + * Regular expression match. + */ +static int match(const char *string, char *pattern) +{ + int status; + regex_t re; + if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) != 0) { + return 0; + } + status = regexec(&re, string, (size_t)0, NULL, 0); + regfree(&re); + if (status != 0) { + return 0; + } + return 1; +} + +/** + * Apply cgroups settings from the /etc/sysconfig/sandbox config file. + */ +static int setup_cgroups() +{ + char *cpus = NULL; /* which CPUs to use */ + char *cgroupname = NULL;/* name for the cgroup */ + char *mem = NULL; /* string for memory amount to pass to cgroup */ + int64_t memusage = 0; /* amount of memory to use max (percent) */ + int cpupercentage = 0; /* what percentage of cpu to allow usage */ + FILE* fp; + char buf[BUF_SIZE]; + char *tok = NULL; + int rc = -1; + char *str = NULL; + const char* fname = "/etc/sysconfig/sandbox"; + + if ((fp = fopen(fname, "rt")) == NULL) { + fprintf(stderr, "Error opening sandbox config file."); + return rc; + } + while(fgets(buf, BUF_SIZE, fp) != NULL) { + /* Skip comments */ + if (buf[0] == '#') continue; + + /* Copy the string, ignoring whitespace */ + int len = strlen(buf); + free(str); + str = malloc((len + 1) * sizeof(char)); + + int ind = 0; + int i; + for (i = 0; i < len; i++) { + char cur = buf[i]; + if (cur != ' ' && cur != '\t') { + str[ind] = cur; + ind++; + } + } + str[ind] = '\0'; + + tok = strtok(str, "=\n"); + if (tok != NULL) { + if (!strcmp(tok, "CPUAFFINITY")) { + tok = strtok(NULL, "=\n"); + cpus = strdup(tok); + if (!strcmp(cpus, "ALL")) { + free(cpus); + cpus = NULL; + } + } else if (!strcmp(tok, "MEMUSAGE")) { + tok = strtok(NULL, "=\n"); + if (match(tok, "^[0-9]+[kKmMgG%]")) { + char *ind = strchr(tok, '%'); + if (ind != NULL) { + *ind = '\0';; + memusage = atoi(tok); + } else { + mem = strdup(tok); + } + } else { + fprintf(stderr, "Error parsing config file."); + goto err; + } + + } else if (!strcmp(tok, "CPUUSAGE")) { + tok = strtok(NULL, "=\n"); + if (match(tok, "^[0-9]+\%")) { + char* ind = strchr(tok, '%'); + *ind = '\0'; + cpupercentage = atoi(tok); + } else { + fprintf(stderr, "Error parsing config file."); + goto err; + } + } else if (!strcmp(tok, "NAME")) { + tok = strtok(NULL, "=\n"); + cgroupname = strdup(tok); + } else { + continue; + } + } + + } + if (mem == NULL) { + long phypz = sysconf(_SC_PHYS_PAGES); + long psize = sysconf(_SC_PAGE_SIZE); + memusage = phypz * psize * (float) memusage / 100.0; + } + + cgroup_init(); + + int64_t current_runtime = 0; + int64_t current_period = 0 ; + int64_t current_mem = 0; + char *curr_cpu_path = NULL; + char *curr_mem_path = NULL; + int ret = cgroup_get_current_controller_path(getpid(), "cpu", &curr_cpu_path); + if (ret) { + sandbox_error("Error while trying to get current controller path.\n"); + } else { + struct cgroup *curr = cgroup_new_cgroup(curr_cpu_path); + cgroup_get_cgroup(curr); + cgroup_get_value_int64(cgroup_get_controller(curr, "cpu"), "cpu.rt_runtime_us", ¤t_runtime); + cgroup_get_value_int64(cgroup_get_controller(curr, "cpu"), "cpu.rt_period_us", ¤t_period); + } + + ret = cgroup_get_current_controller_path(getpid(), "memory", &curr_mem_path); + if (ret) { + sandbox_error("Error while trying to get current controller path.\n"); + } else { + struct cgroup *curr = cgroup_new_cgroup(curr_mem_path); + cgroup_get_cgroup(curr); + cgroup_get_value_int64(cgroup_get_controller(curr, "memory"), "memory.limit_in_bytes", ¤t_mem); + } + + if (((float) cpupercentage) / 100.0> (float)current_runtime / (float) current_period) { + sandbox_error("CPU usage restricted!\n"); + goto err; + } + + if (mem == NULL) { + if (memusage > current_mem) { + sandbox_error("Attempting to use more memory than allowed!"); + goto err; + } + } + + long nprocs = sysconf(_SC_NPROCESSORS_ONLN); + + struct sched_param sp; + sp.sched_priority = sched_get_priority_min(SCHED_FIFO); + sched_setscheduler(getpid(), SCHED_FIFO, &sp); + struct cgroup *sandbox_group = cgroup_new_cgroup(cgroupname); + cgroup_add_controller(sandbox_group, "memory"); + cgroup_add_controller(sandbox_group, "cpu"); + + if (mem == NULL) { + if (memusage > 0) { + cgroup_set_value_uint64(cgroup_get_controller(sandbox_group, "memory"), "memory.limit_in_bytes", memusage); + } + } else { + cgroup_set_value_string(cgroup_get_controller(sandbox_group, "memory"), "memory.limit_in_bytes", mem); + } + if (cpupercentage > 0) { + cgroup_set_value_uint64(cgroup_get_controller(sandbox_group, "cpu"), "cpu.rt_runtime_us", + (float) cpupercentage / 100.0 * 60000); + cgroup_set_value_uint64(cgroup_get_controller(sandbox_group, "cpu"), "cpu.rt_period_us",60000 * nprocs); + } + if (cpus != NULL) { + cgroup_set_value_string(cgroup_get_controller(sandbox_group, "cpu"), "cgroup.procs",cpus); + } + + uint64_t allocated_mem; + if (cgroup_get_value_uint64(cgroup_get_controller(sandbox_group, "memory"), "memory.limit_in_bytes", &allocated_mem) > current_mem) { + sandbox_error("Attempting to use more memory than allowed!\n"); + goto err; + } + + rc = cgroup_create_cgroup(sandbox_group, 1); + if (rc != 0) { + sandbox_error("Failed to create group. Ensure that cgconfig service is running. \n"); + goto err; + } + + cgroup_attach_task(sandbox_group); + + rc = 0; +err: + fclose(fp); + free(str); + free(mem); + free(cgroupname); + free(cpus); + return rc; +} + +/* + If path is empy 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; } -#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -t tmpdir ] [ -h homedir ] -- CONTEXT executable [args] ") +/** + * 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 it 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; + } + if (fgetfilecon(fd_s, &con) == -1) { + fprintf(stderr, _("Failed to get context of the directory %s: %s\n"), src, strerror(errno)); + 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 permissions 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 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 usecgroups = 0; + 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'}, + {"cgroups", 1, 0, 'c'}, + {"context", 1, 0, 'Z'}, + {"capabilities", 1, 0, 'C'}, {NULL, 0, 0, 0} }; @@ -180,6 +849,12 @@ int main(int argc, char **argv) { return -1; } +#ifdef USE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + struct passwd *pwd=getpwuid(uid); if (!pwd) { perror(_("getpwduid failed")); @@ -187,34 +862,36 @@ int main(int argc, char **argv) { } 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, "Ccvh: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': + usecgroups = 1; + break; + case 'C': + cap_set = CAPNG_SELECT_CAPS; + break; + case 'Z': + execcon = optarg; break; default: fprintf(stderr, "%s\n", USAGE_STRING); @@ -223,76 +900,84 @@ int main(int argc, char **argv) { } 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 (set_signal_handles()) return -1; - if (tmpdir_s && seunshare_mount(tmpdir_s, "/tmp", pwd) < 0) - return -1; - } + if (usecgroups && setup_cgroups() < 0) 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); + + /* 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); - if (drop_capabilities(uid)) { - perror(_("Failed to drop all capabilities")); + /* 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 ((rc = clearenv())) { - perror(_("Unable to clear environment")); - free(display); - exit(-1); + if (unshare(CLONE_NEWNS) < 0) { + perror(_("Failed to unshare")); + goto childerr; } - if (setexeccon(scontext)) { - fprintf(stderr, _("Could not set exec context to %s.\n"), - scontext); - 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 ((rc = clearenv()) != 0) { + perror(_("Failed to clear environment")); + goto childerr; + } if (display) rc |= setenv("DISPLAY", display, 1); rc |= setenv("HOME", pwd->pw_dir, 1); @@ -300,22 +985,47 @@ int main(int argc, char **argv) { 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; } + diff --git a/policycoreutils/sandbox/start b/policycoreutils/sandbox/start new file mode 100644 index 0000000..52950d7 --- /dev/null +++ b/policycoreutils/sandbox/start @@ -0,0 +1,9 @@ +#! /usr/bin/python -Es +import gtk, commands, sys +rc = [-1,''] +try: + rc=commands.getstatusoutput(sys.argv[1]) +except: + pass +if rc[0] == 0: + print rc[1] -- 1.7.6
Attachment:
0091-policycoreutils-sandbox-FIXME-update-sandbox.patch.sig
Description: PGP signature