mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-22 01:57:43 +00:00
Committing per IRC discussions. Does not update the Makefile to install it yet.
= 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). When using '-X', it: - adds an explicit deny rule for ~/.Xauthority - generates a dynamic Xauthority file for the session in ~/.Xauthority-sandbox<DISPLAYNUMBER> - adds an allow rule for ~/.Xauthority-sandbox<DISPLAYNUMBER> - adds checks for xhost being properly setup - honors the --with-xauthority option which can be used with --profile With the above, the :0.0 display should no longer be accessible. Eg: $ ./aa-sandbox -t ~/sandbox-xterm -X /usr/bin/xterm $ XAUTHORITY=~/.Xauthority DISPLAY=:0.0 xinput No protocol specified Unable to connect to X server This requires a specifically configured xauth/xhost setup, which is less common on modern distributions. The man page details how to get this setup. = 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 * make pulseaudio in xpra opt-in (currently it is off) * take advantage of upstream's 3D patches when they stabilize * 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
This commit is contained in:
commit
3c41028cd5
@ -165,3 +165,4 @@ tests/regression/apparmor/unix_fd_server
|
||||
tests/regression/apparmor/unlink
|
||||
tests/regression/apparmor/xattrs
|
||||
tests/regression/apparmor/coredump
|
||||
./utils/apparmor/__pycache__
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
import apparmor.easyprof
|
||||
from apparmor.easyprof import AppArmorException, error
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
37
utils/aa-sandbox
Executable file
37
utils/aa-sandbox
Executable file
@ -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)
|
214
utils/aa-sandbox.pod
Normal file
214
utils/aa-sandbox.pod
Normal file
@ -0,0 +1,214 @@
|
||||
# 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 restrictive 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-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<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 --with-clipboard
|
||||
|
||||
Allow access to the clipboard when using B<xpra> or B<xpra3d>.
|
||||
|
||||
=item --with-xephyr-geometry=GEOMETRY
|
||||
|
||||
The starting geometry for the Xephyr(1) server to use.
|
||||
|
||||
=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 NOTES
|
||||
|
||||
B<aa-sandbox> 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 KNOWN LIMITATIONS
|
||||
|
||||
While B<aa-sandbox> may be useful in certain situations, there are a number
|
||||
of limitations regarding both confinement and usability:
|
||||
|
||||
=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. See B<NOTES>,
|
||||
above).
|
||||
|
||||
Using a nested X server for each application is expensive.
|
||||
|
||||
Only the old X cursor is available with B<xpra> and B<xpra3d>.
|
||||
|
||||
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
|
||||
|
||||
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) aa-easyprof(8) Xorg(1) Xecurity(7) xpra(1) Xvfb(1)
|
||||
Xephyr(1)
|
||||
|
||||
=cut
|
95
utils/apparmor/common.py
Normal file
95
utils/apparmor/common.py
Normal file
@ -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=output)
|
||||
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]
|
||||
|
@ -202,9 +202,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):
|
||||
@ -378,7 +377,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:
|
||||
@ -456,51 +458,8 @@ def print_files(files):
|
||||
with open(i) as f:
|
||||
sys.stdout.write(f.read()+"\n")
|
||||
|
||||
def parse_args(args=None):
|
||||
'''Parse arguments'''
|
||||
global DEBUGGING
|
||||
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option("-c", "--config-file",
|
||||
dest="conffile",
|
||||
help="Use alternate configuration file",
|
||||
metavar="FILE")
|
||||
parser.add_option("-d", "--debug",
|
||||
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)
|
||||
def add_parser_policy_args(parser):
|
||||
'''Add parser arguments'''
|
||||
parser.add_option("-a", "--abstractions",
|
||||
dest="abstractions",
|
||||
help="Comma-separated list of abstractions",
|
||||
@ -515,6 +474,54 @@ def parse_args(args=None):
|
||||
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
|
||||
|
||||
if parser == None:
|
||||
parser = optparse.OptionParser()
|
||||
|
||||
parser.add_option("-c", "--config-file",
|
||||
dest="conffile",
|
||||
help="Use alternate configuration file",
|
||||
metavar="FILE")
|
||||
parser.add_option("-d", "--debug",
|
||||
help="Show debugging output",
|
||||
action='store_true',
|
||||
default=False)
|
||||
parser.add_option("--list-templates",
|
||||
help="List available templates",
|
||||
action='store_true',
|
||||
default=False)
|
||||
parser.add_option("--show-template",
|
||||
help="Show specified template",
|
||||
action='store_true',
|
||||
default=False)
|
||||
parser.add_option("--list-policy-groups",
|
||||
help="List available policy groups",
|
||||
action='store_true',
|
||||
default=False)
|
||||
parser.add_option("--show-policy-group",
|
||||
help="Show specified policy groups",
|
||||
action='store_true',
|
||||
default=False)
|
||||
parser.add_option("-n", "--name",
|
||||
dest="name",
|
||||
help="Name of policy",
|
||||
@ -537,6 +544,9 @@ def parse_args(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
|
||||
|
691
utils/apparmor/sandbox.py
Normal file
691
utils/apparmor/sandbox.py
Normal file
@ -0,0 +1,691 @@
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# 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, msg, cmd
|
||||
import apparmor.easyprof
|
||||
import optparse
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import signal
|
||||
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('--with-clipboard',
|
||||
dest='with_clipboard',
|
||||
default=False,
|
||||
help='Allow clipboard access',
|
||||
action='store_true')
|
||||
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('--with-xephyr-geometry',
|
||||
dest='xephyr_geometry',
|
||||
default=None,
|
||||
help='Geometry for Xephyr window')
|
||||
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:
|
||||
apparmor.common.DEBUGGING = True
|
||||
|
||||
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.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"
|
||||
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.geteuid())[0],
|
||||
re.sub(r'/', '_', binary))
|
||||
|
||||
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={}, verify_rules=[]):
|
||||
'''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 = 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:
|
||||
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")
|
||||
|
||||
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)
|
||||
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, 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
|
||||
|
||||
# preserve our environment
|
||||
self.old_environ = dict()
|
||||
for env in ['DISPLAY', 'XAUTHORITY', 'UBUNTU_MENUPROXY',
|
||||
'QT_X11_NO_NATIVE_MENUBAR', 'LIBOVERLAY_SCROLLBAR']:
|
||||
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()
|
||||
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
|
||||
# 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'''
|
||||
self.pids.reverse()
|
||||
debug(self.pids)
|
||||
for pid in self.pids:
|
||||
# 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):
|
||||
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'''
|
||||
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
|
||||
# sandboxed applications
|
||||
tmp = ":%d" % i
|
||||
os.environ["DISPLAY"] = tmp
|
||||
rc, report = cmd(['xset', '-q'])
|
||||
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")
|
||||
|
||||
# Use dedicated .Xauthority file
|
||||
xauth = os.path.join(os.path.expanduser('~'), \
|
||||
'.Xauthority-sandbox%s' % display.split(':')[1])
|
||||
|
||||
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
|
||||
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" % username)
|
||||
|
||||
def start(self):
|
||||
'''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):
|
||||
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)
|
||||
|
||||
'''Run any setup code'''
|
||||
SandboxXserver.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?
|
||||
]
|
||||
|
||||
if not self.geometry:
|
||||
self.geometry = "640x480"
|
||||
x_args = ['-nolisten', 'tcp',
|
||||
'-screen', self.geometry,
|
||||
'-br', # black background
|
||||
'-reset', # reset after last client exists
|
||||
'-terminate', # terminate at server reset
|
||||
'-title', self.generate_title(),
|
||||
] + x_exts + x_extra_args
|
||||
|
||||
args = ['/usr/bin/Xephyr'] + x_args + [self.display]
|
||||
debug(" ".join(args))
|
||||
os.execv(args[0], 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
|
||||
set_environ(self.new_environ)
|
||||
|
||||
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
|
||||
|
||||
|
||||
class SandboxXpra(SandboxXserver):
|
||||
def cleanup(self):
|
||||
sys.stderr.flush()
|
||||
listener = os.fork()
|
||||
if listener == 0:
|
||||
args = ['/usr/bin/xpra', 'stop', self.display]
|
||||
debug(" ".join(args))
|
||||
os.execv(args[0], args)
|
||||
sys.exit(0)
|
||||
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):
|
||||
'''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' % 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:
|
||||
# http://xpra.org/Xdummy.html
|
||||
conf = '''# /usr/share/doc/xpra/examples/dummy.xorg.conf.gz
|
||||
# http://xpra.org/Xdummy.html
|
||||
##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"
|
||||
# In kByte
|
||||
#VideoRam 4096000
|
||||
#VideoRam 256000
|
||||
# This should be good for 3840*2560*32bpp: http://winswitch.org/trac/ticket/140
|
||||
VideoRam 64000
|
||||
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
|
||||
# http://winswitch.org/trac/ticket/140
|
||||
Virtual 3840 2560
|
||||
EndSubSection
|
||||
EndSection
|
||||
|
||||
Section "ServerLayout"
|
||||
Identifier "dummy_layout"
|
||||
Screen "screen0"
|
||||
InputDevice "NoMouse"
|
||||
InputDevice "NoKeyboard"
|
||||
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' % self.new_environ['XAUTHORITY'])
|
||||
xvfb_args.append('-config %s' % xorg_conf)
|
||||
extensions = ['Composite', 'GLX', 'RANDR', 'RENDER', 'SECURITY']
|
||||
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):
|
||||
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?
|
||||
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)
|
||||
|
||||
'''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'])
|
||||
|
||||
x_args = ['--no-daemon',
|
||||
#'--no-mmap', # for security?
|
||||
'--no-pulseaudio']
|
||||
if not self.clipboard:
|
||||
x_args.append('--no-clipboard')
|
||||
|
||||
if xvfb_args != '':
|
||||
x_args.append(" ".join(xvfb_args))
|
||||
|
||||
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)
|
||||
|
||||
started = False
|
||||
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:
|
||||
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.generate_title(),
|
||||
#'--no-mmap', # for security?
|
||||
'--no-tray',
|
||||
'--no-pulseaudio']
|
||||
if not self.clipboard:
|
||||
args.append('--no-clipboard')
|
||||
|
||||
debug(" ".join(args))
|
||||
sys.stderr.flush()
|
||||
os.execv(args[0], args)
|
||||
sys.exit(0)
|
||||
|
||||
self.pids.append(listener_attach)
|
||||
|
||||
msg("TODO: filter '~/.xpra/run-xpra'")
|
||||
|
||||
|
||||
def run_xsandbox(command, opt):
|
||||
'''Run X application in a sandbox'''
|
||||
old_cwd = os.getcwd()
|
||||
|
||||
# first, start X
|
||||
if opt.xserver.lower() == "xephyr":
|
||||
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,
|
||||
clipboard=opt.with_clipboard)
|
||||
else:
|
||||
x = SandboxXpra(command[0], geometry=None,
|
||||
xauth=opt.xauthority,
|
||||
clipboard=opt.with_clipboard)
|
||||
|
||||
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)
|
||||
|
||||
if not opt.read_path:
|
||||
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, required_rules)
|
||||
except Exception as e:
|
||||
x.cleanup()
|
||||
raise
|
||||
x.cleanup()
|
||||
os.chdir(old_cwd)
|
||||
|
||||
return rc, report
|
29
utils/easyprof/templates/sandbox
Normal file
29
utils/easyprof/templates/sandbox
Normal file
@ -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###
|
||||
}
|
46
utils/easyprof/templates/sandbox-x
Normal file
46
utils/easyprof/templates/sandbox-x
Normal file
@ -0,0 +1,46 @@
|
||||
#
|
||||
# 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/gnome>
|
||||
#include <abstractions/kde>
|
||||
|
||||
#include <abstractions/X>
|
||||
audit deny @{HOME}/.Xauthority mrwlk,
|
||||
|
||||
/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###
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user