Compile the policy using a script executed by meson. Generate 2 versions of the binary policy to allow installation to systems with any selinux type (targeted, mls and minimum). Signed-off-by: Vit Mojzis <vmojzis@xxxxxxxxxx> --- Changes: - Hide errors regarding duplicate definition of interfaces libvirt.spec.in | 92 ++++++++++++++++ src/security/meson.build | 13 +++ src/security/selinux/compile_policy.py | 144 +++++++++++++++++++++++++ src/security/selinux/mcs/meson.build | 20 ++++ src/security/selinux/meson.build | 7 ++ src/security/selinux/mls/meson.build | 20 ++++ 6 files changed, 296 insertions(+) create mode 100755 src/security/selinux/compile_policy.py create mode 100644 src/security/selinux/mcs/meson.build create mode 100644 src/security/selinux/meson.build create mode 100644 src/security/selinux/mls/meson.build diff --git a/libvirt.spec.in b/libvirt.spec.in index da7af2824e..940582b2c7 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -3,6 +3,12 @@ # This spec file assumes you are building on a Fedora or RHEL version # that's still supported by the vendor. It may work on other distros # or versions, but no effort will be made to ensure that going forward. + +%if 0%{?fedora} > 33 || 0%{?rhel} > 8 + %global with_selinux 1 + %global modulename virt +%endif + %define min_rhel 7 %define min_fedora 31 @@ -467,6 +473,12 @@ Requires(pre): shadow-utils # Needed by /usr/libexec/libvirt-guests.sh script. Requires: gettext +%if 0%{?with_selinux} +# This ensures that the *-selinux package and all it’s dependencies are not pulled +# into containers and other systems that do not use SELinux +Requires: (%{name}-daemon-selinux if selinux-policy-base) +%endif + # Ensure smooth upgrades Obsoletes: libvirt-admin < 7.3.0 Provides: libvirt-admin @@ -979,6 +991,19 @@ Requires: libvirt-daemon-driver-network = %{version}-%{release} %description nss Libvirt plugin for NSS for translating domain names into IP addresses. +%if 0%{?with_selinux} +# SELinux subpackage +%package daemon-selinux +Summary: Libvirt daemon SELinux policy +Requires: selinux-policy-base +Requires(post): selinux-policy-base +BuildRequires: selinux-policy-devel +BuildArch: noarch +%{?selinux_requires} + +%description daemon-selinux +SELinux policy module for libvirt daemons. +%endif %prep @@ -1495,6 +1520,63 @@ getent group virtlogin >/dev/null || groupadd -r virtlogin exit 0 %endif +%if 0%{?with_selinux} +# SELinux contexts are saved so that only affected files can be +# relabeled after the policy module installation +%pre daemon-selinux +if [ -e /etc/selinux/config ]; then + . /etc/selinux/config + %selinux_relabel_pre -s ${SELINUXTYPE} +fi + +%post daemon-selinux +# only policy reload is needed - module installation is managed by triggers +/usr/sbin/selinuxenabled && /usr/sbin/load_policy || : + +%postun daemon-selinux +if [ $1 -eq 0 ]; then + /usr/sbin/selinuxenabled && /usr/sbin/load_policy || : +fi + +%posttrans daemon-selinux +if [ -e /etc/selinux/config ]; then + . /etc/selinux/config + %selinux_relabel_post -s ${SELINUXTYPE} +fi + +# install the policy module to corresponding policy store if +# selinux-policy-{targeted|mls|minimum} package is installed on the system +%triggerin -n %{name}-daemon-selinux -- selinux-policy-targeted +/usr/sbin/semodule -n -s targeted -X 200 -i %{_datadir}/selinux/packages/%{modulename}.pp.bz2 || : + +%triggerin -n %{name}-daemon-selinux -- selinux-policy-minimum +/usr/sbin/semodule -n -s minimum -X 200 -i %{_datadir}/selinux/packages/%{modulename}.pp.bz2 || : +# libvirt module is installed by default, but disabled -- enable it +/usr/sbin/semodule -n -s minimum -e %{modulename} || : + +%triggerin -n %{name}-daemon-selinux -- selinux-policy-mls +/usr/sbin/semodule -n -s mls -X 200 -i %{_datadir}/selinux/packages/mls/%{modulename}.pp.bz2 || : + +# remove the policy module from corresponding module store if +# libvirt-selinux or selinux-policy-* was removed from the system, +# but not when either package gets updated +%triggerun -n %{name}-daemon-selinux -- selinux-policy-targeted +if ([ $1 -eq 0 ] || [ $2 -eq 0 ]) && [ -e %{_sharedstatedir}/selinux/targeted/active/modules/200/%{modulename} ]; then + /usr/sbin/semodule -n -s targeted -X 200 -r %{modulename} || : +fi + +%triggerun -n %{name}-daemon-selinux -- selinux-policy-minimum +if ([ $1 -eq 0 ] || [ $2 -eq 0 ]) && [ -e %{_sharedstatedir}/selinux/minimum/active/modules/200/%{modulename} ]; then + /usr/sbin/semodule -n -s minimum -X 200 -r %{modulename} || : + /usr/sbin/semodule -n -d %{modulename} || : +fi + +%triggerun -n %{name}-daemon-selinux -- selinux-policy-mls +if ([ $1 -eq 0 ] || [ $2 -eq 0 ]) && [ -e %{_sharedstatedir}/selinux/mls/active/modules/200/%{modulename} ]; then + /usr/sbin/semodule -n -s mls -X 200 -r %{modulename} || : +fi +%endif + %files %files docs @@ -1955,5 +2037,15 @@ exit 0 %{_datadir}/libvirt/api/libvirt-qemu-api.xml %{_datadir}/libvirt/api/libvirt-lxc-api.xml +%if 0%{?with_selinux} +%files daemon-selinux +%{_datadir}/selinux/packages/%{modulename}.pp.* +%{_datadir}/selinux/packages/mls/%{modulename}.pp.* +%ghost %{_sharedstatedir}/selinux/targeted/active/modules/200/%{modulename} +%ghost %{_sharedstatedir}/selinux/minimum/active/modules/200/%{modulename} +%ghost %{_sharedstatedir}/selinux/mls/active/modules/200/%{modulename} +%{_datadir}/selinux/devel/include/contrib/%{modulename}.if +%endif + %changelog diff --git a/src/security/meson.build b/src/security/meson.build index 416fec7900..1d377bbbf9 100644 --- a/src/security/meson.build +++ b/src/security/meson.build @@ -56,3 +56,16 @@ endif if conf.has('WITH_APPARMOR_PROFILES') subdir('apparmor') endif + +os_release = run_command('grep', '^ID=', '/etc/os-release').stdout() +os_version = run_command('grep', '^VERSION_ID=', '/etc/os-release').stdout().split('=') +if (os_version.length() == 2) + os_version = os_version[1] +else + os_version = 0 +endif + +if ((os_release.contains('fedora') and os_version.version_compare('>33')) or + (os_release.contains('rhel') and os_version.version_compare('>8'))) + subdir('selinux') +endif diff --git a/src/security/selinux/compile_policy.py b/src/security/selinux/compile_policy.py new file mode 100755 index 0000000000..95f0741d1a --- /dev/null +++ b/src/security/selinux/compile_policy.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see +# <http://www.gnu.org/licenses/>. + +# This script is based on selinux-policy Makefile +# https://github.com/fedora-selinux/selinux-policy/blob/rawhide/support/Makefile.devel + +import subprocess +import sys +import os +import glob + +if len(sys.argv) != 7: + print(("Usage: {} <policy>.te <policy>.if <policy>.fc <output>.pp <tmpdir>" + " <type (mls/mcs)>").format(sys.argv[0]), file=sys.stderr) + exit(os.EX_USAGE) + +module_name = os.path.splitext(os.path.basename(sys.argv[1]))[0] + +m4param = ["-D", "distro_redhat", "-D", "hide_broken_symptoms", + "-D", "mls_num_sens=16", "-D", "mls_num_cats=1024", + "-D", "mcs_num_cats=1024"] + +if sys.argv[6] == "mls": + m4param = ["-D", "enable_mls"] + m4param +else: + m4param = ["-D", "enable_mcs"] + m4param + +SHAREDIR = "/usr/share/selinux" +HEADERDIR = os.path.join(SHAREDIR, "devel/include") + +m4support = sorted(glob.glob("{}/support/*.spt".format(HEADERDIR))) +header_layers = glob.glob("{}/*/".format(HEADERDIR)) +header_layers = sorted([x for x in header_layers + if os.path.join(HEADERDIR, "support") not in x]) + +header_interfaces = [] +for layer in header_layers: + header_interfaces.extend(glob.glob("{}/*.if".format(layer))) +header_interfaces.sort() + +# prepare temp folder +try: + os.makedirs(sys.argv[5]) +except Exception: + pass + +# remove old trash from the temp folder +tmpfiles = ["{}.{}".format(module_name, ext) + for ext in ["mod", "mod.fc", "tmp"]] +for name in ["iferror.m4", "all_interfaces.conf"] + tmpfiles: + try: + os.remove(os.path.join(sys.argv[5], name)) + except Exception: + pass + +# tmp/all_interfaces.conf +# echo "ifdef(\`__if_error',\`m4exit(1)')" > $5/iferror.m4 +with open(os.path.join(sys.argv[5], "iferror.m4"), "w") as file: + file.write("ifdef(`__if_error',`m4exit(1)')\n") + +# echo "divert(-1)" > $5/all_interfaces.conf +with open(os.path.join(sys.argv[5], "all_interfaces.conf"), "w") as int_file: + int_file.write("divert(-1)\n") + +# m4 $M4SUPPORT $HEADER_INTERFACES $2 $5/iferror.m4 +# | sed -e s/dollarsstar/\$\$\*/g >> $5/all_interfaces.conf +m4_run = subprocess.run(r"m4 {} | sed -e s/dollarsstar/\$\$\*/g >> {}".format( + " ".join([*m4support, *header_interfaces, sys.argv[2], + os.path.join(sys.argv[5], "iferror.m4")]), + os.path.join(sys.argv[5], "all_interfaces.conf")), + shell=True, check=True, stderr=subprocess.PIPE, + universal_newlines=True) + +# Filter out messages about duplicate definition of interfaces. e.g. +# virt.if:13: Error: duplicate definition of virt_stub_lxc(). Original +# definition on 13. +# They are expected and can be safely ignored. +for line in m4_run.stderr.split('\n'): + if line and "Error: duplicate definition of" not in line: + print(line, file=sys.stderr) + +# doesn't work properly without "shell=True" +# m4_process = Popen(["m4", *m4support, *header_interfaces, sys.argv[2], +# os.path.join(sys.argv[5], "iferror.m4")], +# stdout=PIPE, stderr=PIPE) +# sed_process = Popen(["sed", "-e", "s/dollarsstar/\$\$\*/g"], +# stdin=m4_process.stdout, stdout=int_file) +# outs, errs = m4_process.communicate() + +# echo "divert" >> $5/all_interfaces.conf +with open(os.path.join(sys.argv[5], "all_interfaces.conf"), "a") as file: + file.write("divert\n") + +# tmp/%.mod +# m4 $M4PARAM -s $M4SUPPORT $5/all_interfaces.conf $1 > $5/$MODULE_NAME.tmp +with open(os.path.join(sys.argv[5], "{}.tmp".format(module_name)), + "w") as tmp_file: + subprocess.run(["m4", *m4param, "-s", *m4support, + os.path.join(sys.argv[5], "all_interfaces.conf"), + sys.argv[1]], stdout=tmp_file, check=True) + +# /usr/bin/checkmodule -M -m $5/$MODULE_NAME.tmp -o $5/$MODULE_NAME.mod +subprocess.run(["/usr/bin/checkmodule", + "-M", + "-m", + os.path.join(sys.argv[5], "{}.tmp".format(module_name)), + "-o", + os.path.join(sys.argv[5], "{}.mod".format(module_name))], + check=True) + + +# tmp/%.mod.fc +# m4 $M4PARAM $M4SUPPORT $3 > $5/$MODULE_NAME.mod.fc +with open(os.path.join(sys.argv[5], + "{}.mod.fc".format(module_name)), "w") as mod_fc_file: + subprocess.run(["m4", *m4param, *m4support, sys.argv[3]], + stdout=mod_fc_file, check=True) + +# %.pp +# /usr/bin/semodule_package -o $4 -m $5/$MODULE_NAME.mod +# -f $5/$MODULE_NAME.mod.fc +subprocess.run(["/usr/bin/semodule_package", + "-o", + sys.argv[4], + "-m", + os.path.join(sys.argv[5], "{}.mod".format(module_name)), + "-f", + os.path.join(sys.argv[5], "{}.mod.fc".format(module_name))], + check=True) diff --git a/src/security/selinux/mcs/meson.build b/src/security/selinux/mcs/meson.build new file mode 100644 index 0000000000..419253f151 --- /dev/null +++ b/src/security/selinux/mcs/meson.build @@ -0,0 +1,20 @@ +selinux_sources = [ + '../virt.te', + '../virt.if', + '../virt.fc', +] + +# targeted/minimum policy module +virt_pp = custom_target('virt.pp', + output : 'virt.pp', + input : selinux_sources, + command : [compile_policy_prog, '@INPUT@', '@OUTPUT@', 'selinux/mcs/tmp', 'mcs'], + install : false) + +bzip = custom_target('virt.pp.bz2', + output : 'virt.pp.bz2', + input : virt_pp, + command : [bzip2_prog, '-c', '-9', '@INPUT@'], + capture : true, + install : true, + install_dir : 'share/selinux/packages') diff --git a/src/security/selinux/meson.build b/src/security/selinux/meson.build new file mode 100644 index 0000000000..c8eec463d2 --- /dev/null +++ b/src/security/selinux/meson.build @@ -0,0 +1,7 @@ +set_variable('compile_policy_prog', find_program('compile_policy.py')) +set_variable('bzip2_prog', find_program('bzip2')) + +install_data('virt.if', install_dir : 'share/selinux/devel/include/contrib') + +subdir('mcs') +subdir('mls') diff --git a/src/security/selinux/mls/meson.build b/src/security/selinux/mls/meson.build new file mode 100644 index 0000000000..20bab41fea --- /dev/null +++ b/src/security/selinux/mls/meson.build @@ -0,0 +1,20 @@ +selinux_sources = [ + '../virt.te', + '../virt.if', + '../virt.fc', +] + +# MLS policy module +virt_pp_mls = custom_target('virt.pp', + output : 'virt.pp', + input : selinux_sources, + command : [compile_policy_prog, '@INPUT@', '@OUTPUT@', 'selinux/mls/tmp', 'mls'], + install : false) + +bzip_mls = custom_target('virt.pp.bz2', + output : 'virt.pp.bz2', + input : virt_pp_mls, + command : [bzip2_prog, '-c', '-9', '@INPUT@'], + capture : true, + install : true, + install_dir : 'share/selinux/packages/mls') -- 2.30.2