platform_external_selinux/policycoreutils/sandbox/sandbox
Eric Paris 0b8af757b6 policycoreutils: Don't add user site directory to sys.path
SELinux pythons applications should not allow the user to change the
sys.path

Signed-off-by: Eric Paris <eparis@redhat.com>
Acked-by: Dan Walsh <dwalsh@redhat.com>
2011-08-03 18:02:36 -04:00

417 lines
15 KiB
Python

#! /usr/bin/python -Es
# Authors: Dan Walsh <dwalsh@redhat.com>
# 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
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 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.
"""))
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(str(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:
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")
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")
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/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
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)