1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-03 05:18:09 +03:00

core: make ProtectHostname= optionally take a hostname

Closes #35623.
This commit is contained in:
Yu Watanabe 2024-12-15 10:36:42 +09:00
parent 0d298a771a
commit e76fcd0e40
11 changed files with 208 additions and 27 deletions

View File

@ -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 {
<para><varname>ProtectHostnameEx</varname> implement the destination parameter of the
unit file setting <varname>ProtectHostname=</varname> listed in
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Unlike boolean <varname>ProtectHostname</varname>, <varname>ProtectHostnameEx</varname>
is a string type.</para>
Unlike boolean <varname>ProtectHostname</varname>, <varname>ProtectHostnameEx</varname> is a pair of
strings, the first one is a boolean string or special value <literal>private</literal>, and the second
one is an optional private hostname that will be set in a new UTS namespace for the unit.</para>
</refsect2>
</refsect1>
@ -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")

View File

@ -2062,11 +2062,13 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
<varlistentry>
<term><varname>ProtectHostname=</varname></term>
<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>
<listitem><para>Takes a boolean argument or <literal>private</literal>. 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. <literal>yes:foo</literal> or <literal>private:host.example.com</literal>), and the
hostname is set in the new UTS namespace for the unit. 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

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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));

View File

@ -337,6 +337,7 @@ struct ExecContext {
ProtectHome protect_home;
PrivatePIDs private_pids;
ProtectHostname protect_hostname;
char *private_hostname;
bool dynamic_user;
bool remove_ipc;

View File

@ -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 -%}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <<EOF
[Service]
Type=oneshot
ExecStart=bash -xec 'test "\$\$(hostname)" = "%i"; (! hostname foo); test "\$\$(hostname)" = "%i"'
ProtectHostname=yes:%i
EOF
systemctl daemon-reload
systemctl start --wait test-protect-hostname-yes@hoge.example.com.service
# Verify host hostname is unchanged.
test "$(hostname)" = "$LEGACY_HOSTNAME"
test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
systemd-run --wait -p ProtectHostname=yes -p PrivateMounts=yes \
findmnt --mountpoint /proc/sys/kernel/hostname
}
@ -36,9 +63,51 @@ testcase_private() {
test "$(hostname)" = "$LEGACY_HOSTNAME"
test "$(hostnamectl hostname)" = "$HOSTNAME_FROM_SYSTEMD"
# ProtectHostname=private can optionally take a hostname.
systemd-run --wait -p ProtectHostnameEx=private:hoge \
-P bash -xec '
test "$(hostname)" = "hoge"
hostname foo
test "$(hostname)" = "foo"
'
# 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-private@.service <<EOF
[Service]
Type=oneshot
ExecStart=bash -xec 'test "\$\$(hostname)" = "%i"; hostname foo; test "\$\$(hostname)" = "foo"'
ProtectHostname=private:%i
EOF
systemctl daemon-reload
systemctl start --wait test-protect-hostname-private@hoge.example.com.service
# 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)
}
testcase_invalid() {
# ProtectHostname=no cannot take hostname.
(! systemd-run --wait -p ProtectHostnameEx=no:hoge true)
# Invalid hostname.
(! systemd-run --wait -p ProtectHostnameEx=yes: true)
(! systemd-run --wait -p ProtectHostnameEx=yes:.foo true)
(! systemd-run --wait -p ProtectHostnameEx=yes:foo.-example.com true)
(! systemd-run --wait -p ProtectHostnameEx=yes:foo..example.com true)
(! systemd-run --wait -p ProtectHostnameEx=private: true)
(! systemd-run --wait -p ProtectHostnameEx=private:.foo true)
(! systemd-run --wait -p ProtectHostnameEx=private:foo.-example.com true)
(! systemd-run --wait -p ProtectHostnameEx=private:foo..example.com true)
}
run_testcases