I've finally gotten aa-sandbox working in a manner that I am ready to
share. The code is located at (I've attached a patch as well):
https://code.launchpad.net/~jdstrand/+junk/apparmor-sandbox
The code is quite new and review/comments are appreciated. I have not
updated the Makefiles yet, and there is a man page. aa-sandbox works
with both Python2 and Python3.
= How it works =
There are basically two modes:
1. using an existing profile with --profile
2. dynamically generating a profile
For '1', aa-sandbox is just a wrapper around aa-exec.
For '2', aa-sandbox leverages easyprof and allows you to specify policy
in a limited way on the command line. It then loads the policy into the
kernel as a profile (ie, 'profile <foo> { ... }') so it doesn't get in
the way of existing profiles. It currently calls apparmor_parser via
sudo or pkexec. Once the profile is loaded, aa-exec the application
under the profile.
When -X is specified, the application is launched inside its own X
server using either xpra (the default, which uses Xvfb), xephyr and
xpra3d (xpra, but using Xorg with the xdummy[1] driver for now[2].
xpra3d doesn't currently perform well, but works ok with newer Gnome
applications that now require GLX).
= Trying it out =
Apply the patch, then:
$ cd ./utils
# cli
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates --read-path=/proc/
/usr/bin/uptime
# 2d only
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates -X /usr/bin/xeyes
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates -X /usr/bin/gedit
# 2d alternate (xephyr)
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates -X
--with-xserver=xephyr /usr/bin/xeyes
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates -X
--with-xserver=xephyr /usr/bin/gedit
# 3d
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates -X
--with-xserver=xpra3d /usr/bin/xeyes
$ ./aa-sandbox --templates-dir=`pwd`/easyprof/templates -X
--with-xserver=xpra3d /usr/bin/glxgears
# With an existing profile:
$ ./aa-sandbox --profile=/usr/bin/evolution -X --with-xserver=xpra3d
/usr/bin/evolution
= The Patch =
The patch itself is pretty self contained:
utils/aa-easyprof:
- adjusted to import optparse
utils/easyprof/templates/sandbox*
- add two new templates to easyprof
utils/apparmor/easyprof.py:
- use 'profile <foo>' if '<foo>' is not an absolute path
- adjust parser handling so we can reuse it
utils/aa-sandbox:
- small script to drive utils/apparmor/sandbox.py
utils/apparmor/common.py:
- the start of our python library. aa-easyprof would eventually use
this (along with the various rewrites), but for now, only the
sandboxing uses it.
utils/apparmor/sandbox.py:
- the sandboxing code itself. Of particular note is the use of classing
to support different X servers
utils/aa-sandbox.pod:
- the corresponding man page
= Improvements =
* don't use sudo
* xpra has issues with the mouse position in longer documents (eg this
email in evolution)
* make clipboard in xpra opt-in (currently it is off)
* make pulseaudio in xpra opt-in (currently it is off)
* take advantage of upstream's 3D patches when they stabilize
* audit our usage of various X servers
* supporting --geometry with xpra
* investigate how applications can work with the Unity global menu
* surely lots more :)
[1]http://xpra.org/Xdummy.html
[2]http://xpra.org/trac/ticket/147
--
Jamie Strandboge | http://www.canonical.com
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/aa-easyprof apparmor-sandbox/utils/aa-easyprof
--- apparmor-trunk/utils/aa-easyprof 2012-06-29 07:43:23.000000000 -0500
+++ apparmor-sandbox/utils/aa-easyprof 2012-08-23 15:53:08.000000000 -0500
@@ -11,6 +11,7 @@
import apparmor.easyprof
from apparmor.easyprof import AppArmorException, error
+import optparse
import os
import sys
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/aa-sandbox apparmor-sandbox/utils/aa-sandbox
--- apparmor-trunk/utils/aa-sandbox 1969-12-31 18:00:00.000000000 -0600
+++ apparmor-sandbox/utils/aa-sandbox 2012-08-23 20:49:01.000000000 -0500
@@ -0,0 +1,37 @@
+#! /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 optparse
+import os
+import sys
+
+if __name__ == "__main__":
+ argv = 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")
+
+ binary = args[0]
+ if not apparmor.sandbox.check_requirements(binary):
+ sys.exit(1)
+
+ if opt.withx:
+ rc, report = apparmor.sandbox.run_xsandbox(args, opt)
+ else:
+ rc, report = apparmor.sandbox.run_sandbox(args, opt)
+
+ apparmor.common.msg(report)
+ sys.exit(rc)
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/aa-sandbox.pod apparmor-sandbox/utils/aa-sandbox.pod
--- apparmor-trunk/utils/aa-sandbox.pod 1969-12-31 18:00:00.000000000 -0600
+++ apparmor-sandbox/utils/aa-sandbox.pod 2012-08-24 12:15:51.000000000 -0500
@@ -0,0 +1,140 @@
+# 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<aa-sandbox> [option] <path to binary>
+
+=head1 DESCRIPTION
+
+B<aa-sandbox> 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
+with B<aa-genprof> and B<aa-logprof>.
+
+=head1 OPTIONS
+
+B<aa-sandbox> 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. If not specified, uses
+B<sandbox> or B<sandbox-x> when B<-X> is specified. See aa-easyprof(8) for
+details. Privileged access is required to load the dynamically generated
+profile (B<aa-sandbox> will prompt for a password).
+
+=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 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 writes in addition to reads.
+
+=item --profile=PROFILE
+
+Instead of generating a dynamic profile, specify an existing, loaded profile.
+This does not require privileged access.
+
+=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<xpra> (the default),
+B<xpra3d> and B<xephyr>. 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<xephyr>
+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
+
+=back
+
+=head1 BUGS
+
+If you find any bugs, please report them to Launchpad at
+L<https://bugs.launchpad.net/apparmor/+filebug>.
+
+=head1 SEE ALSO
+
+apparmor(7) apparmor.d(5) xpra(1) Xvfb(1) Xorg(1) Xephyr(1) aa-easyprof(8)
+
+=cut
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/apparmor/common.py apparmor-sandbox/utils/apparmor/common.py
--- apparmor-trunk/utils/apparmor/common.py 1969-12-31 18:00:00.000000000 -0600
+++ apparmor-sandbox/utils/apparmor/common.py 2012-08-23 20:49:01.000000000 -0500
@@ -0,0 +1,95 @@
+# ------------------------------------------------------------------
+#
+# 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.
+#
+# ------------------------------------------------------------------
+
+from __future__ import print_function
+import subprocess
+import sys
+
+DEBUGGING = False
+
+#
+# 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("ERROR: %s" % (out), file=sys.stderr)
+ except IOError:
+ pass
+
+ if do_exit:
+ sys.exit(exit_code)
+
+def warn(out):
+ '''Print warning message'''
+ try:
+ print("WARN: %s" % (out), file=sys.stderr)
+ except IOError:
+ pass
+
+def msg(out, output=sys.stdout):
+ '''Print message'''
+ try:
+ print("%s" % (out), file=sys.stdout)
+ except IOError:
+ pass
+
+def debug(out):
+ '''Print debug message'''
+ global DEBUGGING
+ if DEBUGGING:
+ try:
+ print("DEBUG: %s" % (out), file=sys.stderr)
+ 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 as ex:
+ return [127, str(ex)]
+
+ 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 as ex:
+ return [127, str(ex)]
+
+ if sys.version_info[0] >= 3:
+ out = sp2.communicate()[0].decode('ascii', 'ignore')
+ else:
+ out = sp2.communicate()[0]
+
+ return [sp2.returncode, out]
+
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/apparmor/easyprof.py apparmor-sandbox/utils/apparmor/easyprof.py
--- apparmor-trunk/utils/apparmor/easyprof.py 2012-06-29 07:43:23.000000000 -0500
+++ apparmor-sandbox/utils/apparmor/easyprof.py 2012-08-23 15:53:08.000000000 -0500
@@ -202,9 +202,8 @@
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):
@@ -378,7 +377,10 @@
# 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:
@@ -456,11 +458,46 @@
with open(i) as f:
sys.stdout.write(f.read()+"\n")
-def parse_args(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")
+ 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'''
global DEBUGGING
- parser = optparse.OptionParser()
+ if parser == None:
+ parser = optparse.OptionParser()
+
parser.add_option("-c", "--config-file",
dest="conffile",
help="Use alternate configuration file",
@@ -469,52 +506,22 @@
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',
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",
@@ -537,6 +544,9 @@
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
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/apparmor/sandbox.py apparmor-sandbox/utils/apparmor/sandbox.py
--- apparmor-trunk/utils/apparmor/sandbox.py 1969-12-31 18:00:00.000000000 -0600
+++ apparmor-sandbox/utils/apparmor/sandbox.py 2012-08-24 11:54:06.000000000 -0500
@@ -0,0 +1,431 @@
+# ------------------------------------------------------------------
+#
+# 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, warn, msg, cmd
+import apparmor.easyprof
+import optparse
+import os
+import pwd
+import re
+import sys
+import tempfile
+import time
+
+def check_requirements(binary):
+ '''Verify necessary software is installed'''
+ exes = ['xset', # for detecting free X display
+ '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:
+ 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):
+ '''Parse arguments'''
+ if parser == None:
+ 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('--with-xserver',
+ dest='xserver',
+ default='xpra',
+ help='Nested X server to use: xpra (default), xpra3d, xephyr')
+ parser.add_option('-d', '--debug',
+ dest='debug',
+ default=False,
+ help='Show debug messages',
+ action='store_true')
+ parser.add_option('-r', '--with-geometry',
+ dest='geometry',
+ 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:
+ 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:
+ 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'''
+ return "sandbox-%s%s" % (pwd.getpwuid(os.getuid())[0],
+ re.sub(r'/', '_', binary))
+
+def aa_exec(command, opt):
+ '''Execute binary under specified policy'''
+ if opt.profile != None:
+ policy_name = opt.profile
+ else:
+ 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)
+
+ 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 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")
+
+ args = ['aa-exec', '-p', policy_name] + command
+ rc, report = cmd(args)
+ return rc, report
+
+def run_sandbox(command, opt):
+ '''Run application'''
+ # aa-exec
+ rc, report = aa_exec(command, opt)
+ return rc, report
+
+class SandboxXserver():
+ def __init__(self, geometry, title, driver=None):
+ self.geometry = geometry
+ 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)
+ os.environ["UBUNTU_MENUPROXY"] = ""
+
+ 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:
+ 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'''
+
+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:
+ # 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.geometry,
+ '-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))
+ cmd(args)
+ sys.exit(0)
+ self.pids.append(listener_x)
+
+ time.sleep(1) # 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))
+ cmd(args)
+ sys.exit(0)
+
+ 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 _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:
+ # 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)
+ rc, report = cmd(['which', e])
+ 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)
+ 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:
+ # This will clean out any dead sessions
+ cmd(['xpra', 'list'])
+
+ 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))
+ if apparmor.common.DEBUGGING == True:
+ sys.stderr.flush()
+ os.execv(args[0], args)
+ else:
+ cmd(args)
+ sys.exit(0)
+ self.pids.append(listener_x)
+
+ started = False
+ 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()
+ self.cleanup()
+ raise AppArmorException("Could not start xpra (try again with -d)")
+
+ # Next, attach to xpra
+ sys.stdout.flush()
+ os.chdir(os.environ["HOME"])
+ listener_attach = os.fork()
+ if listener_attach == 0:
+ args = ['/usr/bin/xpra', 'attach', self.display,
+ '--title=%s' % self.title,
+ #'--no-mmap', # for security?
+ '--no-tray',
+ '--no-clipboard',
+ '--no-pulseaudio']
+ debug(" ".join(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
+ msg("TODO: --with-geometry not honored in xpra")
+ msg("TODO: filter '~/.xpra/run-xpra'")
+
+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
+ if opt.xserver.lower() == "xephyr":
+ x = SandboxXephyr(opt.geometry, command[0])
+ elif opt.xserver.lower() == "xpra3d":
+ x = SandboxXpra(opt.geometry, command[0], driver="xdummy")
+ else:
+ x = SandboxXpra(opt.geometry, command[0])
+
+ 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)
+ 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
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/easyprof/templates/sandbox apparmor-sandbox/utils/easyprof/templates/sandbox
--- apparmor-trunk/utils/easyprof/templates/sandbox 1969-12-31 18:00:00.000000000 -0600
+++ apparmor-sandbox/utils/easyprof/templates/sandbox 2012-05-10 02:54:28.000000000 -0500
@@ -0,0 +1,29 @@
+#
+# 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 <tunables/global>
+
+###VAR###
+
+###BINARY### {
+ #include <abstractions/base>
+ / r,
+ /**/ r,
+ /usr/** r,
+
+ ###ABSTRACTIONS###
+
+ ###POLICYGROUPS###
+
+ ###READS###
+
+ ###WRITES###
+}
diff -Naur -x .bzr -x .bzrignore apparmor-trunk/utils/easyprof/templates/sandbox-x apparmor-sandbox/utils/easyprof/templates/sandbox-x
--- apparmor-trunk/utils/easyprof/templates/sandbox-x 1969-12-31 18:00:00.000000000 -0600
+++ apparmor-sandbox/utils/easyprof/templates/sandbox-x 2012-05-10 02:54:21.000000000 -0500
@@ -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 <tunables/global>
+
+###VAR###
+
+###BINARY### {
+ #include <abstractions/base>
+ #include <abstractions/X>
+ #include <abstractions/gnome>
+ #include <abstractions/kde>
+
+ /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###
+}
--
AppArmor mailing list
[email protected]
Modify settings or unsubscribe at:
https://lists.ubuntu.com/mailman/listinfo/apparmor