Merge pull request #27438 from bluca/dump_ratelimit

manager: restrict Dump*() to privileged callers or ratelimit
This commit is contained in:
Luca Boccassi
2023-05-19 21:29:58 +01:00
committed by GitHub
12 changed files with 107 additions and 5 deletions
+9
View File
@@ -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));
}
+1
View File
@@ -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);
+32 -2
View File
@@ -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) {
+3
View File
@@ -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;
+1
View File
@@ -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);
+23
View File
@@ -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;
+5
View File
@@ -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
+2
View File
@@ -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>