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

Reply via email to