Re: Updated sandbox patch.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Update patch

Changed X_FILES to SAVE_FILES
Removed Level line
Fixed missing seunshare line
Fixed __validdir function
Make __includefiles use __include
Fixed Descriptions
Fixed setype selection
Did not fix "_t" assumption.
Removed warnings ignore.
Changed chcon command to be internal.
Switched to useing Popen rather then spawnvp
Fixed up man pages
Fixed init script description and usage statement
Fixed all of your comments on seunshare.c except running as root.  I
think there is a vulnerability in dropping capabilities that makes
allowing this to be run as root problematic.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.14 (GNU/Linux)
Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/

iEYEARECAAYFAkv0IIkACgkQrlYvE4MpobPNnQCdGXyaG4YUYoueZb+dG2IjjSiL
8zMAn1bws/c4ub26T12X4ru/+I9nXR/7
=WE7M
-----END PGP SIGNATURE-----
diff --git a/policycoreutils/Makefile b/policycoreutils/Makefile
index 538302b..86ed03f 100644
--- a/policycoreutils/Makefile
+++ b/policycoreutils/Makefile
@@ -1,4 +1,4 @@
-SUBDIRS = setfiles semanage load_policy newrole run_init secon audit2allow audit2why scripts sestatus semodule_package semodule semodule_link semodule_expand semodule_deps setsebool po
+SUBDIRS = setfiles semanage load_policy newrole run_init sandbox secon audit2allow audit2why scripts sestatus semodule_package semodule semodule_link semodule_expand semodule_deps setsebool po
 
 INOTIFYH = $(shell ls /usr/include/sys/inotify.h 2>/dev/null)
 
