<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
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>
gdb
grep
gzip
+ hostname
jq
kbd
kexec-tools
"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
.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,
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);
typedef enum ProtectHostname {
PROTECT_HOSTNAME_NO,
PROTECT_HOSTNAME_YES,
+ PROTECT_HOSTNAME_PRIVATE,
_PROTECT_HOSTNAME_MAX,
_PROTECT_HOSTNAME_INVALID = -EINVAL,
} ProtectHostname;
--- /dev/null
+#!/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