diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
index d196f4767ce..2ab21f06079 100644
--- a/man/org.freedesktop.systemd1.xml
+++ b/man/org.freedesktop.systemd1.xml
@@ -3359,7 +3359,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -4885,8 +4885,9 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
ProtectHostnameEx implement the destination parameter of the
unit file setting ProtectHostname= listed in
systemd.exec5.
- Unlike boolean ProtectHostname, ProtectHostnameEx
- is a string type.
+ Unlike boolean ProtectHostname, ProtectHostnameEx is a pair of
+ strings, the first one is a boolean string or special value private, and the second
+ one is an optional private hostname that will be set in a new UTS namespace for the unit.
@@ -5552,7 +5553,7 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -7561,7 +7562,7 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
@@ -9537,7 +9538,7 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b ProtectHostname = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
- readonly s ProtectHostnameEx = '...';
+ readonly (ss) ProtectHostnameEx = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b MemoryKSM = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 0413ac9025b..fe84be0b851 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -2062,11 +2062,13 @@ BindReadOnlyPaths=/var/lib/systemd
ProtectHostname=
- Takes a boolean argument or private. If enabled, sets up a new UTS namespace
- for the executed processes. If set to a true value, changing hostname or domainname via
- sethostname() and setdomainname() system calls is prevented. If set to
- private, changing hostname or domainname is allowed but only affects the unit's UTS namespace.
- Defaults to off.
+ Takes a boolean argument or private. If enabled, sets up a new UTS
+ namespace for the executed processes. If enabled, a hostname can be optionally specified following a
+ colon (e.g. yes:foo or private:host.example.com), and the
+ hostname is set in the new UTS namespace for the unit. If set to a true value, changing hostname or
+ domainname via sethostname() and setdomainname() system
+ calls is prevented. If set to private, changing hostname or domainname is allowed
+ but only affects the unit's UTS namespace. Defaults to off.
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
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index bfd6694683c..5ac08c996f1 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -13,6 +13,7 @@
#include "creds-util.h"
#include "dbus-execute.h"
#include "dbus-util.h"
+#include "dns-domain.h"
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
@@ -21,6 +22,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "hexdecoct.h"
+#include "hostname-util.h"
#include "iovec-util.h"
#include "ioprio-util.h"
#include "journal-file.h"
@@ -64,7 +66,6 @@ static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_tmp_ex, "s", PrivateTmp,
static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_users_ex, "s", PrivateUsers, private_users_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_protect_control_groups_ex, "s", ProtectControlGroups, protect_control_groups_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_pids, "s", PrivatePIDs, private_pids_to_string);
-static BUS_DEFINE_PROPERTY_GET_REF(property_get_protect_hostname_ex, "s", ProtectHostname, protect_hostname_to_string);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_level, "i", int, LOG_PRI);
static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_facility, "i", int, LOG_FAC);
static BUS_DEFINE_PROPERTY_GET(property_get_cpu_affinity_from_numa, "b", ExecContext, exec_context_get_cpu_affinity_from_numa);
@@ -1084,6 +1085,20 @@ static int property_get_protect_hostname(
return sd_bus_message_append_basic(reply, 'b', &b);
}
+static int property_get_protect_hostname_ex(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecContext *c = ASSERT_PTR(userdata);
+
+ return sd_bus_message_append(reply, "(ss)", protect_hostname_to_string(c->protect_hostname), c->private_hostname);
+}
+
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1259,7 +1274,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("ProtectProc", "s", property_get_protect_proc, offsetof(ExecContext, protect_proc), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProcSubset", "s", property_get_proc_subset, offsetof(ExecContext, proc_subset), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ProtectHostname", "b", property_get_protect_hostname, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("ProtectHostnameEx", "s", property_get_protect_hostname_ex, offsetof(ExecContext, protect_hostname), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ProtectHostnameEx", "(ss)", property_get_protect_hostname_ex, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MemoryKSM", "b", bus_property_get_tristate, offsetof(ExecContext, memory_ksm), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2027,21 +2042,29 @@ int bus_exec_context_set_transient_property(
}
if (streq(name, "ProtectHostnameEx")) {
- const char *s;
- ProtectHostname t;
+ const char *s, *h = NULL;
- r = sd_bus_message_read(message, "s", &s);
+ r = sd_bus_message_read(message, "(ss)", &s, &h);
if (r < 0)
return r;
- t = protect_hostname_from_string(s);
- if (t < 0)
+ if (!isempty(h) && !hostname_is_valid(h, /* flags = */ 0))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname in %s setting: %s", name, h);
+
+ ProtectHostname t = protect_hostname_from_string(s);
+ if (t < 0 || (t == PROTECT_HOSTNAME_NO && !isempty(h)))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s setting: %s", name, s);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->protect_hostname = t;
- (void) unit_write_settingf(u, flags, name, "ProtectHostname=%s",
- protect_hostname_to_string(c->protect_hostname));
+ r = free_and_strdup(&c->private_hostname, empty_to_null(h));
+ if (r < 0)
+ return r;
+
+ (void) unit_write_settingf(u, flags, name, "ProtectHostname=%s%s%s",
+ protect_hostname_to_string(c->protect_hostname),
+ c->private_hostname ? ":" : "",
+ strempty(c->private_hostname));
}
return 1;
diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c
index fe1ee884dae..ea21d6d42ba 100644
--- a/src/core/exec-invoke.c
+++ b/src/core/exec-invoke.c
@@ -40,6 +40,7 @@
#include "exit-status.h"
#include "fd-util.h"
#include "hexdecoct.h"
+#include "hostname-setup.h"
#include "io-util.h"
#include "iovec-util.h"
#include "journal-send.h"
@@ -1698,6 +1699,8 @@ static int apply_restrict_filesystems(const ExecContext *c, const ExecParameters
#endif
static int apply_protect_hostname(const ExecContext *c, const ExecParameters *p, int *ret_exit_status) {
+ int r;
+
assert(c);
assert(p);
@@ -1714,6 +1717,13 @@ static int apply_protect_hostname(const ExecContext *c, const ExecParameters *p,
log_exec_warning(c, p,
"ProtectHostname=%s is configured, but UTS namespace setup is prohibited (container manager?), ignoring namespace setup.",
protect_hostname_to_string(c->protect_hostname));
+
+ } else if (c->private_hostname) {
+ r = sethostname_idempotent(c->private_hostname);
+ if (r < 0) {
+ *ret_exit_status = EXIT_NAMESPACE;
+ return log_exec_error_errno(c, p, r, "Failed to set private hostname '%s': %m", c->private_hostname);
+ }
}
} else
log_exec_warning(c, p,
@@ -1722,8 +1732,6 @@ static int apply_protect_hostname(const ExecContext *c, const ExecParameters *p,
#if HAVE_SECCOMP
if (c->protect_hostname == PROTECT_HOSTNAME_YES) {
- int r;
-
if (skip_seccomp_unavailable(c, p, "ProtectHostname="))
return 0;
diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c
index 9dce5a9c258..f05c69bf2c5 100644
--- a/src/core/execute-serialize.c
+++ b/src/core/execute-serialize.c
@@ -1982,6 +1982,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
if (r < 0)
return r;
+ r = serialize_item(f, "exec-context-private-hostname", c->private_hostname);
+ if (r < 0)
+ return r;
+
r = serialize_item(f, "exec-context-protect-proc", protect_proc_to_string(c->protect_proc));
if (r < 0)
return r;
@@ -2884,6 +2888,10 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
c->protect_hostname = protect_hostname_from_string(val);
if (c->protect_hostname < 0)
return -EINVAL;
+ } else if ((val = startswith(l, "exec-context-private-hostname="))) {
+ r = free_and_strdup(&c->private_hostname, val);
+ if (r < 0)
+ return r;
} else if ((val = startswith(l, "exec-context-protect-proc="))) {
c->protect_proc = protect_proc_from_string(val);
if (c->protect_proc < 0)
diff --git a/src/core/execute.c b/src/core/execute.c
index a01096a7000..3c8d4c2be12 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -723,6 +723,8 @@ void exec_context_done(ExecContext *c) {
c->root_image_policy = image_policy_free(c->root_image_policy);
c->mount_image_policy = image_policy_free(c->mount_image_policy);
c->extension_image_policy = image_policy_free(c->extension_image_policy);
+
+ c->private_hostname = mfree(c->private_hostname);
}
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
@@ -1066,7 +1068,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
"%sRestrictRealtime: %s\n"
"%sRestrictSUIDSGID: %s\n"
"%sKeyringMode: %s\n"
- "%sProtectHostname: %s\n"
+ "%sProtectHostname: %s%s%s\n"
"%sProtectProc: %s\n"
"%sProcSubset: %s\n",
prefix, c->umask,
@@ -1093,7 +1095,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
prefix, yes_no(c->restrict_realtime),
prefix, yes_no(c->restrict_suid_sgid),
prefix, exec_keyring_mode_to_string(c->keyring_mode),
- prefix, protect_hostname_to_string(c->protect_hostname),
+ prefix, protect_hostname_to_string(c->protect_hostname), c->private_hostname ? ":" : "", strempty(c->private_hostname),
prefix, protect_proc_to_string(c->protect_proc),
prefix, proc_subset_to_string(c->proc_subset));
diff --git a/src/core/execute.h b/src/core/execute.h
index f1b94e7f4c0..6421f19cc44 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -337,6 +337,7 @@ struct ExecContext {
ProtectHome protect_home;
PrivatePIDs private_pids;
ProtectHostname protect_hostname;
+ char *private_hostname;
bool dynamic_user;
bool remove_ipc;
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 90290a8b0ee..04a560110e6 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -180,7 +180,7 @@
{% else %}
{{type}}.SmackProcessLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0
{% endif %}
-{{type}}.ProtectHostname, config_parse_protect_hostname, 0, offsetof({{type}}, exec_context.protect_hostname)
+{{type}}.ProtectHostname, config_parse_protect_hostname, 0, offsetof({{type}}, exec_context)
{{type}}.MemoryKSM, config_parse_tristate, 0, offsetof({{type}}, exec_context.memory_ksm)
{%- endmacro -%}
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index a108216a960..eca3ee6872b 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -40,6 +40,7 @@
#include "fs-util.h"
#include "fstab-util.h"
#include "hexdecoct.h"
+#include "hostname-util.h"
#include "iovec-util.h"
#include "ioprio-util.h"
#include "ip-protocol-list.h"
@@ -141,7 +142,6 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMo
DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess);
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_home, protect_home, ProtectHome);
-DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_hostname, protect_hostname, ProtectHostname);
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_system, protect_system, ProtectSystem);
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_preserve_mode, exec_preserve_mode, ExecPreserveMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType);
@@ -6743,3 +6743,53 @@ int config_parse_cgroup_nft_set(
return config_parse_nft_set(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->nft_set_context, u);
}
+
+int config_parse_protect_hostname(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ ExecContext *c = ASSERT_PTR(data);
+ Unit *u = ASSERT_PTR(userdata);
+ _cleanup_free_ char *h = NULL, *p = NULL;
+ int r;
+
+ if (isempty(rvalue)) {
+ c->protect_hostname = PROTECT_HOSTNAME_NO;
+ c->private_hostname = mfree(c->private_hostname);
+ return 1;
+ }
+
+ const char *colon = strchr(rvalue, ':');
+ if (colon) {
+ r = unit_full_printf_full(u, colon + 1, HOST_NAME_MAX, &h);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve unit specifiers in '%s', ignoring: %m", colon + 1);
+ return 0;
+ }
+
+ if (!hostname_is_valid(h, /* flags = */ 0))
+ return log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid hostname is specified to %s=, ignoring: %s", lvalue, h);
+
+ p = strndup(rvalue, colon - rvalue);
+ if (!p)
+ return log_oom();
+ }
+
+ ProtectHostname t = protect_hostname_from_string(p ?: rvalue);
+ if (t < 0 || (t == PROTECT_HOSTNAME_NO && h))
+ return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue);
+
+ c->protect_hostname = t;
+ free_and_replace(c->private_hostname, h);
+ return 1;
+}
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 4e623036d03..a499c0e9845 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -1045,7 +1045,6 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"SyslogIdentifier",
"ProtectSystem",
"ProtectHome",
- "ProtectHostnameEx",
"PrivateTmpEx",
"PrivateUsersEx",
"ProtectControlGroupsEx",
@@ -2269,6 +2268,24 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
return 1;
}
+ if (streq(field, "ProtectHostnameEx")) {
+ const char *colon = strchr(eq, ':');
+ if (colon) {
+ if (isempty(colon + 1))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %s=%s", field, eq);
+
+ _cleanup_free_ char *p = strndup(eq, colon - eq);
+ if (!p)
+ return -ENOMEM;
+
+ r = sd_bus_message_append(m, "(sv)", field, "(ss)", p, colon + 1);
+ } else
+ r = sd_bus_message_append(m, "(sv)", field, "(ss)", eq, NULL);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 1;
+ }
return 0;
}
diff --git a/test/units/TEST-07-PID1.protect-hostname.sh b/test/units/TEST-07-PID1.protect-hostname.sh
index c2ede395535..10d448b80de 100755
--- a/test/units/TEST-07-PID1.protect-hostname.sh
+++ b/test/units/TEST-07-PID1.protect-hostname.sh
@@ -21,6 +21,33 @@ testcase_yes() {
# can only set hostname.
(! systemd-run --wait -p ProtectHostname=yes hostname foo)
+ # ProtectHostname=yes can optionally take a hostname.
+ systemd-run --wait -p ProtectHostnameEx=yes:hoge \
+ -P bash -xec '
+ test "$(hostname)" = "hoge"
+ (! hostname foo)
+ test "$(hostname)" = "hoge"
+ '
+
+ # Verify host hostname is unchanged.
+ test "$(hostname)" = "$LEGACY_HOSTNAME"
+ test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
+
+ # ProtectHostname= supportes specifiers.
+ mkdir -p /run/systemd/system/
+ cat >/run/systemd/system/test-protect-hostname-yes@.service </run/systemd/system/test-protect-hostname-private@.service <