mirror of
https://github.com/systemd/systemd.git
synced 2025-09-05 01:44:45 +03:00
Merge pull request #27438 from bluca/dump_ratelimit
manager: restrict Dump*() to privileged callers or ratelimit
This commit is contained in:
@@ -1403,7 +1403,8 @@ node /org/freedesktop/systemd1 {
|
|||||||
<function>DumpByFileDescriptor()</function>/<function>DumpUnitsMatchingPatternsByFileDescriptor()</function>
|
<function>DumpByFileDescriptor()</function>/<function>DumpUnitsMatchingPatternsByFileDescriptor()</function>
|
||||||
are usually the preferred interface, since it ensures the data can be passed reliably from the service
|
are usually the preferred interface, since it ensures the data can be passed reliably from the service
|
||||||
manager to the client. Note though that they cannot work when communicating with the service manager
|
manager to the client. Note though that they cannot work when communicating with the service manager
|
||||||
remotely, as file descriptors are strictly local to a system.</para>
|
remotely, as file descriptors are strictly local to a system. All the <function>Dump*()</function>
|
||||||
|
methods are rate limited for unprivileged users.</para>
|
||||||
|
|
||||||
<para><function>Reload()</function> may be invoked to reload all unit files.</para>
|
<para><function>Reload()</function> may be invoked to reload all unit files.</para>
|
||||||
|
|
||||||
@@ -1778,7 +1779,9 @@ node /org/freedesktop/systemd1 {
|
|||||||
<function>UnsetAndSetEnvironment()</function>) require
|
<function>UnsetAndSetEnvironment()</function>) require
|
||||||
<interfacename>org.freedesktop.systemd1.set-environment</interfacename>. <function>Reload()</function>
|
<interfacename>org.freedesktop.systemd1.set-environment</interfacename>. <function>Reload()</function>
|
||||||
and <function>Reexecute()</function> require
|
and <function>Reexecute()</function> require
|
||||||
<interfacename>org.freedesktop.systemd1.reload-daemon</interfacename>.
|
<interfacename>org.freedesktop.systemd1.reload-daemon</interfacename>. Operations which dump internal
|
||||||
|
state require <interfacename>org.freedesktop.systemd1.bypass-dump-ratelimit</interfacename> to avoid
|
||||||
|
rate limits.
|
||||||
</para>
|
</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
@@ -278,7 +278,7 @@ multi-user.target @47.820s
|
|||||||
<para>Without any parameter, this command outputs a (usually very long) human-readable serialization of
|
<para>Without any parameter, this command outputs a (usually very long) human-readable serialization of
|
||||||
the complete service manager state. Optional glob pattern may be specified, causing the output to be
|
the complete service manager state. Optional glob pattern may be specified, causing the output to be
|
||||||
limited to units whose names match one of the patterns. The output format is subject to change without
|
limited to units whose names match one of the patterns. The output format is subject to change without
|
||||||
notice and should not be parsed by applications.</para>
|
notice and should not be parsed by applications. This command is rate limited for unprivileged users.</para>
|
||||||
|
|
||||||
<example>
|
<example>
|
||||||
<title>Show the internal state of user manager</title>
|
<title>Show the internal state of user manager</title>
|
||||||
|
@@ -49,3 +49,12 @@ usec_t ratelimit_end(const RateLimit *rl) {
|
|||||||
|
|
||||||
return usec_add(rl->begin, rl->interval);
|
return usec_add(rl->begin, rl->interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usec_t ratelimit_left(const RateLimit *rl) {
|
||||||
|
assert(rl);
|
||||||
|
|
||||||
|
if (rl->begin == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return usec_sub_unsigned(ratelimit_end(rl), now(CLOCK_MONOTONIC));
|
||||||
|
}
|
||||||
|
@@ -25,3 +25,4 @@ bool ratelimit_below(RateLimit *r);
|
|||||||
unsigned ratelimit_num_dropped(RateLimit *r);
|
unsigned ratelimit_num_dropped(RateLimit *r);
|
||||||
|
|
||||||
usec_t ratelimit_end(const RateLimit *rl);
|
usec_t ratelimit_end(const RateLimit *rl);
|
||||||
|
usec_t ratelimit_left(const RateLimit *rl);
|
||||||
|
@@ -1413,17 +1413,47 @@ static int dump_impl(
|
|||||||
|
|
||||||
assert(message);
|
assert(message);
|
||||||
|
|
||||||
/* Anyone can call this method */
|
/* 'status' access is the bare minimum always needed for this, as the policy might straight out
|
||||||
|
* forbid a client from querying any information from systemd, regardless of any rate limiting. */
|
||||||
r = mac_selinux_access_check(message, "status", error);
|
r = mac_selinux_access_check(message, "status", error);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
/* Rate limit reached? Check if the caller is privileged/allowed by policy to bypass this. We
|
||||||
|
* check the rate limit first to avoid the expensive roundtrip to polkit when not needed. */
|
||||||
|
if (!ratelimit_below(&m->dump_ratelimit)) {
|
||||||
|
/* We need a way for SELinux to constrain the operation when the rate limit is active, even
|
||||||
|
* if polkit would allow it, but we cannot easily add new named permissions, so we need to
|
||||||
|
* use an existing one. Reload/reexec are also slow but non-destructive/modifying
|
||||||
|
* operations, and can cause PID1 to stall. So it seems similar enough in terms of security
|
||||||
|
* considerations and impact, and thus use the same access check for dumps which, given the
|
||||||
|
* large amount of data to fetch, can stall PID1 for quite some time. */
|
||||||
|
r = mac_selinux_access_check(message, "reload", error);
|
||||||
|
if (r < 0)
|
||||||
|
goto ratelimited;
|
||||||
|
|
||||||
|
r = bus_verify_bypass_dump_ratelimit_async(m, message, error);
|
||||||
|
if (r < 0)
|
||||||
|
goto ratelimited;
|
||||||
|
if (r == 0)
|
||||||
|
/* No authorization for now, but the async polkit stuff will call us again when it
|
||||||
|
* has it */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
r = manager_get_dump_string(m, patterns, &dump);
|
r = manager_get_dump_string(m, patterns, &dump);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
return reply(message, dump);
|
return reply(message, dump);
|
||||||
|
|
||||||
|
ratelimited:
|
||||||
|
log_warning("Dump request rejected due to rate limit on unprivileged callers, blocked for %s.",
|
||||||
|
FORMAT_TIMESPAN(ratelimit_left(&m->dump_ratelimit), USEC_PER_SEC));
|
||||||
|
return sd_bus_error_setf(error,
|
||||||
|
SD_BUS_ERROR_LIMITS_EXCEEDED,
|
||||||
|
"Dump request rejected due to rate limit on unprivileged callers, blocked for %s.",
|
||||||
|
FORMAT_TIMESPAN(ratelimit_left(&m->dump_ratelimit), USEC_PER_SEC));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reply_dump(sd_bus_message *message, char *dump) {
|
static int reply_dump(sd_bus_message *message, char *dump) {
|
||||||
|
@@ -1203,6 +1203,9 @@ int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_erro
|
|||||||
int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) {
|
int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) {
|
||||||
return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error);
|
return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error);
|
||||||
}
|
}
|
||||||
|
int bus_verify_bypass_dump_ratelimit_async(Manager *m, sd_bus_message *call, sd_bus_error *error) {
|
||||||
|
return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.bypass-dump-ratelimit", NULL, false, UID_INVALID, &m->polkit_registry, error);
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t manager_bus_n_queued_write(Manager *m) {
|
uint64_t manager_bus_n_queued_write(Manager *m) {
|
||||||
uint64_t c = 0;
|
uint64_t c = 0;
|
||||||
|
@@ -27,6 +27,7 @@ int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error
|
|||||||
int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
||||||
int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
||||||
int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
||||||
|
int bus_verify_bypass_dump_ratelimit_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
||||||
|
|
||||||
int bus_forward_agent_released(Manager *m, const char *path);
|
int bus_forward_agent_released(Manager *m, const char *path);
|
||||||
|
|
||||||
|
@@ -165,6 +165,14 @@ int manager_serialize(
|
|||||||
(void) serialize_item_format(f, "user-lookup", "%i %i", copy0, copy1);
|
(void) serialize_item_format(f, "user-lookup", "%i %i", copy0, copy1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(void) serialize_item_format(f,
|
||||||
|
"dump-ratelimit",
|
||||||
|
USEC_FMT " " USEC_FMT " %u %u",
|
||||||
|
m->dump_ratelimit.begin,
|
||||||
|
m->dump_ratelimit.interval,
|
||||||
|
m->dump_ratelimit.num,
|
||||||
|
m->dump_ratelimit.burst);
|
||||||
|
|
||||||
bus_track_serialize(m->subscribed, f, "subscribed");
|
bus_track_serialize(m->subscribed, f, "subscribed");
|
||||||
|
|
||||||
r = dynamic_user_serialize(m, f, fds);
|
r = dynamic_user_serialize(m, f, fds);
|
||||||
@@ -550,6 +558,21 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
|
|||||||
* remains set until all serialized contents are handled. */
|
* remains set until all serialized contents are handled. */
|
||||||
if (deserialize_varlink_sockets)
|
if (deserialize_varlink_sockets)
|
||||||
(void) varlink_server_deserialize_one(m->varlink_server, val, fds);
|
(void) varlink_server_deserialize_one(m->varlink_server, val, fds);
|
||||||
|
} else if ((val = startswith(l, "dump-ratelimit="))) {
|
||||||
|
usec_t begin, interval;
|
||||||
|
unsigned num, burst;
|
||||||
|
|
||||||
|
if (sscanf(val, USEC_FMT " " USEC_FMT " %u %u", &begin, &interval, &num, &burst) != 4)
|
||||||
|
log_notice("Failed to parse dump ratelimit, ignoring: %s", val);
|
||||||
|
else {
|
||||||
|
/* If we changed the values across versions, flush the counter */
|
||||||
|
if (interval != m->dump_ratelimit.interval || burst != m->dump_ratelimit.burst)
|
||||||
|
m->dump_ratelimit.num = 0;
|
||||||
|
else
|
||||||
|
m->dump_ratelimit.num = num;
|
||||||
|
m->dump_ratelimit.begin = begin;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
ManagerTimestamp q;
|
ManagerTimestamp q;
|
||||||
|
|
||||||
|
@@ -913,6 +913,11 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags,
|
|||||||
|
|
||||||
.default_memory_pressure_watch = CGROUP_PRESSURE_WATCH_AUTO,
|
.default_memory_pressure_watch = CGROUP_PRESSURE_WATCH_AUTO,
|
||||||
.default_memory_pressure_threshold_usec = USEC_INFINITY,
|
.default_memory_pressure_threshold_usec = USEC_INFINITY,
|
||||||
|
|
||||||
|
.dump_ratelimit = {
|
||||||
|
.interval = 10 * USEC_PER_MINUTE,
|
||||||
|
.burst = 10,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#if ENABLE_EFI
|
#if ENABLE_EFI
|
||||||
|
@@ -471,6 +471,8 @@ struct Manager {
|
|||||||
|
|
||||||
/* Allow users to configure a rate limit for Reload() operations */
|
/* Allow users to configure a rate limit for Reload() operations */
|
||||||
RateLimit reload_ratelimit;
|
RateLimit reload_ratelimit;
|
||||||
|
/* Dump*() are slow, so always rate limit them to 10 per 10 minutes */
|
||||||
|
RateLimit dump_ratelimit;
|
||||||
|
|
||||||
sd_event_source *memory_pressure_event_source;
|
sd_event_source *memory_pressure_event_source;
|
||||||
};
|
};
|
||||||
|
@@ -70,4 +70,14 @@
|
|||||||
</defaults>
|
</defaults>
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
<action id="org.freedesktop.systemd1.bypass-dump-ratelimit">
|
||||||
|
<description gettext-domain="systemd">Dump the systemd state without rate limits</description>
|
||||||
|
<message gettext-domain="systemd">Authentication is required to dump the systemd state without rate limits.</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin</allow_any>
|
||||||
|
<allow_inactive>auth_admin</allow_inactive>
|
||||||
|
<allow_active>auth_admin_keep</allow_active>
|
||||||
|
</defaults>
|
||||||
|
</action>
|
||||||
|
|
||||||
</policyconfig>
|
</policyconfig>
|
||||||
|
@@ -53,6 +53,21 @@ systemd-analyze dot --require systemd-journald.service systemd-logind.service >/
|
|||||||
systemd-analyze dot "systemd-*.service" >/dev/null
|
systemd-analyze dot "systemd-*.service" >/dev/null
|
||||||
(! systemd-analyze dot systemd-journald.service systemd-logind.service "*" bbb ccc)
|
(! systemd-analyze dot systemd-journald.service systemd-logind.service "*" bbb ccc)
|
||||||
# dump
|
# dump
|
||||||
|
# this should be rate limited to 10 calls in 10 minutes for unprivileged callers
|
||||||
|
for _ in {1..10}; do
|
||||||
|
runas testuser systemd-analyze dump systemd-journald.service >/dev/null
|
||||||
|
done
|
||||||
|
(! runas testuser systemd-analyze dump >/dev/null)
|
||||||
|
# still limited after a reload
|
||||||
|
systemctl daemon-reload
|
||||||
|
(! runas testuser systemd-analyze dump >/dev/null)
|
||||||
|
# and a re-exec
|
||||||
|
systemctl daemon-reexec
|
||||||
|
(! runas testuser systemd-analyze dump >/dev/null)
|
||||||
|
# privileged call, so should not be rate limited
|
||||||
|
for _ in {1..10}; do
|
||||||
|
systemd-analyze dump systemd-journald.service >/dev/null
|
||||||
|
done
|
||||||
systemd-analyze dump >/dev/null
|
systemd-analyze dump >/dev/null
|
||||||
systemd-analyze dump "*" >/dev/null
|
systemd-analyze dump "*" >/dev/null
|
||||||
systemd-analyze dump "*.socket" >/dev/null
|
systemd-analyze dump "*.socket" >/dev/null
|
||||||
|
Reference in New Issue
Block a user