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/unlink
|
||||||
tests/regression/apparmor/xattrs
|
tests/regression/apparmor/xattrs
|
||||||
tests/regression/apparmor/coredump
|
tests/regression/apparmor/coredump
|
||||||
|
./utils/apparmor/__pycache__
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
import apparmor.easyprof
|
import apparmor.easyprof
|
||||||
from apparmor.easyprof import AppArmorException, error
|
from apparmor.easyprof import AppArmorException, error
|
||||||
|
import optparse
|
||||||
import os
|
import os
|
||||||
import sys
|
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:
|
class AppArmorEasyProfile:
|
||||||
'''Easy profile class'''
|
'''Easy profile class'''
|
||||||
def __init__(self, binary, opt):
|
def __init__(self, binary, opt):
|
||||||
self.conffile = "/etc/apparmor/easyprof.conf"
|
opt.ensure_value("conffile", "/etc/apparmor/easyprof.conf")
|
||||||
if opt.conffile:
|
self.conffile = os.path.abspath(opt.conffile)
|
||||||
self.conffile = os.path.abspath(opt.conffile)
|
|
||||||
|
|
||||||
self.dirs = dict()
|
self.dirs = dict()
|
||||||
if os.path.isfile(self.conffile):
|
if os.path.isfile(self.conffile):
|
||||||
@ -378,7 +377,10 @@ class AppArmorEasyProfile:
|
|||||||
|
|
||||||
# Fill-in profile name and binary
|
# Fill-in profile name and binary
|
||||||
policy = re.sub(r'###NAME###', name, policy)
|
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
|
# Fill-in various comment fields
|
||||||
if comment != None:
|
if comment != None:
|
||||||
@ -456,51 +458,8 @@ def print_files(files):
|
|||||||
with open(i) as f:
|
with open(i) as f:
|
||||||
sys.stdout.write(f.read()+"\n")
|
sys.stdout.write(f.read()+"\n")
|
||||||
|
|
||||||
def parse_args(args=None):
|
def add_parser_policy_args(parser):
|
||||||
'''Parse arguments'''
|
'''Add parser 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)
|
|
||||||
parser.add_option("-a", "--abstractions",
|
parser.add_option("-a", "--abstractions",
|
||||||
dest="abstractions",
|
dest="abstractions",
|
||||||
help="Comma-separated list of abstractions",
|
help="Comma-separated list of abstractions",
|
||||||
@ -515,6 +474,54 @@ def parse_args(args=None):
|
|||||||
help="Path allowing owner writes",
|
help="Path allowing owner writes",
|
||||||
metavar="PATH",
|
metavar="PATH",
|
||||||
action="append")
|
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",
|
parser.add_option("-n", "--name",
|
||||||
dest="name",
|
dest="name",
|
||||||
help="Name of policy",
|
help="Name of policy",
|
||||||
@ -537,6 +544,9 @@ def parse_args(args=None):
|
|||||||
metavar="@{VARIABLE}=VALUE",
|
metavar="@{VARIABLE}=VALUE",
|
||||||
action="append")
|
action="append")
|
||||||
|
|
||||||
|
# add policy args now
|
||||||
|
add_parser_policy_args(parser)
|
||||||
|
|
||||||
(my_opt, my_args) = parser.parse_args(args)
|
(my_opt, my_args) = parser.parse_args(args)
|
||||||
if my_opt.debug:
|
if my_opt.debug:
|
||||||
DEBUGGING = True
|
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