mirror of
https://github.com/systemd/systemd.git
synced 2024-11-24 02:33:36 +08:00
Merge pull request #27438 from bluca/dump_ratelimit
manager: restrict Dump*() to privileged callers or ratelimit
This commit is contained in:
commit
382e18fd95
@ -1403,7 +1403,8 @@ node /org/freedesktop/systemd1 {
|
||||
<function>DumpByFileDescriptor()</function>/<function>DumpUnitsMatchingPatternsByFileDescriptor()</function>
|
||||
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
|
||||
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>
|
||||
|
||||
@ -1778,7 +1779,9 @@ node /org/freedesktop/systemd1 {
|
||||
<function>UnsetAndSetEnvironment()</function>) require
|
||||
<interfacename>org.freedesktop.systemd1.set-environment</interfacename>. <function>Reload()</function>
|
||||
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>
|
||||
</refsect2>
|
||||
</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
|
||||
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
|
||||
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>
|
||||
<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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
usec_t ratelimit_end(const RateLimit *rl);
|
||||
usec_t ratelimit_left(const RateLimit *rl);
|
||||
|
@ -1413,17 +1413,47 @@ static int dump_impl(
|
||||
|
||||
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);
|
||||
if (r < 0)
|
||||
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);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
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) {
|
||||
|
@ -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) {
|
||||
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 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_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_bypass_dump_ratelimit_async(Manager *m, sd_bus_message *call, sd_bus_error *error);
|
||||
|
||||
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,
|
||||
"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");
|
||||
|
||||
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. */
|
||||
if (deserialize_varlink_sockets)
|
||||
(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 {
|
||||
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_threshold_usec = USEC_INFINITY,
|
||||
|
||||
.dump_ratelimit = {
|
||||
.interval = 10 * USEC_PER_MINUTE,
|
||||
.burst = 10,
|
||||
},
|
||||
};
|
||||
|
||||
#if ENABLE_EFI
|
||||
|
@ -471,6 +471,8 @@ struct Manager {
|
||||
|
||||
/* Allow users to configure a rate limit for Reload() operations */
|
||||
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;
|
||||
};
|
||||
|
@ -70,4 +70,14 @@
|
||||
</defaults>
|
||||
</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>
|
||||
|
@ -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-journald.service systemd-logind.service "*" bbb ccc)
|
||||
# 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 "*.socket" >/dev/null
|
||||
|
Loading…
Reference in New Issue
Block a user