From 89933a4cb02a1609256c1f1ccdeddc106b5eaf1a Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 9 May 2012 11:33:36 -0700 Subject: [PATCH 01/46] add preliminary aa-sandbox which starts an X application in Xephyr. Currently does not add policy --- utils/aa-sandbox | 31 +++++++ utils/apparmor/common.py | 86 ++++++++++++++++++++ utils/apparmor/sandbox.py | 135 +++++++++++++++++++++++++++++++ utils/easyprof/templates/sandbox | 26 ++++++ 4 files changed, 278 insertions(+) create mode 100755 utils/aa-sandbox create mode 100644 utils/apparmor/common.py create mode 100644 utils/apparmor/sandbox.py create mode 100644 utils/easyprof/templates/sandbox diff --git a/utils/aa-sandbox b/utils/aa-sandbox new file mode 100755 index 000000000..428387920 --- /dev/null +++ b/utils/aa-sandbox @@ -0,0 +1,31 @@ +#! /usr/bin/env python +# ------------------------------------------------------------------ +# +# Copyright (C) 2012 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License published by the Free Software Foundation. +# +# ------------------------------------------------------------------ + +import apparmor.sandbox +from apparmor.common import AppArmorException, error +import os +import sys + +if __name__ == "__main__": + argv = sys.argv + (opt, args) = apparmor.sandbox.parse_args(sys.argv) + + if len(args) < 1: + error("Must specify binary") + + if not apparmor.sandbox.check_requirements(args[0]): + sys.exit(1) + + if opt.withx: + apparmor.sandbox.run_xsandbox(opt.resolution, args) + else: + # TODO + sys.exit(1) diff --git a/utils/apparmor/common.py b/utils/apparmor/common.py new file mode 100644 index 000000000..7217f091e --- /dev/null +++ b/utils/apparmor/common.py @@ -0,0 +1,86 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2012 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License published by the Free Software Foundation. +# +# ------------------------------------------------------------------ + +import subprocess +import sys + +DEBUGGING = False +DEBUGGING = True + +# +# Utility classes +# +class AppArmorException(Exception): + '''This class represents AppArmor exceptions''' + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +# +# Utility functions +# +def error(out, exit_code=1, do_exit=True): + '''Print error message and exit''' + try: + print >> sys.stderr, "ERROR: %s" % (out) + except IOError: + pass + + if do_exit: + sys.exit(exit_code) + +def warn(out): + '''Print warning message''' + try: + print >> sys.stderr, "WARN: %s" % (out) + except IOError: + pass + +def msg(out, output=sys.stdout): + '''Print message''' + try: + print >> output, "%s" % (out) + except IOError: + pass + +def debug(out): + '''Print debug message''' + global DEBUGGING + if DEBUGGING: + try: + print >> sys.stderr, "DEBUG: %s" % (out) + except IOError: + pass + +def cmd(command): + '''Try to execute the given command.''' + debug(command) + try: + sp = subprocess.Popen(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + except OSError, ex: + return [127, str(ex)] + + out = sp.communicate()[0] + return [sp.returncode, out] + +def cmd_pipe(command1, command2): + '''Try to pipe command1 into command2.''' + try: + sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) + sp2 = subprocess.Popen(command2, stdin=sp1.stdout) + except OSError, ex: + return [127, str(ex)] + + out = sp2.communicate()[0] + return [sp2.returncode, out] + diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py new file mode 100644 index 000000000..e5abd38ee --- /dev/null +++ b/utils/apparmor/sandbox.py @@ -0,0 +1,135 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2011-2012 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License published by the Free Software Foundation. +# +# ------------------------------------------------------------------ + +from apparmor.common import AppArmorException, debug, error, cmd +import optparse +import os +import sys +import time + +global DEBUGGING + +def check_requirements(binary): + '''Verify necessary software is installed''' + exes = ['Xephyr', 'matchbox-window-manager', binary] + for e in exes: + debug("Searching for '%s'" % e) + rc, report = cmd(['which', e]) + if rc != 0: + error("Could not find '%s'" % e, do_exit=False) + return False + return True + +def parse_args(args=None): + '''Parse arguments''' + global DEBUGGING + + parser = optparse.OptionParser() + parser.add_option('-X', '--with-x', + dest='withx', + default=False, + help='Run in isolated X server', + action='store_true') + parser.add_option('-d', '--debug', + dest='debug', + default=False, + help='Show debug messages', + action='store_true') + parser.add_option('-r', '--with-resolution', + dest='resolution', + default='640x480', + help='Resolution for X application') + + (my_opt, my_args) = parser.parse_args() + if my_opt.debug: + DEBUGGING = True + return (my_opt, my_args) + +def find_free_x_display(): + # TODO: detect/track and get an available display + x_display = ":1" + return x_display + +def run_xsandbox(resolution, command): + '''Run X application in a sandbox''' + # Find a display to run on + x_display = find_free_x_display() + + debug (os.environ["DISPLAY"]) + + # first, start X + listener_x = os.fork() + if listener_x == 0: + # TODO: break into config file? Which are needed? + x_exts = ['-extension', 'GLX', + '-extension', 'MIT-SHM', + '-extension', 'RENDER', + '-extension', 'SECURITY', + '-extension', 'DAMAGE' + ] + # verify_these + x_extra_args = ['-host-cursor', # less secure? + '-fakexa', # for games? seems not needed + '-nodri', # more secure? + ] + + x_args = ['-nolisten', 'tcp', + '-screen', resolution, + '-br', # black background + '-reset', # reset after last client exists + '-terminate', # terminate at server reset + '-title', command[0], + ] + x_exts + x_extra_args + + args = ['/usr/bin/Xephyr'] + x_args + [x_display] + debug(" ".join(args)) + sys.stderr.flush() + os.execv(args[0], args) + sys.exit(0) + + # save environment + old_display = os.environ["DISPLAY"] + old_cwd = os.getcwd() + + # update environment + os.environ["DISPLAY"] = x_display + debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) + + time.sleep(0.2) # FIXME: detect if running + + # Next, start the window manager + sys.stdout.flush() + os.chdir(os.environ["HOME"]) + listener_wm = os.fork() + if listener_wm == 0: + args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] + debug(" ".join(args)) + sys.stderr.flush() + os.execv(args[0], args) + sys.exit(0) + + time.sleep(0.2) # FIXME: detect if running + cmd(command) + + # reset environment + os.environ["DISPLAY"] = old_display + debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) + + os.chdir(old_cwd) + + # kill server now. It should've terminated, but be sure + cmd(['kill', '-15', "%d" % listener_wm]) + os.kill(listener_wm, 15) + os.waitpid(listener_wm, 0) + cmd(['kill', '-15', "%d" % listener_x]) + os.kill(listener_x, 15) + os.waitpid(listener_x, 0) + + diff --git a/utils/easyprof/templates/sandbox b/utils/easyprof/templates/sandbox new file mode 100644 index 000000000..618c7efaf --- /dev/null +++ b/utils/easyprof/templates/sandbox @@ -0,0 +1,26 @@ +# +# Example usage for a program named 'foo' which is installed in /opt/foo +# $ aa-easyprof --template=sandbox \ +# --template-var="@{APPNAME}=foo" \ +# --policy-groups=opt-application,user-application \ +# /opt/foo/bin/foo +# +###ENDUSAGE### +# vim:syntax=apparmor +# AppArmor policy for ###NAME### + +#include + +###VAR### + +###BINARY### { + #include + + ###ABSTRACTIONS### + + ###POLICYGROUPS### + + ###READS### + + ###WRITES### +} From 41a960ecc7bf109de5e8bf20bfd0099955f31e7d Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 9 May 2012 22:38:05 -0700 Subject: [PATCH 02/46] pass a parser to parse_args() so we can call it multiple times --- utils/aa-easyprof | 4 +++- utils/apparmor/easyprof.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/utils/aa-easyprof b/utils/aa-easyprof index 44e9aaa69..f4dc74634 100755 --- a/utils/aa-easyprof +++ b/utils/aa-easyprof @@ -11,6 +11,7 @@ import apparmor.easyprof from apparmor.easyprof import AppArmorException, error +import optparse import os import sys @@ -20,7 +21,8 @@ if __name__ == "__main__": return 'USAGE: %s [options] ' % \ os.path.basename(sys.argv[0]) - (opt, args) = apparmor.easyprof.parse_args() + parser = optparse.OptionParser() + (opt, args) = apparmor.easyprof.parse_args(parser) binary = None m = usage() diff --git a/utils/apparmor/easyprof.py b/utils/apparmor/easyprof.py index 92074d375..5b10b31fc 100644 --- a/utils/apparmor/easyprof.py +++ b/utils/apparmor/easyprof.py @@ -451,11 +451,10 @@ def print_files(files): for i in files: print open(i).read() -def parse_args(args=None): +def parse_args(parser, args=None): '''Parse arguments''' global DEBUGGING - parser = optparse.OptionParser() parser.add_option("-c", "--config-file", dest="conffile", help="Use alternate configuration file", From ac3628c1fd5b74c146fb059da276c531a0d68dc6 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 9 May 2012 22:56:53 -0700 Subject: [PATCH 03/46] make parser optional --- utils/aa-easyprof | 3 +-- utils/apparmor/easyprof.py | 39 +++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/utils/aa-easyprof b/utils/aa-easyprof index f4dc74634..0f2a7995e 100755 --- a/utils/aa-easyprof +++ b/utils/aa-easyprof @@ -21,8 +21,7 @@ if __name__ == "__main__": return 'USAGE: %s [options] ' % \ os.path.basename(sys.argv[0]) - parser = optparse.OptionParser() - (opt, args) = apparmor.easyprof.parse_args(parser) + (opt, args) = apparmor.easyprof.parse_args() binary = None m = usage() diff --git a/utils/apparmor/easyprof.py b/utils/apparmor/easyprof.py index 5b10b31fc..d525a6db0 100644 --- a/utils/apparmor/easyprof.py +++ b/utils/apparmor/easyprof.py @@ -451,10 +451,30 @@ def print_files(files): for i in files: print open(i).read() -def parse_args(parser, args=None): +def add_parser_policy_args(parser): + '''Add parser arguments''' + parser.add_option("-a", "--abstractions", + dest="abstractions", + help="Comma-separated list of abstractions", + metavar="ABSTRACTIONS") + parser.add_option("--read-path", + dest="read_path", + help="Path allowing owner reads", + metavar="PATH", + action="append") + parser.add_option("--write-path", + dest="write_path", + help="Path allowing owner writes", + metavar="PATH", + action="append") + +def parse_args(args=None, parser=None): '''Parse arguments''' global DEBUGGING + if parser == None: + parser = optparse.OptionParser() + parser.add_option("-c", "--config-file", dest="conffile", help="Use alternate configuration file", @@ -495,20 +515,6 @@ def parse_args(parser, args=None): help="Show specified policy groups", action='store_true', default=False) - parser.add_option("-a", "--abstractions", - dest="abstractions", - help="Comma-separated list of abstractions", - metavar="ABSTRACTIONS") - parser.add_option("--read-path", - dest="read_path", - help="Path allowing owner reads", - metavar="PATH", - action="append") - parser.add_option("--write-path", - dest="write_path", - help="Path allowing owner writes", - metavar="PATH", - action="append") parser.add_option("-n", "--name", dest="name", help="Name of policy", @@ -531,6 +537,9 @@ def parse_args(parser, args=None): metavar="@{VARIABLE}=VALUE", action="append") + # add policy args now + add_parser_policy_args(parser) + (my_opt, my_args) = parser.parse_args(args) if my_opt.debug: DEBUGGING = True From af26d11dd22d2d65f7cbf12210507e1e4ef65d49 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 10 May 2012 01:17:56 -0700 Subject: [PATCH 04/46] fix up option parsing implement profile loading and transition (sudo for now) --- utils/aa-sandbox | 16 +++++--- utils/apparmor/easyprof.py | 42 +++++++++++---------- utils/apparmor/sandbox.py | 60 ++++++++++++++++++++++++++---- utils/easyprof/templates/sandbox | 3 ++ utils/easyprof/templates/sandbox-x | 44 ++++++++++++++++++++++ 5 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 utils/easyprof/templates/sandbox-x diff --git a/utils/aa-sandbox b/utils/aa-sandbox index 428387920..3ec5f52e7 100755 --- a/utils/aa-sandbox +++ b/utils/aa-sandbox @@ -11,21 +11,27 @@ import apparmor.sandbox from apparmor.common import AppArmorException, error +import optparse import os import sys if __name__ == "__main__": argv = sys.argv - (opt, args) = apparmor.sandbox.parse_args(sys.argv) + parser = optparse.OptionParser() + apparmor.easyprof.add_parser_policy_args(parser) + (opt, args) = apparmor.sandbox.parse_args(sys.argv, parser) if len(args) < 1: error("Must specify binary") - if not apparmor.sandbox.check_requirements(args[0]): + binary = args[0] + if not apparmor.sandbox.check_requirements(binary): sys.exit(1) if opt.withx: - apparmor.sandbox.run_xsandbox(opt.resolution, args) + rc, report = apparmor.sandbox.run_xsandbox(args, opt) else: - # TODO - sys.exit(1) + rc, report = apparmor.sandbox.run_sandbox(args, opt) + + print report + sys.exit(rc) diff --git a/utils/apparmor/easyprof.py b/utils/apparmor/easyprof.py index d525a6db0..75cf7264b 100644 --- a/utils/apparmor/easyprof.py +++ b/utils/apparmor/easyprof.py @@ -198,9 +198,8 @@ def verify_policy(policy): class AppArmorEasyProfile: '''Easy profile class''' def __init__(self, binary, opt): - self.conffile = "/etc/apparmor/easyprof.conf" - if opt.conffile: - self.conffile = os.path.abspath(opt.conffile) + opt.ensure_value("conffile", "/etc/apparmor/easyprof.conf") + self.conffile = os.path.abspath(opt.conffile) self.dirs = dict() if os.path.isfile(self.conffile): @@ -374,7 +373,10 @@ class AppArmorEasyProfile: # Fill-in profile name and binary policy = re.sub(r'###NAME###', name, policy) - policy = re.sub(r'###BINARY###', binary, policy) + if binary.startswith('/'): + policy = re.sub(r'###BINARY###', binary, policy) + else: + policy = re.sub(r'###BINARY###', "profile %s" % binary, policy) # Fill-in various comment fields if comment != None: @@ -467,6 +469,22 @@ def add_parser_policy_args(parser): help="Path allowing owner writes", metavar="PATH", action="append") + parser.add_option("-t", "--template", + dest="template", + help="Use non-default policy template", + metavar="TEMPLATE", + default='default') + parser.add_option("--templates-dir", + dest="templates_dir", + help="Use non-default templates directory", + metavar="DIR") + parser.add_option("-p", "--policy-groups", + help="Comma-separated list of policy groups", + metavar="POLICYGROUPS") + parser.add_option("--policy-groups-dir", + dest="policy_groups_dir", + help="Use non-default policy-groups directory", + metavar="DIR") def parse_args(args=None, parser=None): '''Parse arguments''' @@ -483,34 +501,18 @@ def parse_args(args=None, parser=None): help="Show debugging output", action='store_true', default=False) - parser.add_option("-t", "--template", - dest="template", - help="Use non-default policy template", - metavar="TEMPLATE", - default='default') parser.add_option("--list-templates", help="List available templates", action='store_true', default=False) - parser.add_option("--templates-dir", - dest="templates_dir", - help="Use non-default templates directory", - metavar="DIR") parser.add_option("--show-template", help="Show specified template", action='store_true', default=False) - parser.add_option("-p", "--policy-groups", - help="Comma-separated list of policy groups", - metavar="POLICYGROUPS") parser.add_option("--list-policy-groups", help="List available policy groups", action='store_true', default=False) - parser.add_option("--policy-groups-dir", - dest="policy_groups_dir", - help="Use non-default policy-groups directory", - metavar="DIR") parser.add_option("--show-policy-group", help="Show specified policy groups", action='store_true', diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index e5abd38ee..ed1f147e4 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -9,12 +9,14 @@ # ------------------------------------------------------------------ from apparmor.common import AppArmorException, debug, error, cmd +import apparmor.easyprof import optparse import os import sys +import tempfile import time -global DEBUGGING +DEBUGGING = False def check_requirements(binary): '''Verify necessary software is installed''' @@ -27,11 +29,13 @@ def check_requirements(binary): return False return True -def parse_args(args=None): +def parse_args(args=None, parser=None): '''Parse arguments''' global DEBUGGING - parser = optparse.OptionParser() + if parser == None: + parser = optparse.OptionParser() + parser.add_option('-X', '--with-x', dest='withx', default=False, @@ -52,12 +56,51 @@ def parse_args(args=None): DEBUGGING = True return (my_opt, my_args) +def gen_policy_name(binary): + '''Generate a temporary policy based on the binary name''' + # TODO: this may not be good enough + return "sandbox-%s" % (os.path.basename(binary)) + +def aa_exec(command, opt): + '''Execute binary under specified policy''' + opt.ensure_value("template_var", None) + opt.ensure_value("name", None) + opt.ensure_value("comment", None) + opt.ensure_value("author", None) + opt.ensure_value("copyright", None) + + binary = command[0] + policy_name = apparmor.sandbox.gen_policy_name(binary) + easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt) + params = apparmor.easyprof.gen_policy_params(policy_name, opt) + policy = easyp.gen_policy(**params) + debug("\n%s" % policy) + + # TODO: get rid of sudo + tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) + tmp.write(policy) + tmp.flush() + rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) + if rc != 0: + raise AppArmorException("Could not load policy") + + args = ['aa-exec', '-p', policy_name] + command + rc, report = cmd(args) + return rc, report + def find_free_x_display(): # TODO: detect/track and get an available display x_display = ":1" return x_display -def run_xsandbox(resolution, command): +def run_sandbox(command, opt): + '''Run application''' + # aa-exec + opt.ensure_value("template", "sandbox") + rc, report = aa_exec(command, opt) + return rc, report + +def run_xsandbox(command, opt): '''Run X application in a sandbox''' # Find a display to run on x_display = find_free_x_display() @@ -81,7 +124,7 @@ def run_xsandbox(resolution, command): ] x_args = ['-nolisten', 'tcp', - '-screen', resolution, + '-screen', opt.resolution, '-br', # black background '-reset', # reset after last client exists '-terminate', # terminate at server reset @@ -116,7 +159,10 @@ def run_xsandbox(resolution, command): sys.exit(0) time.sleep(0.2) # FIXME: detect if running - cmd(command) + + # aa-exec + opt.ensure_value("template", "sandbox-x") + rc, report = aa_exec(command, opt) # reset environment os.environ["DISPLAY"] = old_display @@ -132,4 +178,4 @@ def run_xsandbox(resolution, command): os.kill(listener_x, 15) os.waitpid(listener_x, 0) - + return rc, report diff --git a/utils/easyprof/templates/sandbox b/utils/easyprof/templates/sandbox index 618c7efaf..acc81f97c 100644 --- a/utils/easyprof/templates/sandbox +++ b/utils/easyprof/templates/sandbox @@ -15,6 +15,9 @@ ###BINARY### { #include + / r, + /**/ r, + /usr/** r, ###ABSTRACTIONS### diff --git a/utils/easyprof/templates/sandbox-x b/utils/easyprof/templates/sandbox-x new file mode 100644 index 000000000..63b381bf3 --- /dev/null +++ b/utils/easyprof/templates/sandbox-x @@ -0,0 +1,44 @@ +# +# Example usage for a program named 'foo' which is installed in /opt/foo +# $ aa-easyprof --template=sandbox \ +# --template-var="@{APPNAME}=foo" \ +# --policy-groups=opt-application,user-application \ +# /opt/foo/bin/foo +# +###ENDUSAGE### +# vim:syntax=apparmor +# AppArmor policy for ###NAME### + +#include + +###VAR### + +###BINARY### { + #include + #include + #include + #include + + /etc/passwd r, + + / r, + /**/ r, + /usr/** r, + /var/lib/dbus/machine-id r, + + owner @{PROC}/[0-9]*/auxv r, + owner @{PROC}/[0-9]*/fd/ r, + owner @{PROC}/[0-9]*/environ r, + owner @{PROC}/[0-9]*/mounts r, + owner @{PROC}/[0-9]*/smaps r, + owner @{PROC}/[0-9]*/statm r, + owner @{PROC}/[0-9]*/task/[0-9]*/stat r, + + ###ABSTRACTIONS### + + ###POLICYGROUPS### + + ###READS### + + ###WRITES### +} From cc1c57727d1d713c9d19dd37c9ee02a787d570b0 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 10 May 2012 06:43:52 -0700 Subject: [PATCH 05/46] utils/apparmor/sandbox.py: - print what template we are using on stdout - don't default to a specific template (may change in future) - add username to profile name --- utils/apparmor/sandbox.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index ed1f147e4..b27ed21d0 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -12,6 +12,7 @@ from apparmor.common import AppArmorException, debug, error, cmd import apparmor.easyprof import optparse import os +import pwd import sys import tempfile import time @@ -59,7 +60,8 @@ def parse_args(args=None, parser=None): def gen_policy_name(binary): '''Generate a temporary policy based on the binary name''' # TODO: this may not be good enough - return "sandbox-%s" % (os.path.basename(binary)) + return "sandbox-%s-%s" % (pwd.getpwuid(os.getuid())[0], + os.path.basename(binary)) def aa_exec(command, opt): '''Execute binary under specified policy''' @@ -80,6 +82,7 @@ def aa_exec(command, opt): tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) tmp.write(policy) tmp.flush() + debug("using '%s' template" % opt.template) rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) if rc != 0: raise AppArmorException("Could not load policy") @@ -96,7 +99,7 @@ def find_free_x_display(): def run_sandbox(command, opt): '''Run application''' # aa-exec - opt.ensure_value("template", "sandbox") + #opt.template = "sandbox-x" rc, report = aa_exec(command, opt) return rc, report @@ -161,7 +164,7 @@ def run_xsandbox(command, opt): time.sleep(0.2) # FIXME: detect if running # aa-exec - opt.ensure_value("template", "sandbox-x") + #opt.template = "sandbox-x" rc, report = aa_exec(command, opt) # reset environment From a995c08356a3de2cb55c3b98ef9a935a31b91e38 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 16:29:48 -0500 Subject: [PATCH 06/46] fix up debug handling add required binaries --- utils/apparmor/common.py | 1 - utils/apparmor/sandbox.py | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/utils/apparmor/common.py b/utils/apparmor/common.py index 7217f091e..c58fda813 100644 --- a/utils/apparmor/common.py +++ b/utils/apparmor/common.py @@ -12,7 +12,6 @@ import subprocess import sys DEBUGGING = False -DEBUGGING = True # # Utility classes diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index b27ed21d0..579c6be51 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -17,11 +17,13 @@ import sys import tempfile import time -DEBUGGING = False - def check_requirements(binary): '''Verify necessary software is installed''' - exes = ['Xephyr', 'matchbox-window-manager', binary] + exes = ['Xephyr', + 'matchbox-window-manager', + 'aa-easyprof', # for templates + 'sudo', # eventually get rid of this + binary] for e in exes: debug("Searching for '%s'" % e) rc, report = cmd(['which', e]) @@ -32,8 +34,6 @@ def check_requirements(binary): def parse_args(args=None, parser=None): '''Parse arguments''' - global DEBUGGING - if parser == None: parser = optparse.OptionParser() @@ -53,8 +53,9 @@ def parse_args(args=None, parser=None): help='Resolution for X application') (my_opt, my_args) = parser.parse_args() - if my_opt.debug: - DEBUGGING = True + if my_opt.debug == True: + apparmor.common.DEBUGGING = True + return (my_opt, my_args) def gen_policy_name(binary): From f826be087d9e15cdad43226dba4b88947809d552 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 17:12:14 -0500 Subject: [PATCH 07/46] utils/aa-sandbox: use msq() instead of print utils/apparmor/common.py: adjust for python3 (ie, make bi-lingual) utils/apparmor/sandbox.py: - set reasonable default template - gen_policy_name() uses full pathname - adjust for python3 --- utils/aa-sandbox | 2 +- utils/apparmor/common.py | 26 ++++++++++++++++++-------- utils/apparmor/sandbox.py | 17 +++++++++++++---- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/utils/aa-sandbox b/utils/aa-sandbox index 3ec5f52e7..0452fe89a 100755 --- a/utils/aa-sandbox +++ b/utils/aa-sandbox @@ -33,5 +33,5 @@ if __name__ == "__main__": else: rc, report = apparmor.sandbox.run_sandbox(args, opt) - print report + apparmor.common.msg(report) sys.exit(rc) diff --git a/utils/apparmor/common.py b/utils/apparmor/common.py index c58fda813..32ccc1672 100644 --- a/utils/apparmor/common.py +++ b/utils/apparmor/common.py @@ -8,6 +8,7 @@ # # ------------------------------------------------------------------ +from __future__ import print_function import subprocess import sys @@ -30,7 +31,7 @@ class AppArmorException(Exception): def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: - print >> sys.stderr, "ERROR: %s" % (out) + print("ERROR: %s" % (out), file=sys.stderr) except IOError: pass @@ -40,14 +41,14 @@ def error(out, exit_code=1, do_exit=True): def warn(out): '''Print warning message''' try: - print >> sys.stderr, "WARN: %s" % (out) + print("WARN: %s" % (out), file=sys.stderr) except IOError: pass def msg(out, output=sys.stdout): '''Print message''' try: - print >> output, "%s" % (out) + print("%s" % (out), file=sys.stdout) except IOError: pass @@ -56,7 +57,7 @@ def debug(out): global DEBUGGING if DEBUGGING: try: - print >> sys.stderr, "DEBUG: %s" % (out) + print("DEBUG: %s" % (out), file=sys.stderr) except IOError: pass @@ -66,20 +67,29 @@ def cmd(command): try: sp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - except OSError, ex: + except OSError as ex: return [127, str(ex)] - out = sp.communicate()[0] + if sys.version_info[0] >= 3: + out = sp.communicate()[0].decode('ascii', 'ignore') + else: + out = sp.communicate()[0] + return [sp.returncode, out] + def cmd_pipe(command1, command2): '''Try to pipe command1 into command2.''' try: sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) sp2 = subprocess.Popen(command2, stdin=sp1.stdout) - except OSError, ex: + except OSError as ex: return [127, str(ex)] - out = sp2.communicate()[0] + if sys.version_info[0] >= 3: + out = sp2.communicate()[0].decode('ascii', 'ignore') + else: + out = sp2.communicate()[0] + return [sp2.returncode, out] diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 579c6be51..c6648beb0 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -13,6 +13,7 @@ import apparmor.easyprof import optparse import os import pwd +import re import sys import tempfile import time @@ -55,14 +56,19 @@ def parse_args(args=None, parser=None): (my_opt, my_args) = parser.parse_args() if my_opt.debug == True: apparmor.common.DEBUGGING = True + if my_opt.template == "default": + if my_opt.withx: + my_opt.template = "sandbox-x" + else: + my_opt.template = "sandbox" + return (my_opt, my_args) def gen_policy_name(binary): '''Generate a temporary policy based on the binary name''' - # TODO: this may not be good enough return "sandbox-%s-%s" % (pwd.getpwuid(os.getuid())[0], - os.path.basename(binary)) + re.sub(r'/', '_', binary)) def aa_exec(command, opt): '''Execute binary under specified policy''' @@ -81,7 +87,11 @@ def aa_exec(command, opt): # TODO: get rid of sudo tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) - tmp.write(policy) + if sys.version_info[0] >= 3: + tmp.write(bytes(policy, 'utf-8')) + else: + tmp.write(policy) + tmp.flush() debug("using '%s' template" % opt.template) rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) @@ -165,7 +175,6 @@ def run_xsandbox(command, opt): time.sleep(0.2) # FIXME: detect if running # aa-exec - #opt.template = "sandbox-x" rc, report = aa_exec(command, opt) # reset environment From 354486e326fece59e2bb24097a921ecb694e0c4c Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 17:15:51 -0500 Subject: [PATCH 08/46] utils/apparmor/sandbox.py: slightly cleanup the gen_policy_name --- utils/apparmor/sandbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index c6648beb0..c0e11a63e 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -67,7 +67,7 @@ def parse_args(args=None, parser=None): def gen_policy_name(binary): '''Generate a temporary policy based on the binary name''' - return "sandbox-%s-%s" % (pwd.getpwuid(os.getuid())[0], + return "sandbox-%s%s" % (pwd.getpwuid(os.getuid())[0], re.sub(r'/', '_', binary)) def aa_exec(command, opt): From 7157a62d2b56b7b1316ede18bc746af4a7973ce5 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 17:37:31 -0500 Subject: [PATCH 09/46] utils/apparmor/sandbox.py: detect next DISPLAY to use --- utils/apparmor/sandbox.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index c0e11a63e..55430aa5e 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -22,6 +22,7 @@ def check_requirements(binary): '''Verify necessary software is installed''' exes = ['Xephyr', 'matchbox-window-manager', + 'xset', # for detecting free X display 'aa-easyprof', # for templates 'sudo', # eventually get rid of this binary] @@ -103,8 +104,22 @@ def aa_exec(command, opt): return rc, report def find_free_x_display(): - # TODO: detect/track and get an available display - x_display = ":1" + '''Find a free X display''' + x_display = "" + current = os.environ["DISPLAY"] + for i in range(1,257): # TODO: this puts an artificial limit of 256 + # sandboxed applications + tmp = ":%d" % i + os.environ["DISPLAY"] = tmp + rc, report = cmd(['xset', '-q']) + if rc != 0: + x_display = tmp + break + + os.environ["DISPLAY"] = current + if x_display == "": + raise AppArmorException("Could not find available X display") + return x_display def run_sandbox(command, opt): @@ -119,7 +134,7 @@ def run_xsandbox(command, opt): # Find a display to run on x_display = find_free_x_display() - debug (os.environ["DISPLAY"]) + debug ("DISPLAY=%s" % os.environ["DISPLAY"]) # first, start X listener_x = os.fork() From 1fdc3a5e999fac1f96af00a899c1c9cfd653d0bc Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 19:36:25 -0500 Subject: [PATCH 10/46] utils/apparmor/sandbox.py: - add --xserver option and support both xephyr and xpra - refactoring --- utils/apparmor/sandbox.py | 226 +++++++++++++++++++++++++------------- 1 file changed, 148 insertions(+), 78 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 55430aa5e..79b0ee164 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -44,6 +44,10 @@ def parse_args(args=None, parser=None): default=False, help='Run in isolated X server', action='store_true') + parser.add_option('--xserver', + dest='xserver', + default='xpra', + help='Nested X server to use (default is xpra)') parser.add_option('-d', '--debug', dest='debug', default=False, @@ -59,6 +63,9 @@ def parse_args(args=None, parser=None): apparmor.common.DEBUGGING = True if my_opt.template == "default": if my_opt.withx: + if my_opt.xserver.lower() != 'xpra' and \ + my_opt.xserver.lower() != 'xephyr': + error("Invalid server '%s'. Use 'xpra' or 'xephyr'") my_opt.template = "sandbox-x" else: my_opt.template = "sandbox" @@ -103,25 +110,6 @@ def aa_exec(command, opt): rc, report = cmd(args) return rc, report -def find_free_x_display(): - '''Find a free X display''' - x_display = "" - current = os.environ["DISPLAY"] - for i in range(1,257): # TODO: this puts an artificial limit of 256 - # sandboxed applications - tmp = ":%d" % i - os.environ["DISPLAY"] = tmp - rc, report = cmd(['xset', '-q']) - if rc != 0: - x_display = tmp - break - - os.environ["DISPLAY"] = current - if x_display == "": - raise AppArmorException("Could not find available X display") - - return x_display - def run_sandbox(command, opt): '''Run application''' # aa-exec @@ -129,68 +117,156 @@ def run_sandbox(command, opt): rc, report = aa_exec(command, opt) return rc, report +class SandboxXserver(): + def __init__(self, resolution, title): + self.resolution = resolution + self.title = title + self.pids = [] + self.find_free_x_display() + + def find_free_x_display(self): + '''Find a free X display''' + display = "" + current = os.environ["DISPLAY"] + for i in range(1,257): # TODO: this puts an artificial limit of 256 + # sandboxed applications + tmp = ":%d" % i + os.environ["DISPLAY"] = tmp + rc, report = cmd(['xset', '-q']) + if rc != 0: + display = tmp + break + + os.environ["DISPLAY"] = current + if display == "": + raise AppArmorException("Could not find available X display") + + self.display = display + + def cleanup(self): + '''Cleanup our forked pids, etc''' + # kill server now. It should've terminated, but be sure + for pid in self.pids: + cmd(['kill', '-15', "%d" % pid]) + os.kill(pid, 15) + os.waitpid(pid, 0) + + def start(self): + '''start() should be overridden''' + +class SandboxXephyr(SandboxXserver): + def start(self): + '''Start a Xephyr server''' + listener_x = os.fork() + if listener_x == 0: + # TODO: break into config file? Which are needed? + x_exts = ['-extension', 'GLX', + '-extension', 'MIT-SHM', + '-extension', 'RENDER', + '-extension', 'SECURITY', + '-extension', 'DAMAGE' + ] + # verify_these + x_extra_args = ['-host-cursor', # less secure? + '-fakexa', # for games? seems not needed + '-nodri', # more secure? + ] + + x_args = ['-nolisten', 'tcp', + '-screen', self.resolution, + '-br', # black background + '-reset', # reset after last client exists + '-terminate', # terminate at server reset + '-title', self.title, + ] + x_exts + x_extra_args + + args = ['/usr/bin/Xephyr'] + x_args + [self.display] + debug(" ".join(args)) + sys.stderr.flush() + os.execv(args[0], args) + sys.exit(0) + self.pids.append(listener_x) + + time.sleep(0.2) # FIXME: detect if running + + # Next, start the window manager + sys.stdout.flush() + os.chdir(os.environ["HOME"]) + listener_wm = os.fork() + if listener_wm == 0: + # update environment + os.environ["DISPLAY"] = self.display + debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) + + args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] + debug(" ".join(args)) + sys.stderr.flush() + os.execv(args[0], args) + sys.exit(0) + + self.pids.append(listener_wm) + time.sleep(0.2) # FIXME: detect if running + +class SandboxXpra(SandboxXserver): + def cleanup(self): + cmd(['xpra', 'stop', self.display]) + SandboxXserver.cleanup(self) + + def start(self): + listener_x = os.fork() + if listener_x == 0: + x_args = ['--no-daemon', + '--no-clipboard', + '--no-pulseaudio'] + # TODO: --password-file + args = ['/usr/bin/xpra', 'start', self.display] + x_args + debug(" ".join(args)) + sys.stderr.flush() + os.execv(args[0], args) + sys.exit(0) + self.pids.append(listener_x) + + time.sleep(2) # FIXME: detect if running + + # Next, attach to xpra + sys.stdout.flush() + os.chdir(os.environ["HOME"]) + listener_attach = os.fork() + if listener_attach == 0: + # TODO: --pasword-file + args = ['/usr/bin/xpra', 'attach', self.display, + '--title=%s' % self.title] + debug(" ".join(args)) + sys.stderr.flush() + os.execv(args[0], args) + sys.exit(0) + + self.pids.append(listener_attach) + def run_xsandbox(command, opt): '''Run X application in a sandbox''' - # Find a display to run on - x_display = find_free_x_display() - - debug ("DISPLAY=%s" % os.environ["DISPLAY"]) - - # first, start X - listener_x = os.fork() - if listener_x == 0: - # TODO: break into config file? Which are needed? - x_exts = ['-extension', 'GLX', - '-extension', 'MIT-SHM', - '-extension', 'RENDER', - '-extension', 'SECURITY', - '-extension', 'DAMAGE' - ] - # verify_these - x_extra_args = ['-host-cursor', # less secure? - '-fakexa', # for games? seems not needed - '-nodri', # more secure? - ] - - x_args = ['-nolisten', 'tcp', - '-screen', opt.resolution, - '-br', # black background - '-reset', # reset after last client exists - '-terminate', # terminate at server reset - '-title', command[0], - ] + x_exts + x_extra_args - - args = ['/usr/bin/Xephyr'] + x_args + [x_display] - debug(" ".join(args)) - sys.stderr.flush() - os.execv(args[0], args) - sys.exit(0) - # save environment old_display = os.environ["DISPLAY"] + debug ("DISPLAY=%s" % old_display) old_cwd = os.getcwd() + # first, start X + if opt.xserver.lower() == "xephyr": + x = SandboxXephyr(opt.resolution, command[0]) + else: + x = SandboxXpra(opt.resolution, command[0]) + x.start() + # update environment - os.environ["DISPLAY"] = x_display + os.environ["DISPLAY"] = x.display debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) - time.sleep(0.2) # FIXME: detect if running - - # Next, start the window manager - sys.stdout.flush() - os.chdir(os.environ["HOME"]) - listener_wm = os.fork() - if listener_wm == 0: - args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] - debug(" ".join(args)) - sys.stderr.flush() - os.execv(args[0], args) - sys.exit(0) - - time.sleep(0.2) # FIXME: detect if running - # aa-exec - rc, report = aa_exec(command, opt) + try: + rc, report = aa_exec(command, opt) + except: + x.cleanup() + raise # reset environment os.environ["DISPLAY"] = old_display @@ -198,12 +274,6 @@ def run_xsandbox(command, opt): os.chdir(old_cwd) - # kill server now. It should've terminated, but be sure - cmd(['kill', '-15', "%d" % listener_wm]) - os.kill(listener_wm, 15) - os.waitpid(listener_wm, 0) - cmd(['kill', '-15', "%d" % listener_x]) - os.kill(listener_x, 15) - os.waitpid(listener_x, 0) + x.cleanup() return rc, report From 51256d8fe79c46c1d93a4a5c2ee83fb79ea60dcc Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 19:56:18 -0500 Subject: [PATCH 11/46] move X server search code into classes --- utils/apparmor/sandbox.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 79b0ee164..5ab43f132 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -20,18 +20,19 @@ import time def check_requirements(binary): '''Verify necessary software is installed''' - exes = ['Xephyr', - 'matchbox-window-manager', - 'xset', # for detecting free X display + exes = ['xset', # for detecting free X display 'aa-easyprof', # for templates + 'aa-exec', # for changing profile 'sudo', # eventually get rid of this binary] + for e in exes: debug("Searching for '%s'" % e) rc, report = cmd(['which', e]) if rc != 0: error("Could not find '%s'" % e, do_exit=False) return False + return True def parse_args(args=None, parser=None): @@ -65,7 +66,8 @@ def parse_args(args=None, parser=None): if my_opt.withx: if my_opt.xserver.lower() != 'xpra' and \ my_opt.xserver.lower() != 'xephyr': - error("Invalid server '%s'. Use 'xpra' or 'xephyr'") + error("Invalid server '%s'. Use 'xpra' or 'xephyr'" % \ + my_opt.xserver) my_opt.template = "sandbox-x" else: my_opt.template = "sandbox" @@ -113,7 +115,6 @@ def aa_exec(command, opt): def run_sandbox(command, opt): '''Run application''' # aa-exec - #opt.template = "sandbox-x" rc, report = aa_exec(command, opt) return rc, report @@ -156,6 +157,12 @@ class SandboxXserver(): class SandboxXephyr(SandboxXserver): def start(self): + for e in ['Xephyr', 'matchbox-window-manager']: + debug("Searching for '%s'" % e) + rc, report = cmd(['which', e]) + if rc != 0: + raise AppArmorException("Could not find '%s'" % e) + '''Start a Xephyr server''' listener_x = os.fork() if listener_x == 0: @@ -213,6 +220,12 @@ class SandboxXpra(SandboxXserver): SandboxXserver.cleanup(self) def start(self): + for e in ['xpra']: + debug("Searching for '%s'" % e) + rc, report = cmd(['which', e]) + if rc != 0: + raise AppArmorException("Could not find '%s'" % e) + listener_x = os.fork() if listener_x == 0: x_args = ['--no-daemon', From c0821032fb72fd5ff7a62d7e5a131ad9cb76caf3 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 20:25:29 -0500 Subject: [PATCH 12/46] remove and add some comments --- utils/apparmor/sandbox.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 5ab43f132..73f274331 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -101,8 +101,8 @@ def aa_exec(command, opt): tmp.write(bytes(policy, 'utf-8')) else: tmp.write(policy) - tmp.flush() + debug("using '%s' template" % opt.template) rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) if rc != 0: @@ -229,16 +229,15 @@ class SandboxXpra(SandboxXserver): listener_x = os.fork() if listener_x == 0: x_args = ['--no-daemon', + #'--no-mmap', # for security? '--no-clipboard', '--no-pulseaudio'] - # TODO: --password-file args = ['/usr/bin/xpra', 'start', self.display] + x_args debug(" ".join(args)) sys.stderr.flush() os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) - time.sleep(2) # FIXME: detect if running # Next, attach to xpra @@ -246,9 +245,8 @@ class SandboxXpra(SandboxXserver): os.chdir(os.environ["HOME"]) listener_attach = os.fork() if listener_attach == 0: - # TODO: --pasword-file args = ['/usr/bin/xpra', 'attach', self.display, - '--title=%s' % self.title] + '--title=%s' % self.title] debug(" ".join(args)) sys.stderr.flush() os.execv(args[0], args) From d7b2cb6a50fa5b1a7beeb9cba5a366452ebedd63 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 20:39:19 -0500 Subject: [PATCH 13/46] small cleanups for prettier output --- utils/apparmor/sandbox.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 73f274331..357b8a453 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -148,7 +148,6 @@ class SandboxXserver(): '''Cleanup our forked pids, etc''' # kill server now. It should've terminated, but be sure for pid in self.pids: - cmd(['kill', '-15', "%d" % pid]) os.kill(pid, 15) os.waitpid(pid, 0) @@ -194,7 +193,7 @@ class SandboxXephyr(SandboxXserver): sys.exit(0) self.pids.append(listener_x) - time.sleep(0.2) # FIXME: detect if running + time.sleep(1) # FIXME: detect if running # Next, start the window manager sys.stdout.flush() @@ -212,7 +211,7 @@ class SandboxXephyr(SandboxXserver): sys.exit(0) self.pids.append(listener_wm) - time.sleep(0.2) # FIXME: detect if running + time.sleep(1) # FIXME: detect if running class SandboxXpra(SandboxXserver): def cleanup(self): @@ -234,8 +233,9 @@ class SandboxXpra(SandboxXserver): '--no-pulseaudio'] args = ['/usr/bin/xpra', 'start', self.display] + x_args debug(" ".join(args)) - sys.stderr.flush() - os.execv(args[0], args) + cmd(args) + #sys.stderr.flush() + #os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) time.sleep(2) # FIXME: detect if running @@ -248,8 +248,9 @@ class SandboxXpra(SandboxXserver): args = ['/usr/bin/xpra', 'attach', self.display, '--title=%s' % self.title] debug(" ".join(args)) - sys.stderr.flush() - os.execv(args[0], args) + cmd(args) + #sys.stderr.flush() + #os.execv(args[0], args) sys.exit(0) self.pids.append(listener_attach) @@ -278,13 +279,11 @@ def run_xsandbox(command, opt): except: x.cleanup() raise + x.cleanup() # reset environment + os.chdir(old_cwd) os.environ["DISPLAY"] = old_display debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) - os.chdir(old_cwd) - - x.cleanup() - return rc, report From cafd8c9b3ef029c11fc8e46efc66db2ed0382d98 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 20:47:58 -0500 Subject: [PATCH 14/46] drop globalmenu support for now --- utils/apparmor/sandbox.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 357b8a453..86adb60d8 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -125,6 +125,10 @@ class SandboxXserver(): self.pids = [] self.find_free_x_display() + # TODO: for now, drop Unity's globalmenu proxy since it doesn't work + # right in the application. + os.environ["UBUNTU_MENUPROXY"] = "" + def find_free_x_display(self): '''Find a free X display''' display = "" @@ -213,6 +217,7 @@ class SandboxXephyr(SandboxXserver): self.pids.append(listener_wm) time.sleep(1) # FIXME: detect if running + class SandboxXpra(SandboxXserver): def cleanup(self): cmd(['xpra', 'stop', self.display]) @@ -255,6 +260,7 @@ class SandboxXpra(SandboxXserver): self.pids.append(listener_attach) + def run_xsandbox(command, opt): '''Run X application in a sandbox''' # save environment From ed0f41c650a905c9564fc35b167bb03858f1dd4b Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 21:19:37 -0500 Subject: [PATCH 15/46] utils/apparmor/sandbox.py: - whitespace cleanups - move setting DISPLAY into the start() method - add extra options to xpra attach --- utils/apparmor/sandbox.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 86adb60d8..305029b06 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -8,7 +8,7 @@ # # ------------------------------------------------------------------ -from apparmor.common import AppArmorException, debug, error, cmd +from apparmor.common import AppArmorException, debug, error, warn, cmd import apparmor.easyprof import optparse import os @@ -25,7 +25,7 @@ def check_requirements(binary): 'aa-exec', # for changing profile 'sudo', # eventually get rid of this binary] - + for e in exes: debug("Searching for '%s'" % e) rc, report = cmd(['which', e]) @@ -45,7 +45,7 @@ def parse_args(args=None, parser=None): default=False, help='Run in isolated X server', action='store_true') - parser.add_option('--xserver', + parser.add_option('--with-xserver', dest='xserver', default='xpra', help='Nested X server to use (default is xpra)') @@ -71,7 +71,6 @@ def parse_args(args=None, parser=None): my_opt.template = "sandbox-x" else: my_opt.template = "sandbox" - return (my_opt, my_args) @@ -141,7 +140,7 @@ class SandboxXserver(): if rc != 0: display = tmp break - + os.environ["DISPLAY"] = current if display == "": raise AppArmorException("Could not find available X display") @@ -217,12 +216,13 @@ class SandboxXephyr(SandboxXserver): self.pids.append(listener_wm) time.sleep(1) # FIXME: detect if running + os.environ["DISPLAY"] = self.display class SandboxXpra(SandboxXserver): def cleanup(self): cmd(['xpra', 'stop', self.display]) SandboxXserver.cleanup(self) - + def start(self): for e in ['xpra']: debug("Searching for '%s'" % e) @@ -251,7 +251,10 @@ class SandboxXpra(SandboxXserver): listener_attach = os.fork() if listener_attach == 0: args = ['/usr/bin/xpra', 'attach', self.display, - '--title=%s' % self.title] + '--title=%s' % self.title, + #'--no-mmap', # for security? + '--no-clipboard', + '--no-pulseaudio'] debug(" ".join(args)) cmd(args) #sys.stderr.flush() @@ -260,6 +263,8 @@ class SandboxXpra(SandboxXserver): self.pids.append(listener_attach) + os.environ["DISPLAY"] = self.display + def run_xsandbox(command, opt): '''Run X application in a sandbox''' @@ -274,9 +279,6 @@ def run_xsandbox(command, opt): else: x = SandboxXpra(opt.resolution, command[0]) x.start() - - # update environment - os.environ["DISPLAY"] = x.display debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) # aa-exec From 7756e48197401d8920f72b0c7bb4ac1f951fe371 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 23 Aug 2012 21:52:52 -0500 Subject: [PATCH 16/46] utils/apparmor/sandbox.py: - add --profile option - small cleanups --- utils/apparmor/sandbox.py | 67 ++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 305029b06..e6b752df7 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -58,6 +58,10 @@ def parse_args(args=None, parser=None): dest='resolution', default='640x480', help='Resolution for X application') + parser.add_option('--profile', + dest='profile', + default=None, + help='Specify an existing profile (see aa-status)') (my_opt, my_args) = parser.parse_args() if my_opt.debug == True: @@ -81,31 +85,34 @@ def gen_policy_name(binary): def aa_exec(command, opt): '''Execute binary under specified policy''' - opt.ensure_value("template_var", None) - opt.ensure_value("name", None) - opt.ensure_value("comment", None) - opt.ensure_value("author", None) - opt.ensure_value("copyright", None) - - binary = command[0] - policy_name = apparmor.sandbox.gen_policy_name(binary) - easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt) - params = apparmor.easyprof.gen_policy_params(policy_name, opt) - policy = easyp.gen_policy(**params) - debug("\n%s" % policy) - - # TODO: get rid of sudo - tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) - if sys.version_info[0] >= 3: - tmp.write(bytes(policy, 'utf-8')) + if opt.profile != None: + policy_name = opt.profile else: - tmp.write(policy) - tmp.flush() + opt.ensure_value("template_var", None) + opt.ensure_value("name", None) + opt.ensure_value("comment", None) + opt.ensure_value("author", None) + opt.ensure_value("copyright", None) - debug("using '%s' template" % opt.template) - rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) - if rc != 0: - raise AppArmorException("Could not load policy") + binary = command[0] + policy_name = apparmor.sandbox.gen_policy_name(binary) + easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt) + params = apparmor.easyprof.gen_policy_params(policy_name, opt) + policy = easyp.gen_policy(**params) + debug("\n%s" % policy) + + tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) + if sys.version_info[0] >= 3: + tmp.write(bytes(policy, 'utf-8')) + else: + tmp.write(policy) + tmp.flush() + + debug("using '%s' template" % opt.template) + # TODO: get rid of sudo + rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) + if rc != 0: + raise AppArmorException("Could not load policy") args = ['aa-exec', '-p', policy_name] + command rc, report = cmd(args) @@ -125,7 +132,7 @@ class SandboxXserver(): self.find_free_x_display() # TODO: for now, drop Unity's globalmenu proxy since it doesn't work - # right in the application. + # right in the application. (Doesn't work with firefox) os.environ["UBUNTU_MENUPROXY"] = "" def find_free_x_display(self): @@ -191,8 +198,7 @@ class SandboxXephyr(SandboxXserver): args = ['/usr/bin/Xephyr'] + x_args + [self.display] debug(" ".join(args)) - sys.stderr.flush() - os.execv(args[0], args) + cmd(args) sys.exit(0) self.pids.append(listener_x) @@ -209,8 +215,7 @@ class SandboxXephyr(SandboxXserver): args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] debug(" ".join(args)) - sys.stderr.flush() - os.execv(args[0], args) + cmd(args) sys.exit(0) self.pids.append(listener_wm) @@ -239,8 +244,6 @@ class SandboxXpra(SandboxXserver): args = ['/usr/bin/xpra', 'start', self.display] + x_args debug(" ".join(args)) cmd(args) - #sys.stderr.flush() - #os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) time.sleep(2) # FIXME: detect if running @@ -257,14 +260,12 @@ class SandboxXpra(SandboxXserver): '--no-pulseaudio'] debug(" ".join(args)) cmd(args) - #sys.stderr.flush() - #os.execv(args[0], args) sys.exit(0) self.pids.append(listener_attach) os.environ["DISPLAY"] = self.display - + warn("Resolution not honored in xpra") def run_xsandbox(command, opt): '''Run X application in a sandbox''' From 5ce539c43243c5dfe48c4c3e302856e36b10c2bc Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 10:21:48 -0500 Subject: [PATCH 17/46] utils/apparmor/sandbox.py: - add xpra3d server option which uses Xdummy - update debugging output --- utils/apparmor/sandbox.py | 117 +++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index e6b752df7..c2b23136d 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -8,7 +8,7 @@ # # ------------------------------------------------------------------ -from apparmor.common import AppArmorException, debug, error, warn, cmd +from apparmor.common import AppArmorException, debug, error, warn, msg, cmd import apparmor.easyprof import optparse import os @@ -48,7 +48,7 @@ def parse_args(args=None, parser=None): parser.add_option('--with-xserver', dest='xserver', default='xpra', - help='Nested X server to use (default is xpra)') + help='Nested X server to use: xpra (default), xpra3d, xephyr') parser.add_option('-d', '--debug', dest='debug', default=False, @@ -125,11 +125,13 @@ def run_sandbox(command, opt): return rc, report class SandboxXserver(): - def __init__(self, resolution, title): + def __init__(self, resolution, title, driver=None): self.resolution = resolution self.title = title self.pids = [] self.find_free_x_display() + self.driver = driver + self.tempfiles = [] # TODO: for now, drop Unity's globalmenu proxy since it doesn't work # right in the application. (Doesn't work with firefox) @@ -161,6 +163,10 @@ class SandboxXserver(): os.kill(pid, 15) os.waitpid(pid, 0) + for t in self.tempfiles: + if os.path.exists(t): + os.unlink(t) + def start(self): '''start() should be overridden''' @@ -228,6 +234,77 @@ class SandboxXpra(SandboxXserver): cmd(['xpra', 'stop', self.display]) SandboxXserver.cleanup(self) + def _get_xvfb_args(self): + xvfb_args = [] + + if self.driver == None: + # The default from the man page, but be explicit in what we enable + xvfb_args.append('--xvfb=Xvfb') + xvfb_args.append('-screen 0 3840x2560x24+32') + xvfb_args.append('-nolisten tcp') + xvfb_args.append('-noreset') + xvfb_args.append('-auth %s' % os.environ['XAUTHORITY']) + xvfb_args.append('+extension Composite') + xvfb_args.append('-extension GLX') + elif self.driver == 'xdummy': + # The dummy driver allows us to use GLX, etc. See: + # http://xpra.org/Xdummy.html + conf = '''# Based on /usr/share/doc/xpra/examples/dummy.xorg.conf.gz +##Xdummy:## +Section "ServerFlags" + Option "DontVTSwitch" "true" + Option "AllowMouseOpenFail" "true" + Option "PciForceNone" "true" + Option "AutoEnableDevices" "false" + Option "AutoAddDevices" "false" +EndSection + +##Xdummy:## +Section "InputDevice" + Identifier "NoMouse" + Option "CorePointer" "true" + Driver "void" +EndSection + +Section "InputDevice" + Identifier "NoKeyboard" + Option "CoreKeyboard" "true" + Driver "void" +EndSection + +##Xdummy:## +Section "Device" + Identifier "Videocard0" + Driver "dummy" + #VideoRam 4096000 + #VideoRam 256000 +EndSection + +''' + + tmp, xorg_conf = tempfile.mkstemp(prefix='aa-sandbox-xorg.conf-') + self.tempfiles.append(xorg_conf) + if sys.version_info[0] >= 3: + os.write(tmp, bytes(conf, 'utf-8')) + else: + os.write(tmp, conf) + os.close(tmp) + + xvfb_args.append('--xvfb=Xorg') + xvfb_args.append('-dpi 96') # https://www.xpra.org/trac/ticket/163 + xvfb_args.append('-nolisten tcp') + xvfb_args.append('-noreset') + xvfb_args.append('-logfile %s' % os.path.expanduser('~/.xpra/%s.log' % self.display)) + xvfb_args.append('-auth %s' % os.environ['XAUTHORITY']) + xvfb_args.append('-config %s' % xorg_conf) + extensions = ['Composite', 'GLX', 'RANDR', 'RENDER'] + for i in extensions: + xvfb_args.append('+extension %s' % i) + else: + raise AppArmorException("Unsupported X driver '%s'" % self.driver) + + return xvfb_args + def start(self): for e in ['xpra']: debug("Searching for '%s'" % e) @@ -235,15 +312,29 @@ class SandboxXpra(SandboxXserver): if rc != 0: raise AppArmorException("Could not find '%s'" % e) + xvfb_args = self._get_xvfb_args() listener_x = os.fork() if listener_x == 0: + # Debugging tip (can also use glxinfo): + # $ xdpyinfo > /tmp/native + # $ aa-sandbox -X -t sandbox-x /usr/bin/xdpyinfo > /tmp/nested + # $ diff -Naur /tmp/native /tmp/nested + x_args = ['--no-daemon', #'--no-mmap', # for security? '--no-clipboard', '--no-pulseaudio'] + + if xvfb_args != '': + x_args.append(" ".join(xvfb_args)) + args = ['/usr/bin/xpra', 'start', self.display] + x_args debug(" ".join(args)) - cmd(args) + if apparmor.common.DEBUGGING == True: + sys.stderr.flush() + os.execv(args[0], args) + else: + cmd(args) sys.exit(0) self.pids.append(listener_x) time.sleep(2) # FIXME: detect if running @@ -259,13 +350,19 @@ class SandboxXpra(SandboxXserver): '--no-clipboard', '--no-pulseaudio'] debug(" ".join(args)) - cmd(args) + #cmd(args) + if apparmor.common.DEBUGGING == True: + sys.stderr.flush() + os.execv(args[0], args) + else: + cmd(args) sys.exit(0) self.pids.append(listener_attach) os.environ["DISPLAY"] = self.display - warn("Resolution not honored in xpra") + msg("TODO: --with-resolution not honored in xpra") + msg("TODO: filter '~/.xpra/run-xpra'") def run_xsandbox(command, opt): '''Run X application in a sandbox''' @@ -277,15 +374,17 @@ def run_xsandbox(command, opt): # first, start X if opt.xserver.lower() == "xephyr": x = SandboxXephyr(opt.resolution, command[0]) + elif opt.xserver.lower() == "xpra3d": + x = SandboxXpra(opt.resolution, command[0], driver="xdummy") else: x = SandboxXpra(opt.resolution, command[0]) x.start() - debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) + apparmor.common.msg("Using 'DISPLAY=%s'" % os.environ["DISPLAY"]) # aa-exec try: rc, report = aa_exec(command, opt) - except: + except Exception as e: x.cleanup() raise x.cleanup() @@ -293,6 +392,6 @@ def run_xsandbox(command, opt): # reset environment os.chdir(old_cwd) os.environ["DISPLAY"] = old_display - debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) + debug("DISPLAY restored to: %s" % os.environ["DISPLAY"]) return rc, report From cf24f21a7787c6fe4bf7091d06687b6be5aea209 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 10:34:14 -0500 Subject: [PATCH 18/46] utils/apparmor/sandbox.py: fix up arg validation for --with-xserver --- utils/apparmor/sandbox.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index c2b23136d..61fc66837 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -66,12 +66,13 @@ def parse_args(args=None, parser=None): (my_opt, my_args) = parser.parse_args() if my_opt.debug == True: apparmor.common.DEBUGGING = True + if my_opt.withx and my_opt.xserver.lower() != 'xpra' and \ + my_opt.xserver.lower() != 'xpra3d' and \ + my_opt.xserver.lower() != 'xephyr': + error("Invalid server '%s'. Use 'xpra', ''xpra3d', or 'xephyr'" % \ + my_opt.xserver) if my_opt.template == "default": if my_opt.withx: - if my_opt.xserver.lower() != 'xpra' and \ - my_opt.xserver.lower() != 'xephyr': - error("Invalid server '%s'. Use 'xpra' or 'xephyr'" % \ - my_opt.xserver) my_opt.template = "sandbox-x" else: my_opt.template = "sandbox" From a13efcfe0a0fcd42238768df7e8094ea0d40e970 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 10:47:01 -0500 Subject: [PATCH 19/46] utils/apparmor/sandbox.py: detect if xpra is running before attach --- utils/apparmor/sandbox.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 61fc66837..de054aee4 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -338,7 +338,19 @@ EndSection cmd(args) sys.exit(0) self.pids.append(listener_x) - time.sleep(2) # FIXME: detect if running + + started = False + for i in range(10): # 5 seconds to start + time.sleep(0.5) + rc, out = cmd(['xpra', 'list']) + if 'LIVE session at %s' % self.display in out: + started = True + break + + if not started: + sys.stdout.flush() + self.cleanup() + raise AppArmorException("Could not start xpra (try again with -d)") # Next, attach to xpra sys.stdout.flush() From 064887dfbd038392ee69f32da7b155584c350a6c Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 10:49:24 -0500 Subject: [PATCH 20/46] catch exception for x.start() --- utils/apparmor/sandbox.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index de054aee4..5098ebdda 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -391,8 +391,13 @@ def run_xsandbox(command, opt): x = SandboxXpra(opt.resolution, command[0], driver="xdummy") else: x = SandboxXpra(opt.resolution, command[0]) - x.start() - apparmor.common.msg("Using 'DISPLAY=%s'" % os.environ["DISPLAY"]) + + try: + x.start() + except Exception as e: + error(e) + + msg("Using 'DISPLAY=%s'" % os.environ["DISPLAY"]) # aa-exec try: From 3fe45e4a9babaeb7d9de809884801c844606d83e Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 10:52:22 -0500 Subject: [PATCH 21/46] utils/apparmor/sandbox.py: sleep for 0.5 seconds initially, then poll every second --- utils/apparmor/sandbox.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 5098ebdda..8d1ecc2ba 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -340,12 +340,13 @@ EndSection self.pids.append(listener_x) started = False - for i in range(10): # 5 seconds to start - time.sleep(0.5) + time.sleep(0.5) + for i in range(5): # 5 seconds to start rc, out = cmd(['xpra', 'list']) if 'LIVE session at %s' % self.display in out: started = True break + time.sleep(1) if not started: sys.stdout.flush() From 056e642d2bf412586d7e9f4277478a5efe4edbd6 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 10:57:28 -0500 Subject: [PATCH 22/46] utils/apparmor/sandbox.py: bail if we don't have xdummy --- utils/apparmor/sandbox.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 8d1ecc2ba..ef7782a31 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -313,6 +313,14 @@ EndSection if rc != 0: raise AppArmorException("Could not find '%s'" % e) + if self.driver == "xdummy": + # FIXME: is there a better way we can detect this? + drv = "/usr/lib/xorg/modules/drivers/dummy_drv.so" + debug("Searching for '%s'" % drv) + rc, report = cmd(['which', drv]) + if rc != 0: + raise AppArmorException("Could not find '%s'" % drv) + xvfb_args = self._get_xvfb_args() listener_x = os.fork() if listener_x == 0: From c062a8a841e8799d766f68212a87c9d99fe9d97c Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 11:09:35 -0500 Subject: [PATCH 23/46] utils/apparmor/sandbox.py: - fix detection of xdummy driver - update comments - add '--no-tray' to 'xpra attach' --- utils/apparmor/sandbox.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index ef7782a31..659cf92ce 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -236,6 +236,12 @@ class SandboxXpra(SandboxXserver): SandboxXserver.cleanup(self) def _get_xvfb_args(self): + '''Setup xvfb arguments''' + # Debugging tip (can also use glxinfo): + # $ xdpyinfo > /tmp/native + # $ aa-sandbox -X -t sandbox-x /usr/bin/xdpyinfo > /tmp/nested + # $ diff -Naur /tmp/native /tmp/nested + xvfb_args = [] if self.driver == None: @@ -317,17 +323,14 @@ EndSection # FIXME: is there a better way we can detect this? drv = "/usr/lib/xorg/modules/drivers/dummy_drv.so" debug("Searching for '%s'" % drv) - rc, report = cmd(['which', drv]) - if rc != 0: + if not os.path.exists(drv): raise AppArmorException("Could not find '%s'" % drv) xvfb_args = self._get_xvfb_args() listener_x = os.fork() if listener_x == 0: - # Debugging tip (can also use glxinfo): - # $ xdpyinfo > /tmp/native - # $ aa-sandbox -X -t sandbox-x /usr/bin/xdpyinfo > /tmp/nested - # $ diff -Naur /tmp/native /tmp/nested + # This will clean out any dead sessions + cmd(['xpra', 'list']) x_args = ['--no-daemon', #'--no-mmap', # for security? @@ -369,10 +372,10 @@ EndSection args = ['/usr/bin/xpra', 'attach', self.display, '--title=%s' % self.title, #'--no-mmap', # for security? + '--no-tray', '--no-clipboard', '--no-pulseaudio'] debug(" ".join(args)) - #cmd(args) if apparmor.common.DEBUGGING == True: sys.stderr.flush() os.execv(args[0], args) From 72dbf597cce14ecd779c3546ce9079445ecac6be Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 11:21:21 -0500 Subject: [PATCH 24/46] utils/apparmor/sandbox.py: use pkexec if '--with-x' is specified --- utils/apparmor/sandbox.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 659cf92ce..ef99babde 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -24,6 +24,7 @@ def check_requirements(binary): 'aa-easyprof', # for templates 'aa-exec', # for changing profile 'sudo', # eventually get rid of this + 'pkexec', # eventually get rid of this binary] for e in exes: @@ -110,8 +111,11 @@ def aa_exec(command, opt): tmp.flush() debug("using '%s' template" % opt.template) - # TODO: get rid of sudo - rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) + # TODO: get rid of this + if opt.withx == True: + rc, report = cmd(['pkexec', 'apparmor_parser', '-r', '%s' % tmp.name]) + else: + rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) if rc != 0: raise AppArmorException("Could not load policy") From ec5973a3e6bbf78f8e7a8b15553efdcf72a1437a Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 12:06:54 -0500 Subject: [PATCH 25/46] utils/apparmor/sandbox.py: change 'resolution' to 'geometry' --- utils/apparmor/sandbox.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index ef99babde..a7e91a0bf 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -55,8 +55,8 @@ def parse_args(args=None, parser=None): default=False, help='Show debug messages', action='store_true') - parser.add_option('-r', '--with-resolution', - dest='resolution', + parser.add_option('-r', '--with-geometry', + dest='geometry', default='640x480', help='Resolution for X application') parser.add_option('--profile', @@ -130,8 +130,8 @@ def run_sandbox(command, opt): return rc, report class SandboxXserver(): - def __init__(self, resolution, title, driver=None): - self.resolution = resolution + def __init__(self, geometry, title, driver=None): + self.geometry = geometry self.title = title self.pids = [] self.find_free_x_display() @@ -200,7 +200,7 @@ class SandboxXephyr(SandboxXserver): ] x_args = ['-nolisten', 'tcp', - '-screen', self.resolution, + '-screen', self.geometry, '-br', # black background '-reset', # reset after last client exists '-terminate', # terminate at server reset @@ -390,7 +390,7 @@ EndSection self.pids.append(listener_attach) os.environ["DISPLAY"] = self.display - msg("TODO: --with-resolution not honored in xpra") + msg("TODO: --with-geometry not honored in xpra") msg("TODO: filter '~/.xpra/run-xpra'") def run_xsandbox(command, opt): @@ -402,11 +402,11 @@ def run_xsandbox(command, opt): # first, start X if opt.xserver.lower() == "xephyr": - x = SandboxXephyr(opt.resolution, command[0]) + x = SandboxXephyr(opt.geometry, command[0]) elif opt.xserver.lower() == "xpra3d": - x = SandboxXpra(opt.resolution, command[0], driver="xdummy") + x = SandboxXpra(opt.geometry, command[0], driver="xdummy") else: - x = SandboxXpra(opt.resolution, command[0]) + x = SandboxXpra(opt.geometry, command[0]) try: x.start() From bb58f40ae3ebbbe3c8b8c362f8313d9a75faa747 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 12:07:19 -0500 Subject: [PATCH 26/46] add utils/aa-sandbox.pod --- utils/aa-sandbox.pod | 137 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 utils/aa-sandbox.pod diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod new file mode 100644 index 000000000..42ef1d30c --- /dev/null +++ b/utils/aa-sandbox.pod @@ -0,0 +1,137 @@ +# This publication is intellectual property of Canonical Ltd. Its contents +# can be duplicated, either in part or in whole, provided that a copyright +# label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither Canonical Ltd, the authors, nor the translators shall be held +# liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. Canonical Ltd +# essentially adheres to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + +=pod + +=head1 NAME + +aa-sandbox - AppArmor sandboxing + +=head1 SYNOPSIS + +B [option] + +=head1 DESCRIPTION + +B provides a mechanism for sandboxing an application using an +existing profile or via dynamic profile generation. Please note that while this +tool can help with quickly defining an application, its utility is dependent on +the quality of the templates, policy groups and abstractions used. Also, this +tool may create policy which is less restricted than creating policy by hand or +with B and B. + +=head1 OPTIONS + +B accepts the following arguments: + +=over 4 + +=item -t TEMPLATE, --template=TEMPLATE + +Specify the template used to generate a profile. May specify either a system +template or a filename for the template to use. See aa-easyprof(8) for more +information. If not specified, uses B or when using B<-X>, +B. + +=item -p POLICYGROUPS, --policy-groups=POLICYGROUPS + +Specify POLICYGROUPS as a comma-separated list of policy groups. See +aa-easyprof(8) for more information on POLICYGROUPS. + +=item -a ABSTRACTIONS, --abstractions=ABSTRACTIONS + +Specify ABSTRACTIONS as a comma-separated list of AppArmor abstractions. +AppArmor abstractions are located in /etc/apparmor.d/abstractions. See +apparmor.d(5) for details. + +=item -r PATH, --read-path=PATH + +Specify a PATH to allow owner reads. May be specified multiple times. If the +PATH ends in a '/', then PATH is treated as a directory and reads are allowed +to all files under this directory. Can optionally use '/*' at the end of the +PATH to only allow reads to files directly in PATH. + +=item -w PATH, --write-dir=PATH + +Like --read-path but also allow owner writes in additions to reads. + +=item --profile=PROFILE + +Instead of generating a dynamic profile, specify an existing, loaded profile. +This does not require root privileges. + +=item -X, --with-x + +Run the sandboxed application in an isolated X server. + +=item --with-xserver=XSERVER + +Choose the nested XSERVER to use. Supported servers are: B, B and +B. xpra uses the Xvfb(1) virtual framebuffer X server while xpra3d uses +the Xorg(1) server with the Xdummy (dummy_drv.so) driver. + +=item -g GEOMETRY, --with-geometry=GEOMETRY + +The starting geometry to use. Currently only supported with the B +server. + +=back + +=head1 EXAMPLES + +Use the existing system profile 'firefox' to sandbox /usr/bin/firefox: + +=over + +$ aa-sandbox -X --profile=firefox /usr/bin/firefox + +=back + +Sandbox xeyes: + +=over + +$ aa-sandbox -X /usr/bin/xeyes + +=back + +Sandbox glxgears: + +=over + +$ aa-sandbox -X --with-xserver=xpra3d /usr/bin/glxgears + +=back + +Sandbox uptime: + +=over + +$ aa-sandbox --read-path="/proc/*" /usr/bin/uptime + +=head1 BUGS + +If you find any bugs, please report them to Launchpad at +L. + +=head1 SEE ALSO + +apparmor(7) apparmor.d(5) xpra(1) Xvfb(1) Xorg(1) Xephyr(1) aa-easyprof(8) + +=cut From fd4986e726ee4079996bc5626e5f48566d232f31 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 24 Aug 2012 12:16:20 -0500 Subject: [PATCH 27/46] manpage updates --- utils/aa-sandbox.pod | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index 42ef1d30c..2ef6091ff 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -31,7 +31,7 @@ B [option] B provides a mechanism for sandboxing an application using an existing profile or via dynamic profile generation. Please note that while this -tool can help with quickly defining an application, its utility is dependent on +tool can help with quickly confining an application, its utility is dependent on the quality of the templates, policy groups and abstractions used. Also, this tool may create policy which is less restricted than creating policy by hand or with B and B. @@ -45,9 +45,10 @@ B accepts the following arguments: =item -t TEMPLATE, --template=TEMPLATE Specify the template used to generate a profile. May specify either a system -template or a filename for the template to use. See aa-easyprof(8) for more -information. If not specified, uses B or when using B<-X>, -B. +template or a filename for the template to use. If not specified, uses +B or B when B<-X> is specified. See aa-easyprof(8) for +details. Privileged access is required to load the dynamically generated +profile (B will prompt for a password). =item -p POLICYGROUPS, --policy-groups=POLICYGROUPS @@ -62,19 +63,19 @@ apparmor.d(5) for details. =item -r PATH, --read-path=PATH -Specify a PATH to allow owner reads. May be specified multiple times. If the -PATH ends in a '/', then PATH is treated as a directory and reads are allowed -to all files under this directory. Can optionally use '/*' at the end of the -PATH to only allow reads to files directly in PATH. +Specify a PATH to allow reads. May be specified multiple times. If the PATH +ends in a '/', then PATH is treated as a directory and reads are allowed to all +files under this directory. Can optionally use '/*' at the end of the PATH to +only allow reads to files directly in PATH. =item -w PATH, --write-dir=PATH -Like --read-path but also allow owner writes in additions to reads. +Like --read-path but also allow writes in addition to reads. =item --profile=PROFILE Instead of generating a dynamic profile, specify an existing, loaded profile. -This does not require root privileges. +This does not require privileged access. =item -X, --with-x @@ -82,9 +83,9 @@ Run the sandboxed application in an isolated X server. =item --with-xserver=XSERVER -Choose the nested XSERVER to use. Supported servers are: B, B and -B. xpra uses the Xvfb(1) virtual framebuffer X server while xpra3d uses -the Xorg(1) server with the Xdummy (dummy_drv.so) driver. +Choose the nested XSERVER to use. Supported servers are: B (the default), +B and B. xpra uses the Xvfb(1) virtual framebuffer X server +while xpra3d uses the Xorg(1) server with the Xdummy (dummy_drv.so) driver. =item -g GEOMETRY, --with-geometry=GEOMETRY @@ -125,6 +126,8 @@ Sandbox uptime: $ aa-sandbox --read-path="/proc/*" /usr/bin/uptime +=back + =head1 BUGS If you find any bugs, please report them to Launchpad at From 3ad2820ebdf83ede76218bb27e516d071f685bd5 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 10:54:26 -0500 Subject: [PATCH 28/46] utils/apparmor/sandbox.py: - cleanup environment handling - refactor cleanup code - verify Xsecurity is setup correctly (ie, interpret output of xhost) - add generation of .Xauthority-sandbox... - explitly use SECURITY extension --- utils/apparmor/sandbox.py | 155 +++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 37 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index a7e91a0bf..7fcb07f6f 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -82,10 +82,17 @@ def parse_args(args=None, parser=None): def gen_policy_name(binary): '''Generate a temporary policy based on the binary name''' - return "sandbox-%s%s" % (pwd.getpwuid(os.getuid())[0], + return "sandbox-%s%s" % (pwd.getpwuid(os.geteuid())[0], re.sub(r'/', '_', binary)) -def aa_exec(command, opt): +def set_environ(env): + keys = env.keys() + keys.sort() + for k in keys: + msg("Using: %s=%s" % (k, env[k])) + os.environ[k] = env[k] + +def aa_exec(command, opt, environ={}): '''Execute binary under specified policy''' if opt.profile != None: policy_name = opt.profile @@ -119,6 +126,7 @@ def aa_exec(command, opt): if rc != 0: raise AppArmorException("Could not load policy") + set_environ(environ) args = ['aa-exec', '-p', policy_name] + command rc, report = cmd(args) return rc, report @@ -134,18 +142,45 @@ class SandboxXserver(): self.geometry = geometry self.title = title self.pids = [] - self.find_free_x_display() self.driver = driver self.tempfiles = [] + self.timeout = 5 # used by xauth and for server starts - # TODO: for now, drop Unity's globalmenu proxy since it doesn't work - # right in the application. (Doesn't work with firefox) - os.environ["UBUNTU_MENUPROXY"] = "" + # preserve our environment + self.old_environ = dict() + self.old_environ['DISPLAY'] = os.environ['DISPLAY'] + self.old_environ['XAUTHORITY'] = os.environ['XAUTHORITY'] + self.old_environ['UBUNTU_MENUPROXY'] = os.environ['UBUNTU_MENUPROXY'] + + # prepare the new environment + self.display, self.xauth = self.find_free_x_display() + self.new_environ = dict() + self.new_environ['DISPLAY'] = self.display + self.new_environ['XAUTHORITY'] = self.xauth + # Disable the global menu for now + self.new_environ["UBUNTU_MENUPROXY"] = "" + + def cleanup(self): + '''Cleanup our forked pids, reset the environment, etc''' + for pid in self.pids: + # kill server now. It should've terminated, but be sure + debug("Killing '%d'" % pid) + cmd(['kill', "%d" % pid]) + + for t in self.tempfiles: + if os.path.exists(t): + os.unlink(t) + + if os.path.exists(self.xauth): + os.unlink(self.xauth) + + # Reset our environment + set_environ(self.old_environ) def find_free_x_display(self): '''Find a free X display''' display = "" - current = os.environ["DISPLAY"] + current = self.old_environ["DISPLAY"] for i in range(1,257): # TODO: this puts an artificial limit of 256 # sandboxed applications tmp = ":%d" % i @@ -159,21 +194,49 @@ class SandboxXserver(): if display == "": raise AppArmorException("Could not find available X display") - self.display = display + # Use dedicated .Xauthority file + xauth = os.path.join(os.path.expanduser('~'), \ + '.Xauthority-sandbox%s' % display.split(':')[1]) - def cleanup(self): - '''Cleanup our forked pids, etc''' - # kill server now. It should've terminated, but be sure - for pid in self.pids: - os.kill(pid, 15) - os.waitpid(pid, 0) + return display, xauth - for t in self.tempfiles: - if os.path.exists(t): - os.unlink(t) + def verify_host_setup(self): + '''Make sure we have everything we need''' + old_lang = None + if 'LANG' in os.environ: + old_lang = os.environ['LANG'] + + os.environ['LANG'] = 'C' + rc, report = cmd(['xhost']) + + if old_lang: + os.environ['LANG'] = old_lang + + if rc != 0: + raise AppArmorException("'xhost' exited with error") + if 'access control enabled' not in report: + raise AppArmorException("Access control currently disabled. Please enable with 'xhost -'") + username = pwd.getpwuid(os.geteuid())[0] + if ':localuser:%s' % username in report: + raise AppArmorException("Access control allows '%s' full access. Please see 'man aa-sandbox' for details") def start(self): - '''start() should be overridden''' + '''Start a nested X server (need to override)''' + # clean up the old one + if os.path.exists(self.xauth): + os.unlink(self.xauth) + rc, cookie = cmd(['mcookie']) + if rc != 0: + raise AppArmorException("Could not generate magic cookie") + + rc, out = cmd(['xauth', '-f', self.xauth, \ + 'add', \ + self.display, \ + 'MIT-MAGIC-COOKIE-1', \ + cookie.strip()]) + if rc != 0: + raise AppArmorException("Could not generate '%s'" % self.display) + class SandboxXephyr(SandboxXserver): def start(self): @@ -183,6 +246,9 @@ class SandboxXephyr(SandboxXserver): if rc != 0: raise AppArmorException("Could not find '%s'" % e) + '''Run any setup code''' + SandboxXserver.start(self) + '''Start a Xephyr server''' listener_x = os.fork() if listener_x == 0: @@ -221,8 +287,9 @@ class SandboxXephyr(SandboxXserver): listener_wm = os.fork() if listener_wm == 0: # update environment - os.environ["DISPLAY"] = self.display - debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) + set_environ(self.new_environ) + #os.environ["DISPLAY"] = self.display + #debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] debug(" ".join(args)) @@ -232,11 +299,16 @@ class SandboxXephyr(SandboxXserver): self.pids.append(listener_wm) time.sleep(1) # FIXME: detect if running - os.environ["DISPLAY"] = self.display class SandboxXpra(SandboxXserver): def cleanup(self): - cmd(['xpra', 'stop', self.display]) + # This can block + listener = os.fork() + if listener == 0: + cmd(['xpra', 'stop', self.display]) + sys.exit(0) + self.pids.append(listener_x) + time.sleep(2) SandboxXserver.cleanup(self) def _get_xvfb_args(self): @@ -254,8 +326,9 @@ class SandboxXpra(SandboxXserver): xvfb_args.append('-screen 0 3840x2560x24+32') xvfb_args.append('-nolisten tcp') xvfb_args.append('-noreset') - xvfb_args.append('-auth %s' % os.environ['XAUTHORITY']) + xvfb_args.append('-auth %s' % self.new_environ['XAUTHORITY']) xvfb_args.append('+extension Composite') + xvfb_args.append('+extension SECURITY') xvfb_args.append('-extension GLX') elif self.driver == 'xdummy': # The dummy driver allows us to use GLX, etc. See: @@ -306,9 +379,9 @@ EndSection xvfb_args.append('-nolisten tcp') xvfb_args.append('-noreset') xvfb_args.append('-logfile %s' % os.path.expanduser('~/.xpra/%s.log' % self.display)) - xvfb_args.append('-auth %s' % os.environ['XAUTHORITY']) + xvfb_args.append('-auth %s' % self.new_environ['XAUTHORITY']) xvfb_args.append('-config %s' % xorg_conf) - extensions = ['Composite', 'GLX', 'RANDR', 'RENDER'] + extensions = ['Composite', 'GLX', 'RANDR', 'RENDER', 'SECURITY'] for i in extensions: xvfb_args.append('+extension %s' % i) else: @@ -330,9 +403,14 @@ EndSection if not os.path.exists(drv): raise AppArmorException("Could not find '%s'" % drv) + '''Run any setup code''' + SandboxXserver.start(self) + xvfb_args = self._get_xvfb_args() listener_x = os.fork() if listener_x == 0: + os.environ['XAUTHORITY'] = self.xauth + # This will clean out any dead sessions cmd(['xpra', 'list']) @@ -356,7 +434,7 @@ EndSection started = False time.sleep(0.5) - for i in range(5): # 5 seconds to start + for i in range(self.timeout): # 5 seconds to start rc, out = cmd(['xpra', 'list']) if 'LIVE session at %s' % self.display in out: started = True @@ -389,15 +467,15 @@ EndSection self.pids.append(listener_attach) - os.environ["DISPLAY"] = self.display msg("TODO: --with-geometry not honored in xpra") msg("TODO: filter '~/.xpra/run-xpra'") + def cleanup(self): + cmd(['xpra', 'stop', self.display]) + SandboxXserver.cleanup(self) + def run_xsandbox(command, opt): '''Run X application in a sandbox''' - # save environment - old_display = os.environ["DISPLAY"] - debug ("DISPLAY=%s" % old_display) old_cwd = os.getcwd() # first, start X @@ -408,24 +486,27 @@ def run_xsandbox(command, opt): else: x = SandboxXpra(opt.geometry, command[0]) + x.verify_host_setup() + + # Debug: show old environment + keys = x.old_environ.keys() + keys.sort() + for k in keys: + debug ("Old: %s=%s" % (k, x.old_environ[k])) + try: x.start() except Exception as e: error(e) - msg("Using 'DISPLAY=%s'" % os.environ["DISPLAY"]) - # aa-exec try: - rc, report = aa_exec(command, opt) + rc, report = aa_exec(command, opt, x.new_environ) + #x.start2() except Exception as e: x.cleanup() raise x.cleanup() - - # reset environment os.chdir(old_cwd) - os.environ["DISPLAY"] = old_display - debug("DISPLAY restored to: %s" % os.environ["DISPLAY"]) return rc, report From ea6b1568b43c51236e58e3fd1a4b7213e42744af Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 15:27:30 -0500 Subject: [PATCH 29/46] utils/apparmor/sandbox.py: - use signal. instead of hardcoding a number - add --with-xauthority option - remove '-r' and '--with-geometry' and use --with-xephyr-geometry instead - allow passing arguments to the application when using aa-exec - kill with SIGTERM, then try again with SIGKILL - always use os.execv() in forks. Using cmd() when not specifying '-d' created different behaviors between debug and non-debug mode - better cleanup Xpra when aa-exec command fails - use the full dummy.xorg.conf, which gives us the correct modelines for large displays. This fixes the issue "Server's virtual screen is too small .... You may see strange behavior." which should up when the window's size was bigger than the 'current server resolution' --- utils/apparmor/sandbox.py | 180 +++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 34 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 7fcb07f6f..0dccd0c90 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -14,6 +14,7 @@ import optparse import os import pwd import re +import signal import sys import tempfile import time @@ -50,15 +51,19 @@ def parse_args(args=None, parser=None): dest='xserver', default='xpra', help='Nested X server to use: xpra (default), xpra3d, xephyr') + parser.add_option('--with-xauthority', + dest='xauthority', + default=None, + help='Specify Xauthority file to use') parser.add_option('-d', '--debug', dest='debug', default=False, help='Show debug messages', action='store_true') - parser.add_option('-r', '--with-geometry', - dest='geometry', + parser.add_option('--with-xephyr-geometry', + dest='xephyr_geometry', default='640x480', - help='Resolution for X application') + help='Geometry for Xephyr window') parser.add_option('--profile', dest='profile', default=None, @@ -127,7 +132,7 @@ def aa_exec(command, opt, environ={}): raise AppArmorException("Could not load policy") set_environ(environ) - args = ['aa-exec', '-p', policy_name] + command + args = ['aa-exec', '-p', policy_name, '--'] + command rc, report = cmd(args) return rc, report @@ -138,7 +143,7 @@ def run_sandbox(command, opt): return rc, report class SandboxXserver(): - def __init__(self, geometry, title, driver=None): + def __init__(self, title, geometry=None, driver=None, xauth=None): self.geometry = geometry self.title = title self.pids = [] @@ -154,6 +159,11 @@ class SandboxXserver(): # prepare the new environment self.display, self.xauth = self.find_free_x_display() + if xauth: + abs_xauth = os.path.expanduser(xauth) + if os.path.expanduser("~/.Xauthority") == abs_xauth: + raise AppArmorException("Trusted Xauthority file specified. Aborting") + self.xauth = abs_xauth self.new_environ = dict() self.new_environ['DISPLAY'] = self.display self.new_environ['XAUTHORITY'] = self.xauth @@ -162,10 +172,17 @@ class SandboxXserver(): def cleanup(self): '''Cleanup our forked pids, reset the environment, etc''' + self.pids.reverse() + debug(self.pids) for pid in self.pids: - # kill server now. It should've terminated, but be sure - debug("Killing '%d'" % pid) - cmd(['kill', "%d" % pid]) + # Kill server with TERM + debug("kill %d" % pid) + os.kill(pid, signal.SIGTERM) + + for pid in self.pids: + # Shoot the server dead + debug("kill -9 %d" % pid) + os.kill(pid, signal.SIGKILL) for t in self.tempfiles: if os.path.exists(t): @@ -275,7 +292,7 @@ class SandboxXephyr(SandboxXserver): args = ['/usr/bin/Xephyr'] + x_args + [self.display] debug(" ".join(args)) - cmd(args) + os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) @@ -288,8 +305,6 @@ class SandboxXephyr(SandboxXserver): if listener_wm == 0: # update environment set_environ(self.new_environ) - #os.environ["DISPLAY"] = self.display - #debug("DISPLAY is now '%s'" % os.environ["DISPLAY"]) args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no'] debug(" ".join(args)) @@ -302,13 +317,22 @@ class SandboxXephyr(SandboxXserver): class SandboxXpra(SandboxXserver): def cleanup(self): - # This can block + sys.stderr.flush() listener = os.fork() if listener == 0: - cmd(['xpra', 'stop', self.display]) + args = ['/usr/bin/xpra', 'stop', self.display] + debug(" ".join(args)) + os.execv(args[0], args) sys.exit(0) - self.pids.append(listener_x) time.sleep(2) + + # Annoyingly, xpra doesn't clean up itself well if the application + # failed for some reason. Try to account for that. + rc, report = cmd(['ps', 'auxww']) + for line in report.splitlines(): + if '-for-Xpra-%s' % self.display in line: + self.pids.append(int(line.split()[1])) + SandboxXserver.cleanup(self) def _get_xvfb_args(self): @@ -333,7 +357,8 @@ class SandboxXpra(SandboxXserver): elif self.driver == 'xdummy': # The dummy driver allows us to use GLX, etc. See: # http://xpra.org/Xdummy.html - conf = '''# Based on /usr/share/doc/xpra/examples/dummy.xorg.conf.gz + conf = '''# /usr/share/doc/xpra/examples/dummy.xorg.conf.gz +# http://xpra.org/Xdummy.html ##Xdummy:## Section "ServerFlags" Option "DontVTSwitch" "true" @@ -343,6 +368,7 @@ Section "ServerFlags" Option "AutoAddDevices" "false" EndSection + ##Xdummy:## Section "InputDevice" Identifier "NoMouse" @@ -360,10 +386,107 @@ EndSection Section "Device" Identifier "Videocard0" Driver "dummy" + # In kByte #VideoRam 4096000 - #VideoRam 256000 + VideoRam 256000 EndSection +##Xdummy:## +Section "Monitor" + Identifier "Monitor0" + HorizSync 10.0 - 300.0 + VertRefresh 10.0 - 200.0 + DisplaySize 4335 1084 + #The following modeline is invalid (calculator overflowed): + #Modeline "32000x32000@0" -38917.43 32000 32032 -115848 -115816 32000 32775 32826 33601 + Modeline "16384x8192@10" 2101.93 16384 16416 24400 24432 8192 8390 8403 8602 + Modeline "8192x4096@10" 424.46 8192 8224 9832 9864 4096 4195 4202 4301 + Modeline "5120x3200@10" 199.75 5120 5152 5904 5936 3200 3277 3283 3361 + Modeline "3840x2880@10" 133.43 3840 3872 4376 4408 2880 2950 2955 3025 + Modeline "3840x2560@10" 116.93 3840 3872 4312 4344 2560 2622 2627 2689 + Modeline "3840x2048@10" 91.45 3840 3872 4216 4248 2048 2097 2101 2151 + Modeline "2048x2048@10" 49.47 2048 2080 2264 2296 2048 2097 2101 2151 + Modeline "2560x1600@10" 47.12 2560 2592 2768 2800 1600 1639 1642 1681 + Modeline "1920x1200@10" 26.28 1920 1952 2048 2080 1200 1229 1231 1261 + Modeline "1920x1080@10" 23.53 1920 1952 2040 2072 1080 1106 1108 1135 + Modeline "1680x1050@10" 20.08 1680 1712 1784 1816 1050 1075 1077 1103 + Modeline "1600x900@20" 33.92 1600 1632 1760 1792 900 921 924 946 + Modeline "1440x900@20" 30.66 1440 1472 1584 1616 900 921 924 946 + Modeline "1360x768@20" 24.49 1360 1392 1480 1512 768 786 789 807 + #common resolutions for android devices (both orientations): + Modeline "800x1280@20" 25.89 800 832 928 960 1280 1310 1315 1345 + Modeline "1280x800@20" 24.15 1280 1312 1400 1432 800 819 822 841 + Modeline "720x1280@25" 30.22 720 752 864 896 1280 1309 1315 1345 + Modeline "1280x720@25" 27.41 1280 1312 1416 1448 720 737 740 757 + Modeline "768x1024@25" 24.93 768 800 888 920 1024 1047 1052 1076 + Modeline "1024x768@25" 23.77 1024 1056 1144 1176 768 785 789 807 + Modeline "600x1024@25" 19.90 600 632 704 736 1024 1047 1052 1076 + Modeline "1024x600@25" 18.26 1024 1056 1120 1152 600 614 617 631 + Modeline "536x960@25" 16.74 536 568 624 656 960 982 986 1009 + Modeline "960x536@25" 15.23 960 992 1048 1080 536 548 551 563 + Modeline "600x800@25" 15.17 600 632 688 720 800 818 822 841 + Modeline "800x600@25" 14.50 800 832 880 912 600 614 617 631 + Modeline "480x854@25" 13.34 480 512 560 592 854 873 877 897 + Modeline "848x480@25" 12.09 848 880 920 952 480 491 493 505 + Modeline "480x800@25" 12.43 480 512 552 584 800 818 822 841 + Modeline "800x480@25" 11.46 800 832 872 904 480 491 493 505 + Modeline "320x480@50" 10.73 320 352 392 424 480 490 494 505 + Modeline "480x320@50" 9.79 480 512 544 576 320 327 330 337 + Modeline "240x400@50" 6.96 240 272 296 328 400 408 412 421 + Modeline "400x240@50" 6.17 400 432 448 480 240 245 247 253 + Modeline "240x320@50" 5.47 240 272 288 320 320 327 330 337 + Modeline "320x240@50" 5.10 320 352 368 400 240 245 247 253 + #resolutions for android devices (both orientations) + #minus the status bar + #38px status bar (and width rounded up) + Modeline "800x1242@20" 25.03 800 832 920 952 1242 1271 1275 1305 + Modeline "1280x762@20" 22.93 1280 1312 1392 1424 762 780 783 801 + Modeline "720x1242@25" 29.20 720 752 856 888 1242 1271 1276 1305 + Modeline "1280x682@25" 25.85 1280 1312 1408 1440 682 698 701 717 + Modeline "768x986@25" 23.90 768 800 888 920 986 1009 1013 1036 + Modeline "1024x730@25" 22.50 1024 1056 1136 1168 730 747 750 767 + Modeline "600x986@25" 19.07 600 632 704 736 986 1009 1013 1036 + Modeline "1024x562@25" 17.03 1024 1056 1120 1152 562 575 578 591 + Modeline "536x922@25" 16.01 536 568 624 656 922 943 947 969 + Modeline "960x498@25" 14.09 960 992 1040 1072 498 509 511 523 + Modeline "600x762@25" 14.39 600 632 680 712 762 779 783 801 + Modeline "800x562@25" 13.52 800 832 880 912 562 575 578 591 + Modeline "480x810@25" 12.59 480 512 552 584 810 828 832 851 + Modeline "848x442@25" 11.09 848 880 920 952 442 452 454 465 + Modeline "480x762@25" 11.79 480 512 552 584 762 779 783 801 + Modeline "800x442@25" 10.51 800 832 864 896 442 452 454 465 + #32px status bar (no need for rounding): + Modeline "320x448@50" 9.93 320 352 384 416 448 457 461 471 + Modeline "480x288@50" 8.75 480 512 544 576 288 294 297 303 + #24px status bar: + Modeline "240x376@50" 6.49 240 272 296 328 376 384 387 395 + Modeline "400x216@50" 5.50 400 432 448 480 216 220 222 227 + Modeline "240x296@50" 5.02 240 272 288 320 296 302 305 311 + Modeline "320x216@50" 4.55 320 352 368 400 216 220 222 227 +EndSection + +##Xdummy:## +Section "Screen" + Identifier "Screen0" + Device "Videocard0" + Monitor "Monitor0" + DefaultDepth 24 + SubSection "Display" + Viewport 0 0 + Depth 24 + Modes "32000x32000" "16384x8192" "8192x4096" "5120x3200" "3840x2880" "3840x2560" "3840x2048" "2048x2048" "2560x1600" "1920x1440" "1920x1200" "1920x1080" "1600x1200" "1680x1050" "1600x900" "1400x1050" "1440x900" "1280x1024" "1366x768" "1280x800" "1024x768" "1024x600" "800x600" "320x200" + #Virtual 32000 32000 + #Virtual 16384 8192 + Virtual 8192 4096 + EndSubSection +EndSection + +Section "ServerLayout" + Identifier "dummy_layout" + Screen "screen0" + InputDevice "NoMouse" + InputDevice "NoKeyboard" +EndSection ''' tmp, xorg_conf = tempfile.mkstemp(prefix='aa-sandbox-xorg.conf-') @@ -424,11 +547,8 @@ EndSection args = ['/usr/bin/xpra', 'start', self.display] + x_args debug(" ".join(args)) - if apparmor.common.DEBUGGING == True: - sys.stderr.flush() - os.execv(args[0], args) - else: - cmd(args) + sys.stderr.flush() + os.execv(args[0], args) sys.exit(0) self.pids.append(listener_x) @@ -458,21 +578,14 @@ EndSection '--no-clipboard', '--no-pulseaudio'] debug(" ".join(args)) - if apparmor.common.DEBUGGING == True: - sys.stderr.flush() - os.execv(args[0], args) - else: - cmd(args) + sys.stderr.flush() + os.execv(args[0], args) sys.exit(0) self.pids.append(listener_attach) - msg("TODO: --with-geometry not honored in xpra") msg("TODO: filter '~/.xpra/run-xpra'") - def cleanup(self): - cmd(['xpra', 'stop', self.display]) - SandboxXserver.cleanup(self) def run_xsandbox(command, opt): '''Run X application in a sandbox''' @@ -480,11 +593,11 @@ def run_xsandbox(command, opt): # first, start X if opt.xserver.lower() == "xephyr": - x = SandboxXephyr(opt.geometry, command[0]) + x = SandboxXephyr(command[0], opt.geometry, xauth=opt.xauthority) elif opt.xserver.lower() == "xpra3d": - x = SandboxXpra(opt.geometry, command[0], driver="xdummy") + x = SandboxXpra(command[0], geometry=None, driver="xdummy", xauth=opt.xauthority) else: - x = SandboxXpra(opt.geometry, command[0]) + x = SandboxXpra(command[0], geometry=None, xauth=opt.xauthority) x.verify_host_setup() @@ -502,7 +615,6 @@ def run_xsandbox(command, opt): # aa-exec try: rc, report = aa_exec(command, opt, x.new_environ) - #x.start2() except Exception as e: x.cleanup() raise From 392b5e07c0ba86e7aed2f4b31759038eb4be8a62 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 16:11:01 -0500 Subject: [PATCH 30/46] various fixes based on feedback from James Troup. --- utils/aa-sandbox.pod | 27 ++++++++++++++++++++++++++- utils/apparmor/common.py | 2 +- utils/apparmor/sandbox.py | 37 ++++++++++++++++++++++--------------- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index 2ef6091ff..4c4339a5e 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -33,7 +33,7 @@ B provides a mechanism for sandboxing an application using an existing profile or via dynamic profile generation. Please note that while this tool can help with quickly confining an application, its utility is dependent on the quality of the templates, policy groups and abstractions used. Also, this -tool may create policy which is less restricted than creating policy by hand or +tool may create policy which is less restrictive than creating policy by hand or with B and B. =head1 OPTIONS @@ -128,6 +128,30 @@ $ aa-sandbox --read-path="/proc/*" /usr/bin/uptime =back +=head1 NOTES + +B currently relies on Xsecurity rules based on Xauthority. As such, +xhost access controls need to be enabled and server interpreted values for +localuser must be removed. One way of achieving this is adding a late running +Xsession(5) script of the form: + +=over + +# Create an Xauthority file if it doesn't exist + +[ ! -f "$HOME/.Xauthority" ] && [ -x /usr/bin/xauth ] && + xauth generate :0 . trusted > /dev/null + +# Default to the Xauthority file + +[ -f "$HOME/.Xauthority" ] && [ -x /usr/bin/xhost ] && [ -x /usr/bin/id ] && + xhost -si:localuser:`id -un` > /dev/null + +=back + +After adding the above, it is recommended you remove the existing ~/.Xauthority +file, then restart your session. + =head1 BUGS If you find any bugs, please report them to Launchpad at @@ -136,5 +160,6 @@ L. =head1 SEE ALSO apparmor(7) apparmor.d(5) xpra(1) Xvfb(1) Xorg(1) Xephyr(1) aa-easyprof(8) +Xecurity(7) =cut diff --git a/utils/apparmor/common.py b/utils/apparmor/common.py index 32ccc1672..983690d0a 100644 --- a/utils/apparmor/common.py +++ b/utils/apparmor/common.py @@ -48,7 +48,7 @@ def warn(out): def msg(out, output=sys.stdout): '''Print message''' try: - print("%s" % (out), file=sys.stdout) + print("%s" % (out), file=output) except IOError: pass diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 0dccd0c90..c8fd232eb 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -70,13 +70,13 @@ def parse_args(args=None, parser=None): help='Specify an existing profile (see aa-status)') (my_opt, my_args) = parser.parse_args() - if my_opt.debug == True: + if my_opt.debug: apparmor.common.DEBUGGING = True - if my_opt.withx and my_opt.xserver.lower() != 'xpra' and \ - my_opt.xserver.lower() != 'xpra3d' and \ - my_opt.xserver.lower() != 'xephyr': - error("Invalid server '%s'. Use 'xpra', ''xpra3d', or 'xephyr'" % \ - my_opt.xserver) + + valid_xservers = ['xpra', 'xpra3d', 'xephyr'] + if my_opt.withx and my_opt.xserver.lower() not in valid_xservers: + error("Invalid server '%s'. Use one of: %s" % (my_opt.xserver, \ + ", ".join(valid_xservers))) if my_opt.template == "default": if my_opt.withx: my_opt.template = "sandbox-x" @@ -109,7 +109,7 @@ def aa_exec(command, opt, environ={}): opt.ensure_value("copyright", None) binary = command[0] - policy_name = apparmor.sandbox.gen_policy_name(binary) + policy_name = gen_policy_name(binary) easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt) params = apparmor.easyprof.gen_policy_params(policy_name, opt) policy = easyp.gen_policy(**params) @@ -124,7 +124,7 @@ def aa_exec(command, opt, environ={}): debug("using '%s' template" % opt.template) # TODO: get rid of this - if opt.withx == True: + if opt.withx: rc, report = cmd(['pkexec', 'apparmor_parser', '-r', '%s' % tmp.name]) else: rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) @@ -196,6 +196,11 @@ class SandboxXserver(): def find_free_x_display(self): '''Find a free X display''' + old_lang = None + if 'LANG' in os.environ: + old_lang = os.environ['LANG'] + os.environ['LANG'] = 'C' + display = "" current = self.old_environ["DISPLAY"] for i in range(1,257): # TODO: this puts an artificial limit of 256 @@ -203,10 +208,13 @@ class SandboxXserver(): tmp = ":%d" % i os.environ["DISPLAY"] = tmp rc, report = cmd(['xset', '-q']) - if rc != 0: + if rc != 0 and 'Invalid MIT-MAGIC-COOKIE-1' not in report: display = tmp break + if old_lang: + os.environ['LANG'] = old_lang + os.environ["DISPLAY"] = current if display == "": raise AppArmorException("Could not find available X display") @@ -513,11 +521,10 @@ EndSection return xvfb_args def start(self): - for e in ['xpra']: - debug("Searching for '%s'" % e) - rc, report = cmd(['which', e]) - if rc != 0: - raise AppArmorException("Could not find '%s'" % e) + debug("Searching for '%s'" % 'xpra') + rc, report = cmd(['which', 'xpra']) + if rc != 0: + raise AppArmorException("Could not find '%s'" % 'xpra') if self.driver == "xdummy": # FIXME: is there a better way we can detect this? @@ -593,7 +600,7 @@ def run_xsandbox(command, opt): # first, start X if opt.xserver.lower() == "xephyr": - x = SandboxXephyr(command[0], opt.geometry, xauth=opt.xauthority) + x = SandboxXephyr(command[0], geometry=opt.xephyr_geometry, xauth=opt.xauthority) elif opt.xserver.lower() == "xpra3d": x = SandboxXpra(command[0], geometry=None, driver="xdummy", xauth=opt.xauthority) else: From cb3d73424b25aa16505f49d1290b2c943f7eaa5d Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 16:16:04 -0500 Subject: [PATCH 31/46] utils/apparmor/sandbox.py: add --read-path=x.xauth to opt --- utils/apparmor/sandbox.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index c8fd232eb..cbe135f51 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -619,6 +619,10 @@ def run_xsandbox(command, opt): except Exception as e: error(e) + if not opt.read_path: + opt.read_path = [] + opt.read_path.append(x.xauth) + # aa-exec try: rc, report = aa_exec(command, opt, x.new_environ) From 72995c5bcbde3e4ed2d798326a30209875d78cd6 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 16:43:20 -0500 Subject: [PATCH 32/46] utils/easyprof/templates/sandbox-x: add explicit deny rule to deny @{HOME}/.Xauthority utils/apparmor/sandbox.py: verify the above rule is any any dynamic templates that use -X utils/aa-sandbox.pod: update man page to warn about /.Xauthority access --- utils/aa-sandbox.pod | 19 ++++++++++++++++--- utils/apparmor/sandbox.py | 22 ++++++++++++++++++++-- utils/easyprof/templates/sandbox-x | 4 +++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index 4c4339a5e..ec90128ee 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -81,16 +81,29 @@ This does not require privileged access. Run the sandboxed application in an isolated X server. +=item --with-xauthority=XAUTHORITY + +Specify an Xauthority file to use rather than a dynamically generated one. This +is particularly useful in combination with --profile. This option must be used +with care to not allow too much access to the sandboxed application. In +particular, the profile specified with --profile must add a rule to deny access +to ~/.Xauthority for X sandboxing to be effective. Eg: + +=over + +audit deny @{HOME}/.Xauthority mrwlk, + +=back + =item --with-xserver=XSERVER Choose the nested XSERVER to use. Supported servers are: B (the default), B and B. xpra uses the Xvfb(1) virtual framebuffer X server while xpra3d uses the Xorg(1) server with the Xdummy (dummy_drv.so) driver. -=item -g GEOMETRY, --with-geometry=GEOMETRY +=item --with-xephyr-geometry=GEOMETRY -The starting geometry to use. Currently only supported with the B -server. +The starting geometry for the Xephyr(1) server to use. =back diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index cbe135f51..283bcc20b 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -97,7 +97,7 @@ def set_environ(env): msg("Using: %s=%s" % (k, env[k])) os.environ[k] = env[k] -def aa_exec(command, opt, environ={}): +def aa_exec(command, opt, environ={}, verify_rules=[]): '''Execute binary under specified policy''' if opt.profile != None: policy_name = opt.profile @@ -131,6 +131,21 @@ def aa_exec(command, opt, environ={}): if rc != 0: raise AppArmorException("Could not load policy") + rc, report = cmd(['sudo', 'apparmor_parser', '-p', tmp.name]) + if rc != 0: + raise AppArmorException("Could not dump policy") + + # Make sure the dynamic profile has the appropriate line for X + for r in verify_rules: + found = False + for line in report.splitlines(): + line = line.strip() + if r == line: + found = True + break + if not found: + raise AppArmorException("Could not find required rule: %s" % r) + set_environ(environ) args = ['aa-exec', '-p', policy_name, '--'] + command rc, report = cmd(args) @@ -623,9 +638,12 @@ def run_xsandbox(command, opt): opt.read_path = [] opt.read_path.append(x.xauth) + # Only used with dynamic profiles + required_rules = ['audit deny @{HOME}/.Xauthority mrwlk,'] + # aa-exec try: - rc, report = aa_exec(command, opt, x.new_environ) + rc, report = aa_exec(command, opt, x.new_environ, required_rules) except Exception as e: x.cleanup() raise diff --git a/utils/easyprof/templates/sandbox-x b/utils/easyprof/templates/sandbox-x index 63b381bf3..077cb6049 100644 --- a/utils/easyprof/templates/sandbox-x +++ b/utils/easyprof/templates/sandbox-x @@ -15,10 +15,12 @@ ###BINARY### { #include - #include #include #include + #include + audit deny @{HOME}/.Xauthority mrwlk, + /etc/passwd r, / r, From 981188e17a84b69db5909bdc0782b78f82c35a71 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 17:18:21 -0500 Subject: [PATCH 33/46] utils/apparmor/sandbox.py: use a 3840x2560 server size to reduce memory usage --- utils/apparmor/sandbox.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 283bcc20b..a37cbf7a0 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -411,7 +411,9 @@ Section "Device" Driver "dummy" # In kByte #VideoRam 4096000 - VideoRam 256000 + #VideoRam 256000 + # This should be good for 3840*2560*32bpp: http://winswitch.org/trac/ticket/140 + VideoRam 64000 EndSection ##Xdummy:## @@ -500,7 +502,9 @@ Section "Screen" Modes "32000x32000" "16384x8192" "8192x4096" "5120x3200" "3840x2880" "3840x2560" "3840x2048" "2048x2048" "2560x1600" "1920x1440" "1920x1200" "1920x1080" "1600x1200" "1680x1050" "1600x900" "1400x1050" "1440x900" "1280x1024" "1366x768" "1280x800" "1024x768" "1024x600" "800x600" "320x200" #Virtual 32000 32000 #Virtual 16384 8192 - Virtual 8192 4096 + #Virtual 8192 4096 + # http://winswitch.org/trac/ticket/140 + Virtual 3840 2560 EndSubSection EndSection From 091bcd72fde25d0c323ff091688da5419c9162f3 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 27 Aug 2012 20:13:41 -0500 Subject: [PATCH 34/46] adjust title to include the display --- utils/apparmor/sandbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index a37cbf7a0..d53a21171 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -310,7 +310,7 @@ class SandboxXephyr(SandboxXserver): '-br', # black background '-reset', # reset after last client exists '-terminate', # terminate at server reset - '-title', self.title, + '-title', "(%s) %s" % (self.display, self.title), ] + x_exts + x_extra_args args = ['/usr/bin/Xephyr'] + x_args + [self.display] @@ -598,7 +598,7 @@ EndSection listener_attach = os.fork() if listener_attach == 0: args = ['/usr/bin/xpra', 'attach', self.display, - '--title=%s' % self.title, + '--title=(%s) %s' % (self.display, self.title), #'--no-mmap', # for security? '--no-tray', '--no-clipboard', From c92491621b1ce08976d97856413d5c9607e173c3 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 28 Aug 2012 07:41:23 -0500 Subject: [PATCH 35/46] utils/apparmor/sandbox.py: - add --with-clipboard for use with xpra - check for incompatible options --- utils/apparmor/sandbox.py | 44 ++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index d53a21171..c23cd5c9a 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -51,6 +51,11 @@ def parse_args(args=None, parser=None): dest='xserver', default='xpra', help='Nested X server to use: xpra (default), xpra3d, xephyr') + parser.add_option('--with-clipboard', + dest='with_clipboard', + default=False, + help='Allow clipboard access', + action='store_true') parser.add_option('--with-xauthority', dest='xauthority', default=None, @@ -62,7 +67,7 @@ def parse_args(args=None, parser=None): action='store_true') parser.add_option('--with-xephyr-geometry', dest='xephyr_geometry', - default='640x480', + default=None, help='Geometry for Xephyr window') parser.add_option('--profile', dest='profile', @@ -77,6 +82,13 @@ def parse_args(args=None, parser=None): if my_opt.withx and my_opt.xserver.lower() not in valid_xservers: error("Invalid server '%s'. Use one of: %s" % (my_opt.xserver, \ ", ".join(valid_xservers))) + + if my_opt.withx: + if my_opt.xephyr_geometry and my_opt.xserver.lower() != "xephyr": + error("Invalid option --with-xephyr-geometry with '%s'" % my_opt.xserver) + elif my_opt.with_clipboard and my_opt.xserver.lower() == "xephyr": + error("Clipboard not supported with '%s'" % my_opt.xserver) + if my_opt.template == "default": if my_opt.withx: my_opt.template = "sandbox-x" @@ -158,11 +170,15 @@ def run_sandbox(command, opt): return rc, report class SandboxXserver(): - def __init__(self, title, geometry=None, driver=None, xauth=None): + def __init__(self, title, geometry=None, + driver=None, + xauth=None, + clipboard=False): self.geometry = geometry self.title = title self.pids = [] self.driver = driver + self.clipboard = clipboard self.tempfiles = [] self.timeout = 5 # used by xauth and for server starts @@ -305,6 +321,8 @@ class SandboxXephyr(SandboxXserver): '-nodri', # more secure? ] + if not self.geometry: + self.geometry = "640x480" x_args = ['-nolisten', 'tcp', '-screen', self.geometry, '-br', # black background @@ -565,8 +583,9 @@ EndSection x_args = ['--no-daemon', #'--no-mmap', # for security? - '--no-clipboard', '--no-pulseaudio'] + if not self.clipboard: + x_args.append('--no-clipboard') if xvfb_args != '': x_args.append(" ".join(xvfb_args)) @@ -598,11 +617,14 @@ EndSection listener_attach = os.fork() if listener_attach == 0: args = ['/usr/bin/xpra', 'attach', self.display, - '--title=(%s) %s' % (self.display, self.title), + '--title=(%s) %s' % (self.display, + self.title), #'--no-mmap', # for security? '--no-tray', - '--no-clipboard', '--no-pulseaudio'] + if not self.clipboard: + args.append('--no-clipboard') + debug(" ".join(args)) sys.stderr.flush() os.execv(args[0], args) @@ -619,11 +641,17 @@ def run_xsandbox(command, opt): # first, start X if opt.xserver.lower() == "xephyr": - x = SandboxXephyr(command[0], geometry=opt.xephyr_geometry, xauth=opt.xauthority) + x = SandboxXephyr(command[0], geometry=opt.xephyr_geometry, + xauth=opt.xauthority) elif opt.xserver.lower() == "xpra3d": - x = SandboxXpra(command[0], geometry=None, driver="xdummy", xauth=opt.xauthority) + x = SandboxXpra(command[0], geometry=None, + driver="xdummy", + xauth=opt.xauthority, + clipboard=opt.with_clipboard) else: - x = SandboxXpra(command[0], geometry=None, xauth=opt.xauthority) + x = SandboxXpra(command[0], geometry=None, + xauth=opt.xauthority, + clipboard=opt.with_clipboard) x.verify_host_setup() From f2050ec13a2b2246370d612f36f158730bede39e Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 28 Aug 2012 07:44:49 -0500 Subject: [PATCH 36/46] utils/aa-sandbox.pod: document --with-clipboard --- utils/aa-sandbox.pod | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index ec90128ee..535ba6611 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -101,6 +101,10 @@ Choose the nested XSERVER to use. Supported servers are: B (the default), B and B. xpra uses the Xvfb(1) virtual framebuffer X server while xpra3d uses the Xorg(1) server with the Xdummy (dummy_drv.so) driver. +=item --with-clipboard + +Allow access to the clipboard when using B or B. + =item --with-xephyr-geometry=GEOMETRY The starting geometry for the Xephyr(1) server to use. From 06cc33166d0dccff9ffd8a634d5f52c8731a25e6 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 28 Aug 2012 08:01:15 -0500 Subject: [PATCH 37/46] utils/aa-sandbox.pod: document limitations --- utils/aa-sandbox.pod | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index 535ba6611..4dd60ad98 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -169,6 +169,30 @@ Xsession(5) script of the form: After adding the above, it is recommended you remove the existing ~/.Xauthority file, then restart your session. +=head1 LIMITATIONS + +While B may be useful in certain situations, there are a number +of limitations: + +=over + +As mentioned, the quality of the template or the specified profile directly +affects the application's confinement. + +DBus system access is all or nothing and DBus session access is unconditionally +allowed. + +No environment filtering is performed. + +X server usage has not been fully audited (though simple attacks are believed +to be protected against when the system is properly setup). + +Using a nested X server for each application is expensive. + +Surely more... + +=back + =head1 BUGS If you find any bugs, please report them to Launchpad at @@ -176,7 +200,7 @@ L. =head1 SEE ALSO -apparmor(7) apparmor.d(5) xpra(1) Xvfb(1) Xorg(1) Xephyr(1) aa-easyprof(8) -Xecurity(7) +apparmor(7) apparmor.d(5) aa-easyprof(8) Xorg(1) Xecurity(7) xpra(1) Xvfb(1) +Xephyr(1) =cut From 0cd5965fcc865f17a1fe90ac50c4b126ba3d87ff Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 28 Aug 2012 08:09:46 -0500 Subject: [PATCH 38/46] utils/aa-sandbox.pod: - clean up LIMITATIONS a bit - mention lack of cursor support --- utils/aa-sandbox.pod | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index 4dd60ad98..a8927ade4 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -169,10 +169,10 @@ Xsession(5) script of the form: After adding the above, it is recommended you remove the existing ~/.Xauthority file, then restart your session. -=head1 LIMITATIONS +=head1 KNOWN LIMITATIONS While B may be useful in certain situations, there are a number -of limitations: +of limitations regarding both confinement and usability: =over @@ -185,11 +185,12 @@ allowed. No environment filtering is performed. X server usage has not been fully audited (though simple attacks are believed -to be protected against when the system is properly setup). +to be protected against when the system is properly setup. See B, +above). Using a nested X server for each application is expensive. -Surely more... +Only the old X cursor is available with B and B. =back From 7eeaa74dd9d96a62cd7d88d25dec3aaa4b86a375 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 28 Aug 2012 08:39:39 -0500 Subject: [PATCH 39/46] utils/apparmor/sandbox.py: fix error warning --- utils/apparmor/sandbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index c23cd5c9a..57d62addd 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -274,7 +274,7 @@ class SandboxXserver(): raise AppArmorException("Access control currently disabled. Please enable with 'xhost -'") username = pwd.getpwuid(os.geteuid())[0] if ':localuser:%s' % username in report: - raise AppArmorException("Access control allows '%s' full access. Please see 'man aa-sandbox' for details") + raise AppArmorException("Access control allows '%s' full access. Please see 'man aa-sandbox' for details" % username) def start(self): '''Start a nested X server (need to override)''' From 08d91ef714168ffb7ed108554f21b1753fc33278 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 28 Aug 2012 11:56:18 -0500 Subject: [PATCH 40/46] utils/apparmor/sandbox.py: update title again --- utils/apparmor/sandbox.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 57d62addd..8b65d9667 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -256,6 +256,9 @@ class SandboxXserver(): return display, xauth + def generate_title(self): + return "(Sandbox%s) %s" % (self.display, self.title) + def verify_host_setup(self): '''Make sure we have everything we need''' old_lang = None @@ -328,7 +331,7 @@ class SandboxXephyr(SandboxXserver): '-br', # black background '-reset', # reset after last client exists '-terminate', # terminate at server reset - '-title', "(%s) %s" % (self.display, self.title), + '-title', self.generate_title(), ] + x_exts + x_extra_args args = ['/usr/bin/Xephyr'] + x_args + [self.display] @@ -617,8 +620,7 @@ EndSection listener_attach = os.fork() if listener_attach == 0: args = ['/usr/bin/xpra', 'attach', self.display, - '--title=(%s) %s' % (self.display, - self.title), + '--title=%s' % self.generate_title(), #'--no-mmap', # for security? '--no-tray', '--no-pulseaudio'] From a324724cf32f68b13c3a023ba75124eb05092c62 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 29 Aug 2012 08:43:48 -0500 Subject: [PATCH 41/46] utils/apparmor/sandbox.py: set QT_X11_NO_NATIVE_MENUBAR=1 utils/aa-sandbox.pod: update KNOWN LIMITATIONS for global menu --- utils/aa-sandbox.pod | 4 ++++ utils/apparmor/sandbox.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index a8927ade4..d82d590c1 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -192,6 +192,10 @@ Using a nested X server for each application is expensive. Only the old X cursor is available with B and B. +The Ubuntu global menu is not currently supported. Gtk and Qt applications +should display the non-global menu by default, but applications like Firefox +and Thunderbird should be adjusted to disable the global menu. + =back =head1 BUGS diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 8b65d9667..b973b0169 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -187,6 +187,7 @@ class SandboxXserver(): self.old_environ['DISPLAY'] = os.environ['DISPLAY'] self.old_environ['XAUTHORITY'] = os.environ['XAUTHORITY'] self.old_environ['UBUNTU_MENUPROXY'] = os.environ['UBUNTU_MENUPROXY'] + self.old_environ['QT_X11_NO_NATIVE_MENUBAR'] = os.environ['QT_X11_NO_NATIVE_MENUBAR'] # prepare the new environment self.display, self.xauth = self.find_free_x_display() @@ -200,6 +201,7 @@ class SandboxXserver(): self.new_environ['XAUTHORITY'] = self.xauth # Disable the global menu for now self.new_environ["UBUNTU_MENUPROXY"] = "" + self.new_environ["QT_X11_NO_NATIVE_MENUBAR"] = "1" def cleanup(self): '''Cleanup our forked pids, reset the environment, etc''' From 457d19beaf7bcf1dc7c09da90843b169ea44d1b3 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 29 Aug 2012 08:49:15 -0500 Subject: [PATCH 42/46] utils/aa-sandbox.pod: note on hotplugging monitors --- utils/aa-sandbox.pod | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/aa-sandbox.pod b/utils/aa-sandbox.pod index d82d590c1..5c921cc37 100644 --- a/utils/aa-sandbox.pod +++ b/utils/aa-sandbox.pod @@ -196,6 +196,9 @@ The Ubuntu global menu is not currently supported. Gtk and Qt applications should display the non-global menu by default, but applications like Firefox and Thunderbird should be adjusted to disable the global menu. +Xpra does not handle screen resizing when hotplugging monitors gracefully. +Restarting the sandbox will resolve the issue. + =back =head1 BUGS From aa6407d07b2798ad75808f2e55dff0d6cc0f63ad Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 29 Aug 2012 08:56:06 -0500 Subject: [PATCH 43/46] utils/apparmor/sandbox.py: only save environment variables that exist --- utils/apparmor/sandbox.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index b973b0169..88b8b2f05 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -184,10 +184,10 @@ class SandboxXserver(): # preserve our environment self.old_environ = dict() - self.old_environ['DISPLAY'] = os.environ['DISPLAY'] - self.old_environ['XAUTHORITY'] = os.environ['XAUTHORITY'] - self.old_environ['UBUNTU_MENUPROXY'] = os.environ['UBUNTU_MENUPROXY'] - self.old_environ['QT_X11_NO_NATIVE_MENUBAR'] = os.environ['QT_X11_NO_NATIVE_MENUBAR'] + for env in ['DISPLAY', 'XAUTHORITY', 'UBUNTU_MENUPROXY', + 'QT_X11_NO_NATIVE_MENUBAR']: + if env in os.environ: + self.old_environ[env] = os.environ[env] # prepare the new environment self.display, self.xauth = self.find_free_x_display() From a8f5562bdec69c6c14b030e914dea753c596fe4a Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 3 Sep 2012 14:29:05 -0500 Subject: [PATCH 44/46] utils/apparmor/sandbox.py: adjust LIBOVERLAY_SCROLLBAR too, since the overlay scrollbar doesn't track properly --- utils/apparmor/sandbox.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 88b8b2f05..a05d7c47e 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -185,7 +185,7 @@ class SandboxXserver(): # preserve our environment self.old_environ = dict() for env in ['DISPLAY', 'XAUTHORITY', 'UBUNTU_MENUPROXY', - 'QT_X11_NO_NATIVE_MENUBAR']: + 'QT_X11_NO_NATIVE_MENUBAR', 'LIBOVERLAY_SCROLLBAR']: if env in os.environ: self.old_environ[env] = os.environ[env] @@ -202,6 +202,8 @@ class SandboxXserver(): # Disable the global menu for now self.new_environ["UBUNTU_MENUPROXY"] = "" self.new_environ["QT_X11_NO_NATIVE_MENUBAR"] = "1" + # Disable the overlay scrollbar for now-- they don't track correctly + self.new_environ["LIBOVERLAY_SCROLLBAR"] = "0" def cleanup(self): '''Cleanup our forked pids, reset the environment, etc''' From 7baf9a4d36686eafb1d8b1885c7e55724f1ed16c Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Tue, 4 Dec 2012 10:15:34 -0600 Subject: [PATCH 45/46] utils/apparmor/sandbox.py: sigh, add another sleep to avoid a race in firefox --- utils/apparmor/sandbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index a05d7c47e..ba5919822 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -8,7 +8,7 @@ # # ------------------------------------------------------------------ -from apparmor.common import AppArmorException, debug, error, warn, msg, cmd +from apparmor.common import AppArmorException, debug, error, msg, cmd import apparmor.easyprof import optparse import os @@ -605,7 +605,7 @@ EndSection self.pids.append(listener_x) started = False - time.sleep(0.5) + time.sleep(2) # FIXME: detect if running for i in range(self.timeout): # 5 seconds to start rc, out = cmd(['xpra', 'list']) if 'LIVE session at %s' % self.display in out: From e91ac70739446e17533ceb18b94a7b22e3c73aaa Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Mon, 14 Jan 2013 08:55:53 -0600 Subject: [PATCH 46/46] revert r2080, it doesn't actually help anything --- utils/apparmor/sandbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index ba5919822..4171ef4f0 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -605,7 +605,7 @@ EndSection self.pids.append(listener_x) started = False - time.sleep(2) # FIXME: detect if running + time.sleep(0.5) # FIXME: detect if running for i in range(self.timeout): # 5 seconds to start rc, out = cmd(['xpra', 'list']) if 'LIVE session at %s' % self.display in out: