From 87bd784614a1246bfe706dbe5d159c4f242daec6 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Wed, 18 Oct 2023 20:15:07 -0700 Subject: [PATCH] Add dmz-vs-selinux kludge and a way to disable it Add a workaround for a problem of older container-selinux not allowing runc to use dmz feature. If runc sees that SELinux is in enforced mode and the container's SELinux label is set, it disables dmz. Add a build tag, runc_dmz_selinux_nocompat, which disables the workaround. Newer distros that ship container-selinux >= 2.224.0 (currently CentOS Stream 8 and 9, RHEL 8 and 9, and Fedora 38+) may build runc with this build tag set to benefit from dmz working with SELinux. Document the build tag in the top-level and libct/dmz READMEs. Use the build tag in our CI builds for CentOS Stream 9 and Fedora 38, as they already has container-selinux 2.224.0 available in updates. Add a TODO to use the build tag for CentOS Stream 8 once it has container-selinux updated. Signed-off-by: Kir Kolyshkin --- .cirrus.yml | 13 ++++++++++++- README.md | 2 ++ Vagrantfile.fedora | 3 +++ libcontainer/container_linux.go | 4 ++++ libcontainer/dmz/README.md | 12 ++++++++++++ libcontainer/dmz/selinux.go | 10 ++++++++++ libcontainer/dmz/selinux_compat.go | 28 ++++++++++++++++++++++++++++ 7 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 libcontainer/dmz/selinux.go create mode 100644 libcontainer/dmz/selinux_compat.go diff --git a/.cirrus.yml b/.cirrus.yml index 0b24e5c50..a3ee5b5b1 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -57,7 +57,7 @@ task: mkdir -p -m 0700 /root/.ssh vagrant ssh-config >> /root/.ssh/config guest_info_script: | - ssh default 'sh -exc "uname -a && systemctl --version && df -T && cat /etc/os-release && go version && sestatus"' + ssh default 'sh -exc "uname -a && systemctl --version && df -T && cat /etc/os-release && go version && sestatus && rpm -q container-selinux"' check_config_script: | ssh default /vagrant/script/check-config.sh unit_tests_script: | @@ -159,6 +159,17 @@ task: echo -e "Host localhost\n\tStrictHostKeyChecking no\t\nIdentityFile /root/.ssh/id_ed25519\n" >> /root/.ssh/config sed -e "s,PermitRootLogin.*,PermitRootLogin prohibit-password,g" -i /etc/ssh/sshd_config systemctl restart sshd + + # Disable the dmz-vs-selinux workaround for distros that have container-selinux >= 2.224.0. + case $DISTRO in + # TODO: remove centos-stream-8. + centos-7|centos-stream-8) + # Do nothing. + ;; + *) + echo 'export EXTRA_BUILDTAGS=runc_dmz_selinux_nocompat' >> /root/.bashrc + ;; + esac host_info_script: | uname -a # ----- diff --git a/README.md b/README.md index f36eea6b6..7e40776bb 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ make BUILDTAGS="" |---------------|---------------------------------------|--------------------|---------------------| | `seccomp` | Syscall filtering using `libseccomp`. | yes | `libseccomp` | | `!runc_nodmz` | Reduce memory usage for CVE-2019-5736 protection by using a small C binary, [see `memfd-bind` for more details][contrib-memfd-bind]. `runc_nodmz` disables this feature and causes runc to use a different protection mechanism which will further increases memory usage temporarily during container startup. This feature can also be disabled at runtime by setting the `RUNC_DMZ=legacy` environment variable. | yes || +| `runc_dmz_selinux_nocompat` | Disables a SELinux DMZ workaround (new distros should set this). See [dmz README] for details. | no || The following build tags were used earlier, but are now obsoleted: - **nokmem** (since runc v1.0.0-rc94 kernel memory settings are ignored) @@ -76,6 +77,7 @@ The following build tags were used earlier, but are now obsoleted: - **selinux** (since runc v1.0.0-rc93 the feature is always enabled) [contrib-memfd-bind]: /contrib/cmd/memfd-bind/README.md + [dmz README]: /libcontainer/dmz/README.md ### Running the test suite diff --git a/Vagrantfile.fedora b/Vagrantfile.fedora index 9b4f6d726..b1e3d6288 100644 --- a/Vagrantfile.fedora +++ b/Vagrantfile.fedora @@ -32,6 +32,9 @@ EOF # To avoid "avc: denied { nosuid_transition }" from SELinux as we run tests on /tmp. mount -o remount,suid /tmp + # Disable selinux-vs-dmz workaround as Fedora doesn't need it. + echo 'export EXTRA_BUILDTAGS=runc_dmz_selinux_nocompat' >> /root/.bashrc + # Prevent the "fatal: unsafe repository" git complain during build. git config --global --add safe.directory /vagrant diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 35f4f5df3..f5d55a164 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -457,6 +457,10 @@ func slicesContains[S ~[]E, E comparable](slice S, needle E) bool { } func isDmzBinarySafe(c *configs.Config) bool { + if !dmz.WorksWithSELinux(c) { + return false + } + // Because we set the dumpable flag in nsexec, the only time when it is // unsafe to use runc-dmz is when the container process would be able to // race against "runc init" and bypass the ptrace_may_access() checks. diff --git a/libcontainer/dmz/README.md b/libcontainer/dmz/README.md index 3cfa913ff..b415f9e39 100644 --- a/libcontainer/dmz/README.md +++ b/libcontainer/dmz/README.md @@ -15,4 +15,16 @@ It also support all the architectures we support in runc. If the GOARCH we use for compiling doesn't support nolibc, it fallbacks to using the C stdlib. +## SELinux compatibility issue and a workaround + +Older SELinux policy can prevent runc to execute the dmz binary. The issue is +fixed in [container-selinux v2.224.0]. Yet, some older distributions may not +have the fix, so runc has a runtime workaround of disabling dmz if it finds +that SELinux is in enforced mode and the container SELinux label is set. + +Distributions that have a sufficiently new container-selinux can disable the +workaround by building runc with the `runc_dmz_selinux_nocompat` build flag, +essentially allowing dmz to be used together with SELinux. + [nolibc-upstream]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/include/nolibc?h=v6.6-rc3 +[container-selinux v2.224.0]: https://github.com/containers/container-selinux/releases/tag/v2.224.0 diff --git a/libcontainer/dmz/selinux.go b/libcontainer/dmz/selinux.go new file mode 100644 index 000000000..49a76ec17 --- /dev/null +++ b/libcontainer/dmz/selinux.go @@ -0,0 +1,10 @@ +//go:build runc_dmz_selinux_nocompat || !linux + +package dmz + +import "github.com/opencontainers/runc/libcontainer/configs" + +// WorksWithSELinux tells whether runc-dmz can work with SELinux. +func WorksWithSELinux(*configs.Config) bool { + return true +} diff --git a/libcontainer/dmz/selinux_compat.go b/libcontainer/dmz/selinux_compat.go new file mode 100644 index 000000000..027a5e762 --- /dev/null +++ b/libcontainer/dmz/selinux_compat.go @@ -0,0 +1,28 @@ +//go:build linux && !runc_dmz_selinux_nocompat + +package dmz + +import ( + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/selinux/go-selinux" +) + +// WorksWithSELinux tells whether runc-dmz can work with SELinux. +// +// Older SELinux policy can prevent runc to execute the dmz binary. The issue is +// fixed in container-selinux >= 2.224.0: +// +// - https://github.com/containers/container-selinux/issues/274 +// - https://github.com/containers/container-selinux/pull/280 +// +// Alas, there is is no easy way to do a runtime check if dmz works with +// SELinux, so the below workaround is enabled by default. It results in +// disabling dmz in case container SELinux label is set and the selinux is in +// enforced mode. +// +// Newer distributions that have the sufficiently new container-selinux version +// can build runc with runc_dmz_selinux_nocompat build flag to disable this +// workaround (essentially allowing dmz to be used together with SELinux). +func WorksWithSELinux(c *configs.Config) bool { + return c.ProcessLabel == "" || selinux.EnforceMode() != selinux.Enforcing +}