run: add ability to set selinux context

When running libvirt from the build directory with the 'run' script, it
will run as unconfined_t. This can result in unexpected behavior when
selinux is enforcing due to the fact that the selinux policies are
written assuming that libvirt is running with the
system_u:system_r:virtd_t context. This patch adds a new --selinux
option to the run script. When this option is specified, it will launch
the specified binary using the 'runcon' utility to set its selinux
context to the one mentioned above. Since this may require root
privileges, setting the selinux context is not the default behavior and
must be enabled with the command line switch.

Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Reviewed-by: Martin Kletzander <mkletzan@redhat.com>
This commit is contained in:
Jonathon Jongsma 2023-04-21 13:38:10 -05:00
parent 2bad705ebb
commit 2aa5c0789c
1 changed files with 79 additions and 19 deletions

76
run.in
View File

@ -40,9 +40,11 @@
# #
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
import argparse
import os import os
import os.path import os.path
import random import random
import shutil
import signal import signal
import subprocess import subprocess
import sys import sys
@ -59,15 +61,20 @@ def prepend(env, varname, extradir):
here = "@abs_builddir@" here = "@abs_builddir@"
if len(sys.argv) < 2: parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
print("syntax: %s BINARY [ARGS...]" % sys.argv[0], file=sys.stderr) parser.add_argument('--selinux',
action='store_true',
help='Run in the appropriate selinux context')
opts, args = parser.parse_known_args()
if len(args) < 1:
print("syntax: %s [--selinux] BINARY [ARGS...]" % sys.argv[0], file=sys.stderr)
sys.exit(1) sys.exit(1)
prog = sys.argv[1] prog = args[0]
args = sys.argv[1:]
env = os.environ env = os.environ
prepend(env, "LD_LIBRARY_PATH", os.path.join(here, "src")) prepend(env, "LD_LIBRARY_PATH", os.path.join(here, "src"))
prepend(env, "PKG_CONFIG_PATH", os.path.join(here, "src")) prepend(env, "PKG_CONFIG_PATH", os.path.join(here, "src"))
prepend(env, "PATH", os.path.join(here, "tools")) prepend(env, "PATH", os.path.join(here, "tools"))
@ -130,10 +137,25 @@ def change_unit(name, action):
return ret == 0 return ret == 0
def chcon(path, user, role, type):
print("Setting file context of {} to u={}, r={}, t={}...".format(progpath,
user,
role,
type))
ret = subprocess.call(["chcon", "-u", user, "-r", role, "-t", type, path])
return ret == 0
def restorecon(path):
print("Restoring selinux context for {}...".format(path))
ret = subprocess.call(["restorecon", path])
return ret == 0
try_stop_units = [] try_stop_units = []
if is_systemd_host(): if is_systemd_host():
maybe_stopped_units = [] maybe_stopped_units = []
for arg in sys.argv: for arg in args:
name = os.path.basename(arg) name = os.path.basename(arg)
if is_modular_daemon(name): if is_modular_daemon(name):
# Only need to stop libvirtd or this specific modular unit # Only need to stop libvirtd or this specific modular unit
@ -149,11 +171,10 @@ if is_systemd_host():
if is_unit_active(unit): if is_unit_active(unit):
try_stop_units.append(unit) try_stop_units.append(unit)
if len(try_stop_units) == 0: if len(try_stop_units) == 0 and not opts.selinux:
# Run the program directly, replacing ourselves # Run the program directly, replacing ourselves
os.execvpe(prog, args, env) os.execvpe(prog, args, env)
else: else:
print("Temporarily stopping systemd units...")
stopped_units = [] stopped_units = []
def sighandler(signum, frame): def sighandler(signum, frame):
@ -164,6 +185,11 @@ else:
signal.signal(signal.SIGQUIT, sighandler) signal.signal(signal.SIGQUIT, sighandler)
try: try:
dorestorecon = False
progpath = shutil.which(prog)
if len(try_stop_units):
print("Temporarily stopping systemd units...")
for unit in try_stop_units: for unit in try_stop_units:
print(" > %s" % unit) print(" > %s" % unit)
if not change_unit(unit, "stop"): if not change_unit(unit, "stop"):
@ -171,6 +197,37 @@ else:
stopped_units.append(unit) stopped_units.append(unit)
if opts.selinux:
# if using a wrapper command like 'gdb', setting the selinux
# context won't work because the wrapper command will not be a
# valid entrypoint for the virtd_t context
if os.path.basename(prog) not in ["libvirtd", *modular_daemons]:
raise Exception("'{}' is not recognized as a valid daemon. "
"Selinux process context can only be set when "
"executing a daemon directly without wrapper "
"commands".format(prog))
if not progpath:
raise Exception("Can't find executable {} for selinux labeling"
.format(prog))
if not progpath.startswith(os.path.abspath(here)):
raise Exception("Refusing to change selinux context of file "
"'{}' outside build directory"
.format(progpath))
# selinux won't allow us to transition to the virtd_t context from
# e.g. the user_home_t context (the likely label of the local
# executable file)
if not chcon(progpath, "system_u", "object_r", "virtd_exec_t"):
raise Exception("Failed to change selinux context of binary")
dorestorecon = True
args = ['runcon',
'-u', 'system_u',
'-r', 'system_r',
'-t', 'virtd_t', *args]
print("Running '%s'..." % str(" ".join(args))) print("Running '%s'..." % str(" ".join(args)))
ret = subprocess.call(args, env=env) ret = subprocess.call(args, env=env)
except KeyboardInterrupt: except KeyboardInterrupt:
@ -178,9 +235,12 @@ else:
except Exception as e: except Exception as e:
print("%s" % e, file=sys.stderr) print("%s" % e, file=sys.stderr)
finally: finally:
if len(stopped_units):
print("Re-starting original systemd units...") print("Re-starting original systemd units...")
stopped_units.reverse() stopped_units.reverse()
for unit in stopped_units: for unit in stopped_units:
print(" > %s" % unit) print(" > %s" % unit)
if not change_unit(unit, "start"): if not change_unit(unit, "start"):
print(" ! unable to restart %s" % unit, file=sys.stderr) print(" ! unable to restart %s" % unit, file=sys.stderr)
if dorestorecon:
restorecon(progpath)