2012-05-09 11:33:36 -07:00
|
|
|
# ------------------------------------------------------------------
|
|
|
|
#
|
|
|
|
# Copyright (C) 2011-2012 Canonical Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of version 2 of the GNU General Public
|
|
|
|
# License published by the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
from apparmor.common import AppArmorException, debug, error, cmd
|
2012-05-10 01:17:56 -07:00
|
|
|
import apparmor.easyprof
|
2012-05-09 11:33:36 -07:00
|
|
|
import optparse
|
|
|
|
import os
|
2012-05-10 06:43:52 -07:00
|
|
|
import pwd
|
2012-08-23 17:12:14 -05:00
|
|
|
import re
|
2012-05-09 11:33:36 -07:00
|
|
|
import sys
|
2012-05-10 01:17:56 -07:00
|
|
|
import tempfile
|
2012-05-09 11:33:36 -07:00
|
|
|
import time
|
|
|
|
|
|
|
|
def check_requirements(binary):
|
|
|
|
'''Verify necessary software is installed'''
|
2012-08-23 19:56:18 -05:00
|
|
|
exes = ['xset', # for detecting free X display
|
2012-08-23 16:29:48 -05:00
|
|
|
'aa-easyprof', # for templates
|
2012-08-23 19:56:18 -05:00
|
|
|
'aa-exec', # for changing profile
|
2012-08-23 16:29:48 -05:00
|
|
|
'sudo', # eventually get rid of this
|
|
|
|
binary]
|
2012-08-23 19:56:18 -05:00
|
|
|
|
2012-05-09 11:33:36 -07:00
|
|
|
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
|
2012-08-23 19:56:18 -05:00
|
|
|
|
2012-05-09 11:33:36 -07:00
|
|
|
return True
|
|
|
|
|
2012-05-10 01:17:56 -07:00
|
|
|
def parse_args(args=None, parser=None):
|
2012-05-09 11:33:36 -07:00
|
|
|
'''Parse arguments'''
|
2012-05-10 01:17:56 -07:00
|
|
|
if parser == None:
|
|
|
|
parser = optparse.OptionParser()
|
|
|
|
|
2012-05-09 11:33:36 -07:00
|
|
|
parser.add_option('-X', '--with-x',
|
|
|
|
dest='withx',
|
|
|
|
default=False,
|
|
|
|
help='Run in isolated X server',
|
|
|
|
action='store_true')
|
2012-08-23 19:36:25 -05:00
|
|
|
parser.add_option('--xserver',
|
|
|
|
dest='xserver',
|
|
|
|
default='xpra',
|
|
|
|
help='Nested X server to use (default is xpra)')
|
2012-05-09 11:33:36 -07:00
|
|
|
parser.add_option('-d', '--debug',
|
|
|
|
dest='debug',
|
|
|
|
default=False,
|
|
|
|
help='Show debug messages',
|
|
|
|
action='store_true')
|
|
|
|
parser.add_option('-r', '--with-resolution',
|
|
|
|
dest='resolution',
|
|
|
|
default='640x480',
|
|
|
|
help='Resolution for X application')
|
|
|
|
|
|
|
|
(my_opt, my_args) = parser.parse_args()
|
2012-08-23 16:29:48 -05:00
|
|
|
if my_opt.debug == True:
|
|
|
|
apparmor.common.DEBUGGING = True
|
2012-08-23 17:12:14 -05:00
|
|
|
if my_opt.template == "default":
|
|
|
|
if my_opt.withx:
|
2012-08-23 19:36:25 -05:00
|
|
|
if my_opt.xserver.lower() != 'xpra' and \
|
|
|
|
my_opt.xserver.lower() != 'xephyr':
|
2012-08-23 19:56:18 -05:00
|
|
|
error("Invalid server '%s'. Use 'xpra' or 'xephyr'" % \
|
|
|
|
my_opt.xserver)
|
2012-08-23 17:12:14 -05:00
|
|
|
my_opt.template = "sandbox-x"
|
|
|
|
else:
|
|
|
|
my_opt.template = "sandbox"
|
|
|
|
|
2012-08-23 16:29:48 -05:00
|
|
|
|
2012-05-09 11:33:36 -07:00
|
|
|
return (my_opt, my_args)
|
|
|
|
|
2012-05-10 01:17:56 -07:00
|
|
|
def gen_policy_name(binary):
|
|
|
|
'''Generate a temporary policy based on the binary name'''
|
2012-08-23 17:15:51 -05:00
|
|
|
return "sandbox-%s%s" % (pwd.getpwuid(os.getuid())[0],
|
2012-08-23 17:12:14 -05:00
|
|
|
re.sub(r'/', '_', binary))
|
2012-05-10 01:17:56 -07:00
|
|
|
|
|
|
|
def aa_exec(command, opt):
|
|
|
|
'''Execute binary under specified policy'''
|
|
|
|
opt.ensure_value("template_var", None)
|
|
|
|
opt.ensure_value("name", None)
|
|
|
|
opt.ensure_value("comment", None)
|
|
|
|
opt.ensure_value("author", None)
|
|
|
|
opt.ensure_value("copyright", None)
|
|
|
|
|
|
|
|
binary = command[0]
|
|
|
|
policy_name = apparmor.sandbox.gen_policy_name(binary)
|
|
|
|
easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt)
|
|
|
|
params = apparmor.easyprof.gen_policy_params(policy_name, opt)
|
|
|
|
policy = easyp.gen_policy(**params)
|
|
|
|
debug("\n%s" % policy)
|
|
|
|
|
|
|
|
# TODO: get rid of sudo
|
|
|
|
tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name)
|
2012-08-23 17:12:14 -05:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
tmp.write(bytes(policy, 'utf-8'))
|
|
|
|
else:
|
|
|
|
tmp.write(policy)
|
2012-05-10 01:17:56 -07:00
|
|
|
tmp.flush()
|
2012-08-23 20:25:29 -05:00
|
|
|
|
2012-05-10 06:43:52 -07:00
|
|
|
debug("using '%s' template" % opt.template)
|
2012-05-10 01:17:56 -07:00
|
|
|
rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name])
|
|
|
|
if rc != 0:
|
|
|
|
raise AppArmorException("Could not load policy")
|
|
|
|
|
|
|
|
args = ['aa-exec', '-p', policy_name] + command
|
|
|
|
rc, report = cmd(args)
|
|
|
|
return rc, report
|
|
|
|
|
|
|
|
def run_sandbox(command, opt):
|
|
|
|
'''Run application'''
|
|
|
|
# aa-exec
|
|
|
|
rc, report = aa_exec(command, opt)
|
|
|
|
return rc, report
|
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
class SandboxXserver():
|
|
|
|
def __init__(self, resolution, title):
|
|
|
|
self.resolution = resolution
|
|
|
|
self.title = title
|
|
|
|
self.pids = []
|
|
|
|
self.find_free_x_display()
|
2012-05-09 11:33:36 -07:00
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
def find_free_x_display(self):
|
|
|
|
'''Find a free X display'''
|
|
|
|
display = ""
|
|
|
|
current = os.environ["DISPLAY"]
|
|
|
|
for i in range(1,257): # TODO: this puts an artificial limit of 256
|
|
|
|
# sandboxed applications
|
|
|
|
tmp = ":%d" % i
|
|
|
|
os.environ["DISPLAY"] = tmp
|
|
|
|
rc, report = cmd(['xset', '-q'])
|
|
|
|
if rc != 0:
|
|
|
|
display = tmp
|
|
|
|
break
|
|
|
|
|
|
|
|
os.environ["DISPLAY"] = current
|
|
|
|
if display == "":
|
|
|
|
raise AppArmorException("Could not find available X display")
|
2012-05-09 11:33:36 -07:00
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
self.display = display
|
|
|
|
|
|
|
|
def cleanup(self):
|
|
|
|
'''Cleanup our forked pids, etc'''
|
|
|
|
# kill server now. It should've terminated, but be sure
|
|
|
|
for pid in self.pids:
|
|
|
|
cmd(['kill', '-15', "%d" % pid])
|
|
|
|
os.kill(pid, 15)
|
|
|
|
os.waitpid(pid, 0)
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
'''start() should be overridden'''
|
|
|
|
|
|
|
|
class SandboxXephyr(SandboxXserver):
|
|
|
|
def start(self):
|
2012-08-23 19:56:18 -05:00
|
|
|
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)
|
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
'''Start a Xephyr server'''
|
|
|
|
listener_x = os.fork()
|
|
|
|
if listener_x == 0:
|
|
|
|
# TODO: break into config file? Which are needed?
|
|
|
|
x_exts = ['-extension', 'GLX',
|
|
|
|
'-extension', 'MIT-SHM',
|
|
|
|
'-extension', 'RENDER',
|
|
|
|
'-extension', 'SECURITY',
|
|
|
|
'-extension', 'DAMAGE'
|
|
|
|
]
|
|
|
|
# verify_these
|
|
|
|
x_extra_args = ['-host-cursor', # less secure?
|
|
|
|
'-fakexa', # for games? seems not needed
|
|
|
|
'-nodri', # more secure?
|
|
|
|
]
|
|
|
|
|
|
|
|
x_args = ['-nolisten', 'tcp',
|
|
|
|
'-screen', self.resolution,
|
|
|
|
'-br', # black background
|
|
|
|
'-reset', # reset after last client exists
|
|
|
|
'-terminate', # terminate at server reset
|
|
|
|
'-title', self.title,
|
|
|
|
] + x_exts + x_extra_args
|
|
|
|
|
|
|
|
args = ['/usr/bin/Xephyr'] + x_args + [self.display]
|
|
|
|
debug(" ".join(args))
|
|
|
|
sys.stderr.flush()
|
|
|
|
os.execv(args[0], args)
|
|
|
|
sys.exit(0)
|
|
|
|
self.pids.append(listener_x)
|
|
|
|
|
|
|
|
time.sleep(0.2) # FIXME: detect if running
|
|
|
|
|
|
|
|
# Next, start the window manager
|
|
|
|
sys.stdout.flush()
|
|
|
|
os.chdir(os.environ["HOME"])
|
|
|
|
listener_wm = os.fork()
|
|
|
|
if listener_wm == 0:
|
|
|
|
# update environment
|
|
|
|
os.environ["DISPLAY"] = self.display
|
|
|
|
debug("DISPLAY is now '%s'" % os.environ["DISPLAY"])
|
|
|
|
|
|
|
|
args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no']
|
|
|
|
debug(" ".join(args))
|
|
|
|
sys.stderr.flush()
|
|
|
|
os.execv(args[0], args)
|
|
|
|
sys.exit(0)
|
2012-05-09 11:33:36 -07:00
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
self.pids.append(listener_wm)
|
|
|
|
time.sleep(0.2) # FIXME: detect if running
|
|
|
|
|
|
|
|
class SandboxXpra(SandboxXserver):
|
|
|
|
def cleanup(self):
|
|
|
|
cmd(['xpra', 'stop', self.display])
|
|
|
|
SandboxXserver.cleanup(self)
|
|
|
|
|
|
|
|
def start(self):
|
2012-08-23 19:56:18 -05:00
|
|
|
for e in ['xpra']:
|
|
|
|
debug("Searching for '%s'" % e)
|
|
|
|
rc, report = cmd(['which', e])
|
|
|
|
if rc != 0:
|
|
|
|
raise AppArmorException("Could not find '%s'" % e)
|
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
listener_x = os.fork()
|
|
|
|
if listener_x == 0:
|
|
|
|
x_args = ['--no-daemon',
|
2012-08-23 20:25:29 -05:00
|
|
|
#'--no-mmap', # for security?
|
2012-08-23 19:36:25 -05:00
|
|
|
'--no-clipboard',
|
|
|
|
'--no-pulseaudio']
|
|
|
|
args = ['/usr/bin/xpra', 'start', self.display] + x_args
|
|
|
|
debug(" ".join(args))
|
|
|
|
sys.stderr.flush()
|
|
|
|
os.execv(args[0], args)
|
|
|
|
sys.exit(0)
|
|
|
|
self.pids.append(listener_x)
|
|
|
|
time.sleep(2) # FIXME: detect if running
|
|
|
|
|
|
|
|
# Next, attach to xpra
|
|
|
|
sys.stdout.flush()
|
|
|
|
os.chdir(os.environ["HOME"])
|
|
|
|
listener_attach = os.fork()
|
|
|
|
if listener_attach == 0:
|
|
|
|
args = ['/usr/bin/xpra', 'attach', self.display,
|
2012-08-23 20:25:29 -05:00
|
|
|
'--title=%s' % self.title]
|
2012-08-23 19:36:25 -05:00
|
|
|
debug(" ".join(args))
|
|
|
|
sys.stderr.flush()
|
|
|
|
os.execv(args[0], args)
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
self.pids.append(listener_attach)
|
|
|
|
|
|
|
|
def run_xsandbox(command, opt):
|
|
|
|
'''Run X application in a sandbox'''
|
2012-05-09 11:33:36 -07:00
|
|
|
# save environment
|
|
|
|
old_display = os.environ["DISPLAY"]
|
2012-08-23 19:36:25 -05:00
|
|
|
debug ("DISPLAY=%s" % old_display)
|
2012-05-09 11:33:36 -07:00
|
|
|
old_cwd = os.getcwd()
|
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
# first, start X
|
|
|
|
if opt.xserver.lower() == "xephyr":
|
|
|
|
x = SandboxXephyr(opt.resolution, command[0])
|
|
|
|
else:
|
|
|
|
x = SandboxXpra(opt.resolution, command[0])
|
|
|
|
x.start()
|
|
|
|
|
2012-05-09 11:33:36 -07:00
|
|
|
# update environment
|
2012-08-23 19:36:25 -05:00
|
|
|
os.environ["DISPLAY"] = x.display
|
2012-05-09 11:33:36 -07:00
|
|
|
debug("DISPLAY is now '%s'" % os.environ["DISPLAY"])
|
|
|
|
|
2012-05-10 01:17:56 -07:00
|
|
|
# aa-exec
|
2012-08-23 19:36:25 -05:00
|
|
|
try:
|
|
|
|
rc, report = aa_exec(command, opt)
|
|
|
|
except:
|
|
|
|
x.cleanup()
|
|
|
|
raise
|
2012-05-09 11:33:36 -07:00
|
|
|
|
|
|
|
# reset environment
|
|
|
|
os.environ["DISPLAY"] = old_display
|
|
|
|
debug("DISPLAY is now '%s'" % os.environ["DISPLAY"])
|
|
|
|
|
|
|
|
os.chdir(old_cwd)
|
|
|
|
|
2012-08-23 19:36:25 -05:00
|
|
|
x.cleanup()
|
2012-05-09 11:33:36 -07:00
|
|
|
|
2012-05-10 01:17:56 -07:00
|
|
|
return rc, report
|