diff --git a/policycoreutils/sandbox/Makefile b/policycoreutils/sandbox/Makefile
new file mode 100644
index 0000000..ff0ee7c
--- /dev/null
+++ b/policycoreutils/sandbox/Makefile
@@ -0,0 +1,41 @@
+# Installation directories.
+PREFIX ?= ${DESTDIR}/usr
+INITDIR ?= ${DESTDIR}/etc/rc.d/init.d/
+SYSCONFDIR ?= ${DESTDIR}/etc/sysconfig
+BINDIR ?= $(PREFIX)/bin
+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 
+
+all: sandbox seunshare sandboxX.sh 
+
+seunshare: seunshare.o $(EXTRA_OBJS)
+	$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+install: all
+	-mkdir -p $(BINDIR)
+	install -m 755 sandbox $(BINDIR)
+	-mkdir -p $(MANDIR)/man8
+	install -m 644 sandbox.8 $(MANDIR)/man8/
+	-mkdir -p $(SBINDIR)
+	install -m 4755 seunshare $(SBINDIR)/
+	-mkdir -p $(SHAREDIR)
+	install -m 755 sandboxX.sh $(SHAREDIR)
+	-mkdir -p $(INITDIR)
+	install -m 755 sandbox.init $(INITDIR)/sandbox
+	-mkdir -p $(SYSCONFDIR)
+	install -m 644 sandbox.config $(SYSCONFDIR)/sandbox
+
+test:
+	@python test_sandbox.py -v
+
+clean:
+	-rm -f seunshare *.o *~
+
+indent:
+	../../scripts/Lindent $(wildcard *.[ch])
+
+relabel:
diff --git a/policycoreutils/sandbox/sandbox b/policycoreutils/sandbox/sandbox
new file mode 100644
index 0000000..0124f6c
--- /dev/null
+++ b/policycoreutils/sandbox/sandbox
@@ -0,0 +1,430 @@
+#! /usr/bin/python -E
+# Authors: Dan Walsh <dwalsh@xxxxxxxxxx>
+# Authors: Josh Cogliati
+#
+# Copyright (C) 2009,2010  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import os, sys, socket, random, fcntl, shutil, re, subprocess
+import selinux
+import signal
+from tempfile import mkdtemp
+import pwd
+import commands 
+
+PROGNAME = "policycoreutils"
+HOMEDIR=pwd.getpwuid(os.getuid()).pw_dir
+
+import gettext
+gettext.bindtextdomain(PROGNAME, "/usr/share/locale")
+gettext.textdomain(PROGNAME)
+
+try:
+       gettext.install(PROGNAME,
+                       localedir = "/usr/share/locale",
+                       unicode=False,
+                       codeset = 'utf-8')
+except IOError:
+       import __builtin__
+       __builtin__.__dict__['_'] = unicode
+
+DEFAULT_TYPE = "sandbox_t"
+DEFAULT_X_TYPE = "sandbox_x_t"
+SAVE_FILES = {}
+
+random.seed(None)
+
+def sighandler(signum, frame):
+    signal.signal(signum,  signal.SIG_IGN)
+    os.kill(0, signum)
+    raise KeyboardInterrupt
+
+def setup_sighandlers():
+    signal.signal(signal.SIGHUP,  sighandler)
+    signal.signal(signal.SIGQUIT, sighandler)
+    signal.signal(signal.SIGTERM, sighandler)
+
+def error_exit(msg):
+    sys.stderr.write("%s: " % sys.argv[0])
+    sys.stderr.write("%s\n" % msg)
+    sys.stderr.flush()
+    sys.exit(1)
+
+def chcon(path, context, recursive=False):
+    """ Restore SELinux context on a given path """
+    mode = os.lstat(path)[stat.ST_MODE]
+    lsetfilecon(path, context)
+    if recursive:
+           os.path.walk(path, lambda arg, dirname, fnames:
+                               map(chcon, [os.path.join(dirname, fname)
+                                                for fname in fnames]), context)
+def copyfile(file, dir, dest):
+       import re
+       if file.startswith(dir):
+              dname = os.path.dirname(file)
+              bname = os.path.basename(file)
+              if dname == dir:
+                     dest = dest + "/" + bname
+              else:
+                     newdir = re.sub(dir, dest, dname)
+                     if not os.path.exists(newdir):
+                            os.makedirs(newdir)
+                     dest = newdir + "/" + bname
+
+              try:
+                     if os.path.isdir(file):
+                            shutil.copytree(file, dest)
+                     else:
+                            shutil.copy2(file, dest)
+              except shutil.Error, elist:
+                     for e in elist:
+                            sys.stderr.write(e[1])
+                     
+              SAVE_FILES[file] = (dest, os.path.getmtime(dest))
+
+def savefile(new, orig, X_ind):
+       copy = False
+       if(X_ind):
+              import gtk
+              dlg = gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO,
+                                      gtk.BUTTONS_YES_NO,
+                                      _("Do you want to save changes to '%s' (Y/N): ") % orig)
+              dlg.set_title(_("Sandbox Message"))
+              dlg.set_position(gtk.WIN_POS_MOUSE)
+              dlg.show_all()
+              rc = dlg.run()
+              dlg.destroy()
+              if rc == gtk.RESPONSE_YES:
+                     copy = True
+       else:
+              ans = raw_input(_("Do you want to save changes to '%s' (y/N): ") % orig)
+              if(re.match(_("[yY]"),ans)):
+                     copy = True
+       if(copy):
+              shutil.copy2(new,orig)
+
+def reserve(level):
+    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    sock.bind("\0%s" % level)
+    fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)
+
+def gen_mcs():
+       while True:
+              i1 = random.randrange(0, 1024)
+              i2 = random.randrange(0, 1024)
+              if i1 == i2:
+                     continue
+              if i1 > i2:
+                     tmp = i1
+                     i1 = i2
+                     i2 = tmp
+              level = "s0:c%d,c%d" % (i1, i2)
+              try:
+                     reserve(level)
+              except socket.error:
+                     continue
+              break
+       return level
+
+def fullpath(cmd):
+       for i in [ "/", "./", "../" ]:
+              if cmd.startswith(i):
+                     return cmd
+       for i in  os.environ["PATH"].split(':'):
+              f = "%s/%s" % (i, cmd)
+              if os.access(f, os.X_OK):
+                     return f
+       return cmd
+
+class Sandbox:
+    VERSION = "sandbox .1"
+    SYSLOG = "/var/log/messages"
+
+    def __init__(self):
+        self.setype = DEFAULT_TYPE
+        self.__options = None
+        self.__cmds = None
+        self.__init_files = []
+        self.__paths = []
+        self.__mount = False
+        self.__level = None
+        self.__homedir = None
+        self.__tmpdir = None
+
+    def __validate_mount(self):
+           if self.__options.level:
+                  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"):
+                  raise ValueError(_("""
+/usr/sbin/seunshare is required for the action you want to perform.  
+Install seunshare by executing:
+
+# yum install /usr/sbin/seunshare
+
+"""))
+
+    def __mount_callback(self, option, opt, value, parser):
+           self.__mount = True
+
+    def __x_callback(self, option, opt, value, parser):
+           self.__mount = True
+           setattr(parser.values, option.dest, True)
+
+    def __validdir(self, option, opt, value, parser):
+           if not os.path.isdir(value):
+                  raise IOError("Directory "+value+" not found")
+           setattr(parser.values, option.dest, value)
+           self.__mount = True
+
+    def __include(self, option, opt, value, parser):
+           rp = os.path.realpath(os.path.expanduser(value))
+           if not os.path.exists(rp):
+                  raise IOError(value+" not found")
+
+           if rp not in self.__init_files:
+                  self.__init_files.append(rp)
+
+    def __includefile(self, option, opt, value, parser):
+           fd = open(value, "r")
+           for i in fd.readlines():
+                  try:
+                         self.__include(option, opt, i[:-1], parser)
+                  except IOError, e:
+                         sys.stderr.write(e)
+           fd.close()
+
+    def __copyfiles(self):
+           files = self.__init_files + self.__paths
+           homedir=pwd.getpwuid(os.getuid()).pw_dir
+           for f in files:
+                  copyfile(f, homedir, self.__homedir)
+                  copyfile(f, "/tmp", self.__tmpdir)
+
+    def __setup_sandboxrc(self, wm = "/usr/bin/matchbox-window-manager -use_titlebar no"):
+           execfile =self.__homedir + "/.sandboxrc"
+           fd = open(execfile, "w+") 
+           if self.__options.session:
+                  fd.write("""#!/bin/sh
+#TITLE: /etc/gdm/Xsession
+/etc/gdm/Xsession
+""")
+           else:
+                  command = " ".join(self.__paths)
+                  fd.write("""#! /bin/sh
+#TITLE: %s
+/usr/bin/test -r ~/.xmodmap && /usr/bin/xmodmap ~/.xmodmap
+%s &
+WM_PID=$!
+%s
+kill -TERM $WM_PID  2> /dev/null
+""" % (command, wm, command))
+           fd.close()
+           os.chmod(execfile, 0700)
+
+    def usage(self, message = ""):
+           error_exit("%s\n%s" % (self.__parser.usage, message))
+
+    def __parse_options(self):
+        from optparse import OptionParser
+        usage = _("""
+sandbox [-h] [-[X|M] [-l level ] [-H homedir] [-T tempdir]] [-I includefile ] [-W windowmanager ] [[-i file ] ...] [ -t type ] command
+
+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()
+        parser.add_option("-i", "--include", 
+                          action="callback", callback=self.__include, 
+                          type="string",
+                          help=_("include file in sandbox"))
+        parser.add_option("-I", "--includefile",  action="callback", callback=self.__includefile,
+                          type="string",
+                          help=_("read list of files to include in sandbox from INCLUDEFILE"))
+        parser.add_option("-t", "--type", dest="setype", action="store", default=None,
+                          help=_("run sandbox with SELinux type"))
+        parser.add_option("-M", "--mount", 
+                          action="callback", callback=self.__mount_callback, 
+                          help=_("mount new home and/or tmp directory"))
+
+        parser.add_option("-S", "--session", action="store_true",  dest="session", 
+                          default=False,  help=_("run complete desktop session within sandbox"))
+
+        parser.add_option("-X", dest="X_ind", 
+                          action="callback", callback=self.__x_callback, 
+                          default=False,  help=_("run X application within a sandbox"))
+
+        parser.add_option("-H", "--homedir", 
+                          action="callback", callback=self.__validdir,
+                          type="string",
+                          dest="homedir",  
+                          help=_("alternate home directory to use for mounting"))
+
+        parser.add_option("-T", "--tmpdir", dest="tmpdir",  
+                          type="string",
+                          action="callback", callback=self.__validdir,
+                          help=_("alternate /tmp directory to use for mounting"))
+
+        parser.add_option("-W", "--windowmanager", dest="wm",  
+                          type="string",
+                          default="/usr/bin/matchbox-window-manager -use_titlebar no",
+                          help=_("alternate window manager"))
+
+        parser.add_option("-l", "--level", dest="level", 
+                          help=_("MCS/MLS level for the sandbox"))
+
+        self.__parser=parser
+
+        self.__options, cmds = parser.parse_args()
+
+        if self.__options.X_ind:
+               self.setype = DEFAULT_X_TYPE
+        
+        if self.__options.setype:
+               self.setype = self.__options.setype
+
+        if self.__mount:
+               self.__validate_mount()
+
+        if self.__options.session:
+               if not self.__options.setype:
+                      self.setype = selinux.getcon()[1].split(":")[2]
+               if not self.__options.homedir or not self.__options.tmpdir:
+                      self.usage(_("You must specify a Homedir and tempdir when setting up a session sandbox"))
+               if len(cmds) > 0:
+                      self.usage(_("Commands are not allowed in a session sandbox"))
+               self.__options.X_ind = True
+               self.__homedir = self.__options.homedir
+               self.__tmpdir = self.__options.tmpdir
+        else:
+               if len(cmds) == 0:
+                      self.usage(_("Command required"))
+               cmds[0] = fullpath(cmds[0])
+               if not os.access(cmds[0], os.X_OK):
+                      self.usage(_("%s is not an executable") % cmds[0]  )
+                      
+               self.__cmds = cmds
+
+        for f in cmds:
+               rp = os.path.realpath(f)
+               if os.path.exists(rp):
+                      self.__paths.append(rp)
+               else:
+                      self.__paths.append(f)
+                  
+    def __gen_context(self):
+           if self.__options.level:
+                  level = self.__options.level
+           else:
+                  level = gen_mcs()
+
+           con = selinux.getcon()[1].split(":")
+           self.__execcon = "%s:%s:%s:%s" % (con[0], con[1], self.setype, level)
+           self.__filecon = "%s:%s:%s:%s" % (con[0], "object_r", 
+                                             "%s_file_t" % self.setype[:-2], 
+                                             level)
+    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:
+                  chcon(self.__options.homedir, self.__filecon, True)
+                  self.__homedir = self.__options.homedir
+           else:
+                  selinux.setfscreatecon(self.__filecon)
+                  self.__homedir = mkdtemp(dir=sandboxdir, prefix=".sandbox")
+
+           if self.__options.tmpdir:
+                  chcon(self.__options.homedir, self.__filecon, True)
+                  self.__tmpdir = self.__options.tmpdir
+           else:
+                  selinux.setfscreatecon(self.__filecon)
+                  self.__tmpdir = mkdtemp(dir="/tmp", prefix=".sandbox")
+           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 "%s" -h "%s" -- %s /usr/share/sandbox/sandboxX.sh' % (self.__tmpdir, self.__homedir, self.__execcon)).split()
+                         rc = subprocess.Popen(cmds).wait()
+                         return rc
+
+                  if self.__mount:
+                         cmds =  ('/usr/sbin/seunshare -t "%s" -h "%s" -- %s ' % (self.__tmpdir, self.__homedir, self.__execcon)).split()+self.__paths
+                         rc = subprocess.Popen(cmds).wait()
+                         return rc
+
+                  selinux.setexeccon(self.__execcon)
+                  rc = subprocess.Popen(self.__cmds).wait()
+                  selinux.setexeccon(None)
+                  return rc
+
+           finally:
+                  for i in self.__paths:
+                         if i not in SAVE_FILES:
+                                continue
+                         (dest, mtime) = SAVE_FILES[i]
+                         if os.path.getmtime(dest) > mtime:
+                                savefile(dest, i, self.__options.X_ind)
+
+                  if self.__homedir and not self.__options.homedir: 
+                         shutil.rmtree(self.__homedir)
+                  if self.__tmpdir and not self.__options.tmpdir:
+                         shutil.rmtree(self.__tmpdir)
+    def main(self):
+        try:
+               self.__parse_options()
+               self.__gen_context()
+               self.__setup_dir()
+               return self.__execute()
+        except KeyboardInterrupt:
+            sys.exit(0)
+
+
+if __name__ == '__main__':
+    setup_sighandlers()
+    if selinux.is_selinux_enabled() != 1:
+        error_exit("Requires an SELinux enabled system")
+    
+    try:
+           sandbox = Sandbox()
+           rc = sandbox.main()
+    except OSError, error:
+           error_exit(error.args[1])
+    except ValueError, error:
+           error_exit(error.args[0])
+    except KeyError, error:
+           error_exit(_("Invalid value %s") % error.args[0])
+    except IOError, error:
+           error_exit(error)
+    except KeyboardInterrupt:
+           rc = 0
+           
+    sys.exit(rc)
diff --git a/policycoreutils/sandbox/sandbox.8 b/policycoreutils/sandbox/sandbox.8
new file mode 100644
index 0000000..1479364
--- /dev/null
+++ b/policycoreutils/sandbox/sandbox.8
@@ -0,0 +1,57 @@
+.TH SANDBOX "8" "May 2009" "chcat" "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
+.br
+.SH DESCRIPTION
+.PP
+Run the 
+.I cmd 
+application within a tightly confined SELinux domain.  The default sandbox domain only allows applications the ability to read and write stdin, stdout and any other file descriptors handed to it. It is not allowed to open any other files.  The -M option will mount an alternate homedir and tmpdir to be used by the sandbox.
+
+If you have the 
+.I policycoreutils-sandbox 
+package installed, you can use the -X option and the -M option.
+.B sandbox -X
+allows you to run X applications within a sandbox.  These applications will start up their own X Server and create a temporary home directory and /tmp.  The default SELinux policy does not allow any capabilities or network access.  It also prevents all access to the users other processes and files.  Files specified on the command that are in the home directory or /tmp will be copied into the sandbox directories.
+
+If directories are specified with -H or -T the directory will have its context modified with chcon(1) unless a level is specified with -l.  If the MLS/MCS security level is specified, the user is responsible to set the correct labels.
+.PP
+.TP
+\fB\-H\ homedir
+Use alternate homedir to mount over your home directory.  Defaults to temporary. Requires -X or -M.
+.TP
+\fB\-i file\fR
+Copy this file into the appropriate temporary sandbox directory. Command can be repeated.
+.TP
+\fB\-I inputfile\fR Copy all files listed in inputfile into the
+appropriate temporary sandbox directories.
+.TP
+\fB\-l\fR
+Specify the MLS/MCS Security Level to run the sandbox with.  Defaults to random.
+.TP
+\fB\-M\fR
+Create a Sandbox with temporary files for $HOME and /tmp.
+.TP
+\fB\-t type\fR
+Use alternate sandbox type, defaults to sandbox_t or sandbox_x_t for -X.
+.TP
+\fB\-T\ tmpdir
+Use alternate tempory directory to mount on /tmp.  Defaults to tmpfs. Requires -X or -M.
+.TP
+\fB\-W windowmanager\fR
+Select alternative window manager to run within 
+.B sandbox -X.
+Default to /usr/bin/matchbox-window-manager.
+.TP
+\fB\-X\fR 
+Create an X based Sandbox for gui apps, temporary files for
+$HOME and /tmp, secondary Xserver, defaults to sandbox_x_t
+.PP
+.SH "SEE ALSO"
+.TP
+runcon(1)
+.PP
diff --git a/policycoreutils/sandbox/sandbox.config b/policycoreutils/sandbox/sandbox.config
new file mode 100644
index 0000000..f9f059a
--- /dev/null
+++ b/policycoreutils/sandbox/sandbox.config
@@ -0,0 +1,2 @@
+# Space separate list of homedirs
+HOMEDIRS="/home"
diff --git a/policycoreutils/sandbox/sandbox.init b/policycoreutils/sandbox/sandbox.init
new file mode 100644
index 0000000..44867d1
--- /dev/null
+++ b/policycoreutils/sandbox/sandbox.init
@@ -0,0 +1,74 @@
+#!/bin/bash
+## BEGIN INIT INFO
+# Provides: sandbox
+# Default-Start: 3 4 5
+# Default-Stop: 0 1 2 3 4 6
+# Required-Start:
+#              
+## END INIT INFO
+# sandbox:        Set up / mountpoint to be shared, /var/tmp, /tmp, /home/sandbox unshared
+#
+# 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.
+#
+
+# Source function library.
+. /etc/init.d/functions
+
+HOMEDIRS="/home"
+
+. /etc/sysconfig/sandbox
+
+LOCKFILE=/var/lock/subsys/sandbox
+
+base=${0##*/}
+
+case "$1" in
+    restart)
+    start)
+	[ -f "$LOCKFILE" ] && exit 0
+
+	touch $LOCKFILE
+	mount --make-rshared /
+	mount --rbind /tmp /tmp
+	mount --rbind /var/tmp /var/tmp
+	mount --make-private /tmp
+	mount --make-private /var/tmp
+	for h in $HOMEDIRS; do
+	    mount --rbind $h $h 
+	    mount --make-private $h
+	done
+
+	exit $?
+	;;
+
+    status)
+	if [ -f "$LOCKFILE" ]; then 
+	    echo "$base is running"
+	else
+	    echo "$base is stopped"
+	fi
+	exit 0
+	;;
+
+    stop)
+	rm -f $LOCKFILE
+	exit 0
+	;;
+
+    *)
+	echo $"Usage: $0 {start|stop|status|restart}"
+	exit 3
+	;;
+esac
diff --git a/policycoreutils/sandbox/sandboxX.sh b/policycoreutils/sandbox/sandboxX.sh
new file mode 100644
index 0000000..ed318d0
--- /dev/null
+++ b/policycoreutils/sandbox/sandboxX.sh
@@ -0,0 +1,15 @@
+#!/bin/bash 
+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 }'`
+trap "exit 0" HUP
+
+(/usr/bin/Xephyr -title "$TITLE" -terminate -screen $SCREENSIZE -displayfd 5 5>&1 2>/dev/null) | while read D; do 
+    export DISPLAY=:$D
+    python -c 'import gtk, os; os.system("%s/.sandboxrc" % os.environ["HOME"])'
+    export EXITCODE=$?
+    kill -HUP 0
+    break
+done
+exit 0
diff --git a/policycoreutils/sandbox/seunshare.c b/policycoreutils/sandbox/seunshare.c
new file mode 100644
index 0000000..848f787
--- /dev/null
+++ b/policycoreutils/sandbox/seunshare.c
@@ -0,0 +1,304 @@
+#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>
+#include <unistd.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 <selinux/selinux.h>
+#include <selinux/context.h>	/* for context-mangling functions */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef USE_NLS
+#include <locale.h>		/* for setlocale() */
+#include <libintl.h>		/* for gettext() */
+#define _(msgid) gettext (msgid)
+#else
+#define _(msgid) (msgid)
+#endif
+
+/**
+ * This function will drop all capabilities 
+ * Returns zero on success, non-zero otherwise
+ */
+static int drop_capabilities(uid_t uid)
+{
+	capng_clear(CAPNG_SELECT_BOTH);
+
+	if (capng_lock() < 0) 
+		return -1;
+	/* Change uid */
+	if (setresuid(uid, uid, uid)) {
+		fprintf(stderr, _("Error changing uid, aborting.\n"));
+		return -1;
+	}
+	return capng_apply(CAPNG_SELECT_BOTH);
+}
+
+#define DEFAULT_PATH "/usr/bin:/bin"
+static	int verbose = 0;
+
+/**
+ * Take care of any signal setup
+ */
+static int set_signal_handles(void)
+{
+	sigset_t empty;
+
+	/* Empty the signal mask in case someone is blocking a signal */
+	if (sigemptyset(&empty)) {
+		fprintf(stderr, "Unable to obtain empty signal set\n");
+		return -1;
+	}
+
+	(void)sigprocmask(SIG_SETMASK, &empty, NULL);
+
+	/* Terminate on SIGHUP. */
+	if (signal(SIGHUP, SIG_DFL) == SIG_ERR) {
+		perror("Unable to set SIGHUP handler");
+		return -1;
+	}
+
+	return 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.
+ */
+static int verify_mount(const char *mntdir, struct passwd *pwd) {
+	struct stat sb;
+	if (stat(mntdir, &sb) == -1) {
+		fprintf(stderr, _("Invalid mount point %s: %s\n"), mntdir, 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"));
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * This function checks to see if the shell is known in /etc/shells.
+ * If so, it returns 0. On error or illegal shell, it returns -1.
+ */
+static int verify_shell(const char *shell_name)
+{
+	int rc = -1;
+	const char *buf;
+
+	if (!(shell_name && shell_name[0]))
+		return rc;
+
+	while ((buf = getusershell()) != NULL) {
+		/* ignore comments */
+		if (*buf == '#')
+			continue;
+
+		/* check the shell skipping newline char */
+		if (!strcmp(shell_name, buf)) {
+			rc = 1;
+			break;
+		}
+	}
+	endusershell();
+	return rc;
+}
+
+static int seunshare_mount(const char *src, const char *dst, struct passwd *pwd) {
+	if (verbose)
+		printf("Mount %s on %s\n", src, dst);
+	if (mount(dst, dst,  NULL, MS_BIND, 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, NULL) < 0) {
+		fprintf(stderr, _("Failed to make %s private: %s\n"), dst, strerror(errno));
+		return -1;
+	}
+
+	if (mount(src, dst, NULL, MS_BIND, NULL) < 0) {
+		fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno));
+		return -1;
+	}
+
+	if (verify_mount(dst, pwd) < 0) 
+		return -1;
+}
+
+#define USAGE_STRING _("USAGE: seunshare [ -v ] [ -t tmpdir ] [ -h homedir ] -- CONTEXT executable [args] ")
+
+int main(int argc, char **argv) {
+	int rc;
+	int status = -1;
+
+	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[] */
+	char *homedir_s = NULL;	/* homedir spec'd by user in argv[] */
+
+	const struct option long_options[] = {
+		{"homedir", 1, 0, 'h'},
+		{"tmpdir", 1, 0, 't'},
+		{"verbose", 1, 0, 'v'},
+		{NULL, 0, 0, 0}
+	};
+
+	uid_t uid = getuid();
+
+	if (!uid) {
+		fprintf(stderr, _("Must not be root"));
+		return -1;
+	}
+
+	struct passwd *pwd=getpwuid(uid);
+	if (!pwd) {
+		perror(_("getpwduid failed"));
+		return -1;
+	}
+
+	if (verify_shell(pwd->pw_shell) < 0) {
+		fprintf(stderr, _("Error!  Shell is not valid.\n"));
+		return -1;
+	}
+
+	while (1) {
+		clflag = getopt_long(argc, argv, "h:t:", long_options,
+				     &flag_index);
+		if (clflag == -1)
+			break;
+
+		switch (clflag) {
+		case 't':
+			tmpdir_s = optarg;
+			if (verify_mount(tmpdir_s, pwd) < 0) return -1;
+			break;
+		case 'h':
+			homedir_s = optarg;
+			if (verify_mount(homedir_s, pwd) < 0) return -1;
+			if (verify_mount(pwd->pw_dir, pwd) < 0) return -1;
+			break;
+		case 'v':
+			verbose = 1;
+			break;
+		default:
+			fprintf(stderr, "%s\n", USAGE_STRING);
+			return -1;
+		}
+	}
+
+	if (! homedir_s && ! tmpdir_s) {
+		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);
+		return -1;
+	}
+
+	scontext = argv[optind++];
+	
+	if (set_signal_handles())
+		return -1;
+
+        if (unshare(CLONE_NEWNS) < 0) {
+		perror(_("Failed to unshare"));
+		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 (drop_capabilities(uid)) {
+		perror(_("Failed to drop all capabilities"));
+		return -1;
+	}
+
+	int child = fork();
+	if (child == -1) {
+		perror(_("Unable to fork"));
+		return -1;
+	}
+
+	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 ((rc = clearenv())) {
+			perror(_("Unable to clear environment"));
+			free(display);
+			exit(-1);
+		}
+		
+		if (setexeccon(scontext)) {
+			fprintf(stderr, _("Could not set exec context to %s.\n"),
+				scontext);
+			free(display);
+			exit(-1);
+		}
+
+		if (display) 
+			rc |= setenv("DISPLAY", display, 1);
+		rc |= setenv("HOME", pwd->pw_dir, 1);
+		rc |= setenv("SHELL", pwd->pw_shell, 1);
+		rc |= setenv("USER", pwd->pw_name, 1);
+		rc |= setenv("LOGNAME", pwd->pw_name, 1);
+		rc |= setenv("PATH", DEFAULT_PATH, 1);
+		
+		if (chdir(pwd->pw_dir)) {
+			perror(_("Failed to change dir to homedir"));
+			exit(-1);
+		}
+		setsid();
+		execv(argv[optind], argv + optind);
+		free(display);
+		perror("execv");
+		exit(-1);
+	} else {
+		waitpid(child, &status, 0);
+	}
+
+	return status;
+}
diff --git a/policycoreutils/sandbox/test_sandbox.py b/policycoreutils/sandbox/test_sandbox.py
new file mode 100644
index 0000000..b3b7f64
--- /dev/null
+++ b/policycoreutils/sandbox/test_sandbox.py
@@ -0,0 +1,98 @@
+import unittest, os, shutil 
+from tempfile import mkdtemp
+from subprocess import Popen, PIPE
+
+class SandboxTests(unittest.TestCase):
+    def assertDenied(self, err):
+        self.assert_('Permission denied' in err,
+                     '"Permission denied" not found in %r' % err)
+    def assertNotFound(self, err):
+        self.assert_('not found' in err,
+                     '"not found" not found in %r' % err)
+
+    def assertFailure(self, status):
+        self.assert_(status != 0,
+                     '"Succeeded when it should have failed')
+
+    def assertSuccess(self, status, err):
+        self.assert_(status == 0,
+                     '"Sandbox should have succeeded for this test %r' %  err)
+
+    def test_simple_success(self):
+        "Verify that we can read file descriptors handed to sandbox"
+        p1 = Popen(['cat', '/etc/passwd'], stdout = PIPE)
+        p2 = Popen(['sandbox', 'grep', 'root'], stdin = p1.stdout, stdout=PIPE)
+        out, err = p2.communicate()
+        self.assert_('root' in out)
+
+    def test_cant_kill(self):
+        "Verify that we cannot send kill signal in the sandbox"
+        pid = os.getpid()
+        p = Popen(['sandbox', 'kill', '-HUP', str(pid)], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertDenied(err)
+
+    def test_cant_ping(self):
+        "Verify that we can't ping within the sandbox"
+        p = Popen(['sandbox', 'ping', '-c 1 ', '127.0.0.1'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertDenied(err)
+    
+    def test_cant_mkdir(self):
+        "Verify that we can't mkdir within the sandbox"
+        p = Popen(['sandbox', 'mkdir', '~/test'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertFailure(p.returncode)
+
+    def test_cant_list_homedir(self):
+        "Verify that we can't list homedir within the sandbox"
+        p = Popen(['sandbox', 'ls', '~'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertFailure(p.returncode)
+
+    def test_cant_send_mail(self):
+        "Verify that we can't send mail within the sandbox"
+        p = Popen(['sandbox', 'mail'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertDenied(err)
+    
+    def test_cant_sudo(self):
+        "Verify that we can't run sudo within the sandbox"
+        p = Popen(['sandbox', 'sudo'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertFailure(p.returncode)
+    
+    def test_mount(self):
+        "Verify that we mount a file system"
+        p = Popen(['sandbox', '-M', 'id'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertSuccess(p.returncode, err)
+    
+    def test_set_level(self):
+        "Verify that we set level a file system"
+        p = Popen(['sandbox', '-l', 's0', 'id'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        self.assertSuccess(p.returncode, err)
+    
+    def test_homedir(self):
+        "Verify that we set homedir a file system"
+        homedir = mkdtemp(dir=".", prefix=".sandbox_test")
+        p = Popen(['sandbox', '-H', homedir, '-M', 'id'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        shutil.rmtree(homedir)
+        self.assertSuccess(p.returncode, err)
+    
+    def test_tmpdir(self):
+        "Verify that we set tmpdir a file system"
+        tmpdir = mkdtemp(dir="/tmp", prefix=".sandbox_test")
+        p = Popen(['sandbox', '-T', tmpdir, '-M', 'id'], stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+        shutil.rmtree(tmpdir)
+        self.assertSuccess(p.returncode, err)
+    
+if __name__ == "__main__":
+    import selinux
+    if selinux.security_getenforce() == 1:
+        unittest.main()
+    else:
+        print "SELinux must be in enforcing mode for this test"

Attachment: policycoreutils-sandbox.patch.sig
Description: PGP signature


[Index of Archives]     [Selinux Refpolicy]     [Linux SGX]     [Fedora Users]     [Fedora Desktop]     [Yosemite Photos]     [Yosemite Camping]     [Yosemite Campsites]     [KDE Users]     [Gnome Users]

  Powered by Linux