diff options
author | Ryan Wilson <ryantimwilson@meta.com> | 2024-12-02 17:10:05 +0100 |
---|---|---|
committer | Ryan Wilson <ryantimwilson@meta.com> | 2024-12-06 22:34:04 +0100 |
commit | cf48bde7aea52b18ac3fa218d3f60fd3d533ef66 (patch) | |
tree | 7479fa8984a86fd9d519445d54ff6b174274c82d | |
parent | core: Migrate ProtectHostname to use enum vs boolean (diff) | |
download | systemd-cf48bde7aea52b18ac3fa218d3f60fd3d533ef66.tar.xz systemd-cf48bde7aea52b18ac3fa218d3f60fd3d533ef66.zip |
core: Add ProtectHostname=private
This allows an option for systemd exec units to enable UTS namespaces
but not restrict changing hostname via seccomp. Thus, units can change
hostname without affecting the host.
Fixes: #30348
-rw-r--r-- | man/systemd.exec.xml | 13 | ||||
-rw-r--r-- | mkosi.conf | 1 | ||||
-rw-r--r-- | src/core/exec-invoke.c | 19 | ||||
-rw-r--r-- | src/core/namespace.c | 1 | ||||
-rw-r--r-- | src/core/namespace.h | 1 | ||||
-rwxr-xr-x | test/units/TEST-07-PID1.protect-hostname.sh | 44 |
6 files changed, 70 insertions, 9 deletions
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 14075cb4e7..44ee2022dd 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -2055,8 +2055,11 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting> <varlistentry> <term><varname>ProtectHostname=</varname></term> - <listitem><para>Takes a boolean argument. When set, sets up a new UTS namespace for the executed - processes. In addition, changing hostname or domainname is prevented. Defaults to off.</para> + <listitem><para>Takes a boolean argument or <literal>private</literal>. If enabled, sets up a new UTS namespace + for the executed processes. If set to a true value, changing hostname or domainname via + <function>sethostname()</function> and <function>setdomainname()</function> system calls is prevented. If set to + <literal>private</literal>, changing hostname or domainname is allowed but only affects the unit's UTS namespace. + Defaults to off.</para> <para>Note that the implementation of this setting might be impossible (for example if UTS namespaces are not available), and the unit should be written in a way that does not solely rely on this setting @@ -2066,6 +2069,12 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting> the system into the service, it is hence not suitable for services that need to take notice of system hostname changes dynamically.</para> + <para>Note that this option does not prevent changing system hostname via <command>hostnamectl</command>. + However, <varname>User=</varname> and <varname>Group=</varname> may be used to run as an unprivileged user + to disallow changing system hostname. See <function>SetHostname()</function> in + <citerefentry project="man-pages"><refentrytitle>org.freedesktop.hostname1</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for more details.</para> + <xi:include href="system-or-user-ns.xml" xpointer="singular"/> <xi:include href="version-info.xml" xpointer="v242"/></listitem> diff --git a/mkosi.conf b/mkosi.conf index 35a19a27aa..535e2bd79b 100644 --- a/mkosi.conf +++ b/mkosi.conf @@ -101,6 +101,7 @@ Packages= gdb grep gzip + hostname jq kbd kexec-tools diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index f4aacb55b2..fd306f1143 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1726,15 +1726,17 @@ static int apply_protect_hostname(const ExecContext *c, const ExecParameters *p, "support UTS namespaces, ignoring namespace setup."); #if HAVE_SECCOMP - int r; + if (c->protect_hostname == PROTECT_HOSTNAME_YES) { + int r; - if (skip_seccomp_unavailable(c, p, "ProtectHostname=")) - return 0; + if (skip_seccomp_unavailable(c, p, "ProtectHostname=")) + return 0; - r = seccomp_protect_hostname(); - if (r < 0) { - *ret_exit_status = EXIT_SECCOMP; - return log_exec_error_errno(c, p, r, "Failed to apply hostname restrictions: %m"); + r = seccomp_protect_hostname(); + if (r < 0) { + *ret_exit_status = EXIT_SECCOMP; + return log_exec_error_errno(c, p, r, "Failed to apply hostname restrictions: %m"); + } } #endif @@ -3417,6 +3419,9 @@ static int apply_mount_namespace( .protect_kernel_tunables = needs_sandboxing && context->protect_kernel_tunables, .protect_kernel_modules = needs_sandboxing && context->protect_kernel_modules, .protect_kernel_logs = needs_sandboxing && context->protect_kernel_logs, + /* Only mount /proc/sys/kernel/hostname and domainname read-only if ProtectHostname=yes. Otherwise, ProtectHostname=no + * allows changing hostname for the host and ProtectHostname=private allows changing the hostname in the unit's UTS + * namespace. */ .protect_hostname = needs_sandboxing && context->protect_hostname == PROTECT_HOSTNAME_YES, .private_dev = needs_sandboxing && context->private_devices, diff --git a/src/core/namespace.c b/src/core/namespace.c index c327c9a3ca..2f3b8f03d1 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3308,6 +3308,7 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_home, ProtectHome, PROTECT_HOME_ static const char *const protect_hostname_table[_PROTECT_HOSTNAME_MAX] = { [PROTECT_HOSTNAME_NO] = "no", [PROTECT_HOSTNAME_YES] = "yes", + [PROTECT_HOSTNAME_PRIVATE] = "private", }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(protect_hostname, ProtectHostname, PROTECT_HOSTNAME_YES); diff --git a/src/core/namespace.h b/src/core/namespace.h index 8df91e3bdf..96f62be30a 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -31,6 +31,7 @@ typedef enum ProtectHome { typedef enum ProtectHostname { PROTECT_HOSTNAME_NO, PROTECT_HOSTNAME_YES, + PROTECT_HOSTNAME_PRIVATE, _PROTECT_HOSTNAME_MAX, _PROTECT_HOSTNAME_INVALID = -EINVAL, } ProtectHostname; diff --git a/test/units/TEST-07-PID1.protect-hostname.sh b/test/units/TEST-07-PID1.protect-hostname.sh new file mode 100755 index 0000000000..c2ede39553 --- /dev/null +++ b/test/units/TEST-07-PID1.protect-hostname.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +LEGACY_HOSTNAME="$(hostname)" +HOSTNAME_FROM_SYSTEMD="$(hostnamectl hostname)" + +testcase_yes() { + # hostnamectl calls SetHostname method via dbus socket which executes in homenamed + # in the init namespace. So hostnamectl is not affected by ProtectHostname=yes or + # private since sethostname() system call is executed in the init namespace. + # + # hostnamed does authentication based on UID via polkit so this guarantees admins + # can only set hostname. + (! systemd-run --wait -p ProtectHostname=yes hostname foo) + + systemd-run --wait -p ProtectHostname=yes -p PrivateMounts=yes \ + findmnt --mountpoint /proc/sys/kernel/hostname +} + +testcase_private() { + systemd-run --wait -p ProtectHostnameEx=private \ + -P bash -xec ' + hostname foo + test "$(hostname)" = "foo" + ' + + # Verify host hostname is unchanged. + test "$(hostname)" = "$LEGACY_HOSTNAME" + test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD" + + # Verify /proc/sys/kernel/hostname is not bind mounted from host read-only. + (! systemd-run --wait -p ProtectHostnameEx=private -p PrivateMounts=yes \ + findmnt --mountpoint /proc/sys/kernel/hostname) +} + +run_testcases |