mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-31 06:16:03 +00:00
adjust sandbox code:
- for python3 - to add xpra support - refactoring - cleanups
This commit is contained in:
@@ -33,5 +33,5 @@ if __name__ == "__main__":
|
||||
else:
|
||||
rc, report = apparmor.sandbox.run_sandbox(args, opt)
|
||||
|
||||
print report
|
||||
apparmor.common.msg(report)
|
||||
sys.exit(rc)
|
||||
|
@@ -8,11 +8,11 @@
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
from __future__ import print_function
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
DEBUGGING = False
|
||||
DEBUGGING = True
|
||||
|
||||
#
|
||||
# Utility classes
|
||||
@@ -31,7 +31,7 @@ class AppArmorException(Exception):
|
||||
def error(out, exit_code=1, do_exit=True):
|
||||
'''Print error message and exit'''
|
||||
try:
|
||||
print >> sys.stderr, "ERROR: %s" % (out)
|
||||
print("ERROR: %s" % (out), file=sys.stderr)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
@@ -41,14 +41,14 @@ def error(out, exit_code=1, do_exit=True):
|
||||
def warn(out):
|
||||
'''Print warning message'''
|
||||
try:
|
||||
print >> sys.stderr, "WARN: %s" % (out)
|
||||
print("WARN: %s" % (out), file=sys.stderr)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def msg(out, output=sys.stdout):
|
||||
'''Print message'''
|
||||
try:
|
||||
print >> output, "%s" % (out)
|
||||
print("%s" % (out), file=sys.stdout)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
@@ -57,7 +57,7 @@ def debug(out):
|
||||
global DEBUGGING
|
||||
if DEBUGGING:
|
||||
try:
|
||||
print >> sys.stderr, "DEBUG: %s" % (out)
|
||||
print("DEBUG: %s" % (out), file=sys.stderr)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
@@ -67,20 +67,29 @@ def cmd(command):
|
||||
try:
|
||||
sp = subprocess.Popen(command, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
except OSError, ex:
|
||||
except OSError as ex:
|
||||
return [127, str(ex)]
|
||||
|
||||
out = sp.communicate()[0]
|
||||
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, ex:
|
||||
except OSError as ex:
|
||||
return [127, str(ex)]
|
||||
|
||||
out = sp2.communicate()[0]
|
||||
if sys.version_info[0] >= 3:
|
||||
out = sp2.communicate()[0].decode('ascii', 'ignore')
|
||||
else:
|
||||
out = sp2.communicate()[0]
|
||||
|
||||
return [sp2.returncode, out]
|
||||
|
||||
|
@@ -13,27 +13,30 @@ import apparmor.easyprof
|
||||
import optparse
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
DEBUGGING = False
|
||||
|
||||
def check_requirements(binary):
|
||||
'''Verify necessary software is installed'''
|
||||
exes = ['Xephyr', 'matchbox-window-manager', binary]
|
||||
exes = ['xset', # for detecting free X display
|
||||
'aa-easyprof', # for templates
|
||||
'aa-exec', # for changing profile
|
||||
'sudo', # 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'''
|
||||
global DEBUGGING
|
||||
|
||||
if parser == None:
|
||||
parser = optparse.OptionParser()
|
||||
|
||||
@@ -42,6 +45,10 @@ def parse_args(args=None, parser=None):
|
||||
default=False,
|
||||
help='Run in isolated X server',
|
||||
action='store_true')
|
||||
parser.add_option('--xserver',
|
||||
dest='xserver',
|
||||
default='xpra',
|
||||
help='Nested X server to use (default is xpra)')
|
||||
parser.add_option('-d', '--debug',
|
||||
dest='debug',
|
||||
default=False,
|
||||
@@ -53,15 +60,25 @@ def parse_args(args=None, parser=None):
|
||||
help='Resolution for X application')
|
||||
|
||||
(my_opt, my_args) = parser.parse_args()
|
||||
if my_opt.debug:
|
||||
DEBUGGING = True
|
||||
if my_opt.debug == True:
|
||||
apparmor.common.DEBUGGING = True
|
||||
if my_opt.template == "default":
|
||||
if my_opt.withx:
|
||||
if my_opt.xserver.lower() != 'xpra' and \
|
||||
my_opt.xserver.lower() != 'xephyr':
|
||||
error("Invalid server '%s'. Use 'xpra' or 'xephyr'" % \
|
||||
my_opt.xserver)
|
||||
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'''
|
||||
# TODO: this may not be good enough
|
||||
return "sandbox-%s-%s" % (pwd.getpwuid(os.getuid())[0],
|
||||
os.path.basename(binary))
|
||||
return "sandbox-%s%s" % (pwd.getpwuid(os.getuid())[0],
|
||||
re.sub(r'/', '_', binary))
|
||||
|
||||
def aa_exec(command, opt):
|
||||
'''Execute binary under specified policy'''
|
||||
@@ -80,8 +97,12 @@ def aa_exec(command, opt):
|
||||
|
||||
# TODO: get rid of sudo
|
||||
tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name)
|
||||
tmp.write(policy)
|
||||
if sys.version_info[0] >= 3:
|
||||
tmp.write(bytes(policy, 'utf-8'))
|
||||
else:
|
||||
tmp.write(policy)
|
||||
tmp.flush()
|
||||
|
||||
debug("using '%s' template" % opt.template)
|
||||
rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name])
|
||||
if rc != 0:
|
||||
@@ -91,94 +112,184 @@ def aa_exec(command, opt):
|
||||
rc, report = cmd(args)
|
||||
return rc, report
|
||||
|
||||
def find_free_x_display():
|
||||
# TODO: detect/track and get an available display
|
||||
x_display = ":1"
|
||||
return x_display
|
||||
|
||||
def run_sandbox(command, opt):
|
||||
'''Run application'''
|
||||
# aa-exec
|
||||
#opt.template = "sandbox-x"
|
||||
rc, report = aa_exec(command, opt)
|
||||
return rc, report
|
||||
|
||||
class SandboxXserver():
|
||||
def __init__(self, resolution, title):
|
||||
self.resolution = resolution
|
||||
self.title = title
|
||||
self.pids = []
|
||||
self.find_free_x_display()
|
||||
|
||||
# TODO: for now, drop Unity's globalmenu proxy since it doesn't work
|
||||
# right in the application.
|
||||
os.environ["UBUNTU_MENUPROXY"] = ""
|
||||
|
||||
def find_free_x_display(self):
|
||||
'''Find a free X display'''
|
||||
display = ""
|
||||
current = os.environ["DISPLAY"]
|
||||
for i in range(1,257): # TODO: this puts an artificial limit of 256
|
||||
# sandboxed applications
|
||||
tmp = ":%d" % i
|
||||
os.environ["DISPLAY"] = tmp
|
||||
rc, report = cmd(['xset', '-q'])
|
||||
if rc != 0:
|
||||
display = tmp
|
||||
break
|
||||
|
||||
os.environ["DISPLAY"] = current
|
||||
if display == "":
|
||||
raise AppArmorException("Could not find available X display")
|
||||
|
||||
self.display = display
|
||||
|
||||
def cleanup(self):
|
||||
'''Cleanup our forked pids, etc'''
|
||||
# kill server now. It should've terminated, but be sure
|
||||
for pid in self.pids:
|
||||
os.kill(pid, 15)
|
||||
os.waitpid(pid, 0)
|
||||
|
||||
def start(self):
|
||||
'''start() should be overridden'''
|
||||
|
||||
class SandboxXephyr(SandboxXserver):
|
||||
def start(self):
|
||||
for e in ['Xephyr', 'matchbox-window-manager']:
|
||||
debug("Searching for '%s'" % e)
|
||||
rc, report = cmd(['which', e])
|
||||
if rc != 0:
|
||||
raise AppArmorException("Could not find '%s'" % e)
|
||||
|
||||
'''Start a Xephyr server'''
|
||||
listener_x = os.fork()
|
||||
if listener_x == 0:
|
||||
# TODO: break into config file? Which are needed?
|
||||
x_exts = ['-extension', 'GLX',
|
||||
'-extension', 'MIT-SHM',
|
||||
'-extension', 'RENDER',
|
||||
'-extension', 'SECURITY',
|
||||
'-extension', 'DAMAGE'
|
||||
]
|
||||
# verify_these
|
||||
x_extra_args = ['-host-cursor', # less secure?
|
||||
'-fakexa', # for games? seems not needed
|
||||
'-nodri', # more secure?
|
||||
]
|
||||
|
||||
x_args = ['-nolisten', 'tcp',
|
||||
'-screen', self.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(1) # FIXME: detect if running
|
||||
|
||||
# Next, start the window manager
|
||||
sys.stdout.flush()
|
||||
os.chdir(os.environ["HOME"])
|
||||
listener_wm = os.fork()
|
||||
if listener_wm == 0:
|
||||
# update environment
|
||||
os.environ["DISPLAY"] = self.display
|
||||
debug("DISPLAY is now '%s'" % os.environ["DISPLAY"])
|
||||
|
||||
args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no']
|
||||
debug(" ".join(args))
|
||||
sys.stderr.flush()
|
||||
os.execv(args[0], args)
|
||||
sys.exit(0)
|
||||
|
||||
self.pids.append(listener_wm)
|
||||
time.sleep(1) # FIXME: detect if running
|
||||
|
||||
|
||||
class SandboxXpra(SandboxXserver):
|
||||
def cleanup(self):
|
||||
cmd(['xpra', 'stop', self.display])
|
||||
SandboxXserver.cleanup(self)
|
||||
|
||||
def start(self):
|
||||
for e in ['xpra']:
|
||||
debug("Searching for '%s'" % e)
|
||||
rc, report = cmd(['which', e])
|
||||
if rc != 0:
|
||||
raise AppArmorException("Could not find '%s'" % e)
|
||||
|
||||
listener_x = os.fork()
|
||||
if listener_x == 0:
|
||||
x_args = ['--no-daemon',
|
||||
#'--no-mmap', # for security?
|
||||
'--no-clipboard',
|
||||
'--no-pulseaudio']
|
||||
args = ['/usr/bin/xpra', 'start', self.display] + x_args
|
||||
debug(" ".join(args))
|
||||
cmd(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,
|
||||
'--title=%s' % self.title]
|
||||
debug(" ".join(args))
|
||||
cmd(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'''
|
||||
# Find a display to run on
|
||||
x_display = find_free_x_display()
|
||||
|
||||
debug (os.environ["DISPLAY"])
|
||||
|
||||
# first, start X
|
||||
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', opt.resolution,
|
||||
'-br', # black background
|
||||
'-reset', # reset after last client exists
|
||||
'-terminate', # terminate at server reset
|
||||
'-title', command[0],
|
||||
] + x_exts + x_extra_args
|
||||
|
||||
args = ['/usr/bin/Xephyr'] + x_args + [x_display]
|
||||
debug(" ".join(args))
|
||||
sys.stderr.flush()
|
||||
os.execv(args[0], args)
|
||||
sys.exit(0)
|
||||
|
||||
# save environment
|
||||
old_display = os.environ["DISPLAY"]
|
||||
debug ("DISPLAY=%s" % old_display)
|
||||
old_cwd = os.getcwd()
|
||||
|
||||
# first, start X
|
||||
if opt.xserver.lower() == "xephyr":
|
||||
x = SandboxXephyr(opt.resolution, command[0])
|
||||
else:
|
||||
x = SandboxXpra(opt.resolution, command[0])
|
||||
x.start()
|
||||
|
||||
# update environment
|
||||
os.environ["DISPLAY"] = x_display
|
||||
os.environ["DISPLAY"] = x.display
|
||||
debug("DISPLAY is now '%s'" % os.environ["DISPLAY"])
|
||||
|
||||
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:
|
||||
args = ['/usr/bin/matchbox-window-manager', '-use_titlebar', 'no']
|
||||
debug(" ".join(args))
|
||||
sys.stderr.flush()
|
||||
os.execv(args[0], args)
|
||||
sys.exit(0)
|
||||
|
||||
time.sleep(0.2) # FIXME: detect if running
|
||||
|
||||
# aa-exec
|
||||
#opt.template = "sandbox-x"
|
||||
rc, report = aa_exec(command, opt)
|
||||
try:
|
||||
rc, report = aa_exec(command, opt)
|
||||
except:
|
||||
x.cleanup()
|
||||
raise
|
||||
x.cleanup()
|
||||
|
||||
# reset environment
|
||||
os.chdir(old_cwd)
|
||||
os.environ["DISPLAY"] = old_display
|
||||
debug("DISPLAY is now '%s'" % os.environ["DISPLAY"])
|
||||
|
||||
os.chdir(old_cwd)
|
||||
|
||||
# kill server now. It should've terminated, but be sure
|
||||
cmd(['kill', '-15', "%d" % listener_wm])
|
||||
os.kill(listener_wm, 15)
|
||||
os.waitpid(listener_wm, 0)
|
||||
cmd(['kill', '-15', "%d" % listener_x])
|
||||
os.kill(listener_x, 15)
|
||||
os.waitpid(listener_x, 0)
|
||||
|
||||
return rc, report
|
||||
|
Reference in New Issue
Block a user