summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Wilson <ryantimwilson@meta.com>2024-12-02 17:10:05 +0100
committerRyan Wilson <ryantimwilson@meta.com>2024-12-06 22:34:04 +0100
commitcf48bde7aea52b18ac3fa218d3f60fd3d533ef66 (patch)
tree7479fa8984a86fd9d519445d54ff6b174274c82d
parentcore: Migrate ProtectHostname to use enum vs boolean (diff)
downloadsystemd-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.xml13
-rw-r--r--mkosi.conf1
-rw-r--r--src/core/exec-invoke.c19
-rw-r--r--src/core/namespace.c1
-rw-r--r--src/core/namespace.h1
-rwxr-xr-xtest/units/TEST-07-PID1.protect-hostname.sh44
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