From a26f2b2732733aa361fec0a3a8f0ba377f48e75c Mon Sep 17 00:00:00 2001
From: Anita Zhang <anitzhang@gmail.com>
Date: Sun, 7 Oct 2018 20:28:36 -0700
Subject: [PATCH] core: implement per unit journal rate limiting

Add LogRateLimitIntervalSec= and LogRateLimitBurst= options for
services. If provided, these values get passed to the journald
client context, and those values are used in the rate limiting
function in the journal over the the journald.conf values.

Part of #10230

(cherry picked from commit 90fc172e191f44979005a524521112f2bd1ff21b)

Resolves: #1719577
---
 catalog/systemd.catalog.in                  |  3 +-
 doc/TRANSIENT-SETTINGS.md                   |  2 +
 man/journald.conf.xml                       |  8 +-
 man/systemd.exec.xml                        | 16 ++++
 src/core/dbus-execute.c                     |  8 ++
 src/core/execute.c                          | 14 ++++
 src/core/execute.h                          |  3 +
 src/core/load-fragment-gperf.gperf.m4       |  2 +
 src/core/unit.c                             | 92 +++++++++++++++++++++
 src/core/unit.h                             |  2 +
 src/journal/journald-context.c              | 50 ++++++++++-
 src/journal/journald-context.h              |  3 +
 src/journal/journald-rate-limit.c           | 38 ++++-----
 src/journal/journald-rate-limit.h           |  4 +-
 src/journal/journald-server.c               |  4 +-
 src/shared/bus-unit-util.c                  |  8 ++
 test/fuzz/fuzz-unit-file/directives.service |  2 +
 17 files changed, 231 insertions(+), 28 deletions(-)

diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
index f1bddc6f7d..8234e387cf 100644
--- a/catalog/systemd.catalog.in
+++ b/catalog/systemd.catalog.in
@@ -52,7 +52,8 @@ dropped, other services' messages are unaffected.
 
 The limits controlling when messages are dropped may be configured
 with RateLimitIntervalSec= and RateLimitBurst= in
-/etc/systemd/journald.conf. See journald.conf(5) for details.
+/etc/systemd/journald.conf or LogRateLimitIntervalSec= and LogRateLimitBurst=
+in the unit file. See journald.conf(5) and systemd.exec(5) for details.
 
 -- e9bf28e6e834481bb6f48f548ad13606
 Subject: Journal messages have been missed
diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
index ca9e8387b7..0ea444b133 100644
--- a/doc/TRANSIENT-SETTINGS.md
+++ b/doc/TRANSIENT-SETTINGS.md
@@ -135,6 +135,8 @@ All execution-related settings are available for transient units.
 ✓ SyslogLevelPrefix=
 ✓ LogLevelMax=
 ✓ LogExtraFields=
+✓ LogRateLimitIntervalSec=
+✓ LogRateLimitBurst=
 ✓ SecureBits=
 ✓ CapabilityBoundingSet=
 ✓ AmbientCapabilities=
diff --git a/man/journald.conf.xml b/man/journald.conf.xml
index ee8e8b7faf..b57a244b22 100644
--- a/man/journald.conf.xml
+++ b/man/journald.conf.xml
@@ -140,7 +140,13 @@
         following units: <literal>s</literal>, <literal>min</literal>,
         <literal>h</literal>, <literal>ms</literal>,
         <literal>us</literal>. To turn off any kind of rate limiting,
-        set either value to 0.</para></listitem>
+        set either value to 0.</para>
+
+        <para>If a service provides rate limits for itself through
+        <varname>LogRateLimitIntervalSec=</varname> and/or <varname>LogRateLimitBurst=</varname>
+        in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+        those values will override the settings specified here.</para>
+        </listitem>
       </varlistentry>
 
       <varlistentry>
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 3bd790b485..737c52bcc4 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -1905,6 +1905,22 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
         matching. Assign an empty string to reset the list.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LogRateLimitIntervalSec=</varname></term>
+        <term><varname>LogRateLimitBurst=</varname></term>
+
+        <listitem><para>Configures the rate limiting that is applied to messages generated by this unit. If, in the
+        time interval defined by <varname>LogRateLimitIntervalSec=</varname>, more messages than specified in
+        <varname>LogRateLimitBurst=</varname> are logged by a service, all further messages within the interval are
+        dropped until the interval is over. A message about the number of dropped messages is generated. The time
+        specification for <varname>LogRateLimitIntervalSec=</varname> may be specified in the following units: "s",
+        "min", "h", "ms", "us" (see
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details).
+        The default settings are set by <varname>RateLimitIntervalSec=</varname> and <varname>RateLimitBurst=</varname>
+        configured in <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+        </para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>SyslogIdentifier=</varname></term>
 
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index c44970c10c..33a91c012e 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -718,6 +718,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogLevelMax", "i", bus_property_get_int, offsetof(ExecContext, log_level_max), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_rate_limit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_rate_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1073,6 +1075,12 @@ int bus_exec_context_set_transient_property(
         if (streq(name, "CPUSchedulingPriority"))
                 return bus_set_transient_sched_priority(u, name, &c->cpu_sched_priority, message, flags, error);
 
+        if (streq(name, "LogRateLimitIntervalUSec"))
+                return bus_set_transient_usec(u, name, &c->log_rate_limit_interval_usec, message, flags, error);
+
+        if (streq(name, "LogRateLimitBurst"))
+                return bus_set_transient_unsigned(u, name, &c->log_rate_limit_burst, message, flags, error);
+
         if (streq(name, "Personality"))
                 return bus_set_transient_personality(u, name, &c->personality, message, flags, error);
 
diff --git a/src/core/execute.c b/src/core/execute.c
index c62f3cf849..8293c522bc 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -3693,6 +3693,9 @@ void exec_context_done(ExecContext *c) {
 
         exec_context_free_log_extra_fields(c);
 
+        c->log_rate_limit_interval_usec = 0;
+        c->log_rate_limit_burst = 0;
+
         c->stdin_data = mfree(c->stdin_data);
         c->stdin_data_size = 0;
 }
@@ -4153,6 +4156,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                 fprintf(f, "%sLogLevelMax: %s\n", prefix, strna(t));
         }
 
+        if (c->log_rate_limit_interval_usec > 0) {
+                char buf_timespan[FORMAT_TIMESPAN_MAX];
+
+                fprintf(f,
+                        "%sLogRateLimitIntervalSec: %s\n",
+                        prefix, format_timespan(buf_timespan, sizeof(buf_timespan), c->log_rate_limit_interval_usec, USEC_PER_SEC));
+        }
+
+        if (c->log_rate_limit_burst > 0)
+                fprintf(f, "%sLogRateLimitBurst: %u\n", prefix, c->log_rate_limit_burst);
+
         if (c->n_log_extra_fields > 0) {
                 size_t j;
 
diff --git a/src/core/execute.h b/src/core/execute.h
index bff1634b88..8c91636adc 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -216,6 +216,9 @@ struct ExecContext {
         struct iovec* log_extra_fields;
         size_t n_log_extra_fields;
 
+        usec_t log_rate_limit_interval_usec;
+        unsigned log_rate_limit_burst;
+
         bool cpu_sched_reset_on_fork;
         bool non_blocking;
         bool private_tmp;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 15fb47838c..1066bcfb8f 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -57,6 +57,8 @@ $1.SyslogFacility,               config_parse_log_facility,          0,
 $1.SyslogLevel,                  config_parse_log_level,             0,                             offsetof($1, exec_context.syslog_priority)
 $1.SyslogLevelPrefix,            config_parse_bool,                  0,                             offsetof($1, exec_context.syslog_level_prefix)
 $1.LogLevelMax,                  config_parse_log_level,             0,                             offsetof($1, exec_context.log_level_max)
+$1.LogRateLimitIntervalSec,      config_parse_sec,                   0,                             offsetof($1, exec_context.log_rate_limit_interval_usec)
+$1.LogRateLimitBurst,            config_parse_unsigned,              0,                             offsetof($1, exec_context.log_rate_limit_burst)
 $1.LogExtraFields,               config_parse_log_extra_fields,      0,                             offsetof($1, exec_context)
 $1.Capabilities,                 config_parse_warn_compat,           DISABLED_LEGACY,               offsetof($1, exec_context)
 $1.SecureBits,                   config_parse_exec_secure_bits,      0,                             offsetof($1, exec_context.secure_bits)
diff --git a/src/core/unit.c b/src/core/unit.c
index b0b1c77ef7..115739f4c6 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -3245,6 +3245,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
         unit_serialize_item(u, f, "exported-invocation-id", yes_no(u->exported_invocation_id));
         unit_serialize_item(u, f, "exported-log-level-max", yes_no(u->exported_log_level_max));
         unit_serialize_item(u, f, "exported-log-extra-fields", yes_no(u->exported_log_extra_fields));
+        unit_serialize_item(u, f, "exported-log-rate-limit-interval", yes_no(u->exported_log_rate_limit_interval));
+        unit_serialize_item(u, f, "exported-log-rate-limit-burst", yes_no(u->exported_log_rate_limit_burst));
 
         unit_serialize_item_format(u, f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base);
         if (u->cpu_usage_last != NSEC_INFINITY)
@@ -3508,6 +3510,26 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
 
                         continue;
 
+                } else if (streq(l, "exported-log-rate-limit-interval")) {
+
+                        r = parse_boolean(v);
+                        if (r < 0)
+                                log_unit_debug(u, "Failed to parse exported log rate limit interval %s, ignoring.", v);
+                        else
+                                u->exported_log_rate_limit_interval = r;
+
+                        continue;
+
+                } else if (streq(l, "exported-log-rate-limit-burst")) {
+
+                        r = parse_boolean(v);
+                        if (r < 0)
+                                log_unit_debug(u, "Failed to parse exported log rate limit burst %s, ignoring.", v);
+                        else
+                                u->exported_log_rate_limit_burst = r;
+
+                        continue;
+
                 } else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) {
 
                         r = safe_atou64(v, &u->cpu_usage_base);
@@ -5241,6 +5263,60 @@ fail:
         return r;
 }
 
+static int unit_export_log_rate_limit_interval(Unit *u, const ExecContext *c) {
+        _cleanup_free_ char *buf = NULL;
+        const char *p;
+        int r;
+
+        assert(u);
+        assert(c);
+
+        if (u->exported_log_rate_limit_interval)
+                return 0;
+
+        if (c->log_rate_limit_interval_usec == 0)
+                return 0;
+
+        p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id);
+
+        if (asprintf(&buf, "%" PRIu64, c->log_rate_limit_interval_usec) < 0)
+                return log_oom();
+
+        r = symlink_atomic(buf, p);
+        if (r < 0)
+                return log_unit_debug_errno(u, r, "Failed to create log rate limit interval symlink %s: %m", p);
+
+        u->exported_log_rate_limit_interval = true;
+        return 0;
+}
+
+static int unit_export_log_rate_limit_burst(Unit *u, const ExecContext *c) {
+        _cleanup_free_ char *buf = NULL;
+        const char *p;
+        int r;
+
+        assert(u);
+        assert(c);
+
+        if (u->exported_log_rate_limit_burst)
+                return 0;
+
+        if (c->log_rate_limit_burst == 0)
+                return 0;
+
+        p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id);
+
+        if (asprintf(&buf, "%u", c->log_rate_limit_burst) < 0)
+                return log_oom();
+
+        r = symlink_atomic(buf, p);
+        if (r < 0)
+                return log_unit_debug_errno(u, r, "Failed to create log rate limit burst symlink %s: %m", p);
+
+        u->exported_log_rate_limit_burst = true;
+        return 0;
+}
+
 void unit_export_state_files(Unit *u) {
         const ExecContext *c;
 
@@ -5274,6 +5350,8 @@ void unit_export_state_files(Unit *u) {
         if (c) {
                 (void) unit_export_log_level_max(u, c);
                 (void) unit_export_log_extra_fields(u, c);
+                (void) unit_export_log_rate_limit_interval(u, c);
+                (void) unit_export_log_rate_limit_burst(u, c);
         }
 }
 
@@ -5310,6 +5388,20 @@ void unit_unlink_state_files(Unit *u) {
 
                 u->exported_log_extra_fields = false;
         }
+
+        if (u->exported_log_rate_limit_interval) {
+                p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id);
+                (void) unlink(p);
+
+                u->exported_log_rate_limit_interval = false;
+        }
+
+        if (u->exported_log_rate_limit_burst) {
+                p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id);
+                (void) unlink(p);
+
+                u->exported_log_rate_limit_burst = false;
+        }
 }
 
 int unit_prepare_exec(Unit *u) {
diff --git a/src/core/unit.h b/src/core/unit.h
index 68cc1869e4..99755823eb 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -349,6 +349,8 @@ typedef struct Unit {
         bool exported_invocation_id:1;
         bool exported_log_level_max:1;
         bool exported_log_extra_fields:1;
+        bool exported_log_rate_limit_interval:1;
+        bool exported_log_rate_limit_burst:1;
 
         /* When writing transient unit files, stores which section we stored last. If < 0, we didn't write any yet. If
          * == 0 we are in the [Unit] section, if > 0 we are in the unit type-specific section. */
diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c
index dba3525ed8..c8e97e16de 100644
--- a/src/journal/journald-context.c
+++ b/src/journal/journald-context.c
@@ -140,6 +140,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
         c->timestamp = USEC_INFINITY;
         c->extra_fields_mtime = NSEC_INFINITY;
         c->log_level_max = -1;
+        c->log_rate_limit_interval = s->rate_limit_interval;
+        c->log_rate_limit_burst = s->rate_limit_burst;
 
         r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c);
         if (r < 0) {
@@ -151,7 +153,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
         return 0;
 }
 
-static void client_context_reset(ClientContext *c) {
+static void client_context_reset(Server *s, ClientContext *c) {
+        assert(s);
         assert(c);
 
         c->timestamp = USEC_INFINITY;
@@ -186,6 +189,9 @@ static void client_context_reset(ClientContext *c) {
         c->extra_fields_mtime = NSEC_INFINITY;
 
         c->log_level_max = -1;
+
+        c->log_rate_limit_interval = s->rate_limit_interval;
+        c->log_rate_limit_burst = s->rate_limit_burst;
 }
 
 static ClientContext* client_context_free(Server *s, ClientContext *c) {
@@ -199,7 +205,7 @@ static ClientContext* client_context_free(Server *s, ClientContext *c) {
         if (c->in_lru)
                 assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0);
 
-        client_context_reset(c);
+        client_context_reset(s, c);
 
         return mfree(c);
 }
@@ -464,6 +470,42 @@ static int client_context_read_extra_fields(
         return 0;
 }
 
+static int client_context_read_log_rate_limit_interval(ClientContext *c) {
+        _cleanup_free_ char *value = NULL;
+        const char *p;
+        int r;
+
+        assert(c);
+
+        if (!c->unit)
+                return 0;
+
+        p = strjoina("/run/systemd/units/log-rate-limit-interval:", c->unit);
+        r = readlink_malloc(p, &value);
+        if (r < 0)
+                return r;
+
+        return safe_atou64(value, &c->log_rate_limit_interval);
+}
+
+static int client_context_read_log_rate_limit_burst(ClientContext *c) {
+        _cleanup_free_ char *value = NULL;
+        const char *p;
+        int r;
+
+        assert(c);
+
+        if (!c->unit)
+                return 0;
+
+        p = strjoina("/run/systemd/units/log-rate-limit-burst:", c->unit);
+        r = readlink_malloc(p, &value);
+        if (r < 0)
+                return r;
+
+        return safe_atou(value, &c->log_rate_limit_burst);
+}
+
 static void client_context_really_refresh(
                 Server *s,
                 ClientContext *c,
@@ -490,6 +532,8 @@ static void client_context_really_refresh(
         (void) client_context_read_invocation_id(s, c);
         (void) client_context_read_log_level_max(s, c);
         (void) client_context_read_extra_fields(s, c);
+        (void) client_context_read_log_rate_limit_interval(c);
+        (void) client_context_read_log_rate_limit_burst(c);
 
         c->timestamp = timestamp;
 
@@ -520,7 +564,7 @@ void client_context_maybe_refresh(
         /* If the data isn't pinned and if the cashed data is older than the upper limit, we flush it out
          * entirely. This follows the logic that as long as an entry is pinned the PID reuse is unlikely. */
         if (c->n_ref == 0 && c->timestamp + MAX_USEC < timestamp) {
-                client_context_reset(c);
+                client_context_reset(s, c);
                 goto refresh;
         }
 
diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h
index 9df3a38eff..5e19c71f14 100644
--- a/src/journal/journald-context.h
+++ b/src/journal/journald-context.h
@@ -49,6 +49,9 @@ struct ClientContext {
         size_t extra_fields_n_iovec;
         void *extra_fields_data;
         nsec_t extra_fields_mtime;
+
+        usec_t log_rate_limit_interval;
+        unsigned log_rate_limit_burst;
 };
 
 int client_context_get(
diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c
index 6a8a36a736..539efb8669 100644
--- a/src/journal/journald-rate-limit.c
+++ b/src/journal/journald-rate-limit.c
@@ -39,6 +39,10 @@ struct JournalRateLimitGroup {
         JournalRateLimit *parent;
 
         char *id;
+
+        /* Interval is stored to keep track of when the group expires */
+        usec_t interval;
+
         JournalRateLimitPool pools[POOLS_MAX];
         uint64_t hash;
 
@@ -47,8 +51,6 @@ struct JournalRateLimitGroup {
 };
 
 struct JournalRateLimit {
-        usec_t interval;
-        unsigned burst;
 
         JournalRateLimitGroup* buckets[BUCKETS_MAX];
         JournalRateLimitGroup *lru, *lru_tail;
@@ -58,18 +60,13 @@ struct JournalRateLimit {
         uint8_t hash_key[16];
 };
 
-JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
+JournalRateLimit *journal_rate_limit_new(void) {
         JournalRateLimit *r;
 
-        assert(interval > 0 || burst == 0);
-
         r = new0(JournalRateLimit, 1);
         if (!r)
                 return NULL;
 
-        r->interval = interval;
-        r->burst = burst;
-
         random_bytes(r->hash_key, sizeof(r->hash_key));
 
         return r;
@@ -109,7 +106,7 @@ _pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, us
         assert(g);
 
         for (i = 0; i < POOLS_MAX; i++)
-                if (g->pools[i].begin + g->parent->interval >= ts)
+                if (g->pools[i].begin + g->interval >= ts)
                         return false;
 
         return true;
@@ -126,7 +123,7 @@ static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
                 journal_rate_limit_group_free(r->lru_tail);
 }
 
-static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
+static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t interval, usec_t ts) {
         JournalRateLimitGroup *g;
         struct siphash state;
 
@@ -145,6 +142,8 @@ static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r,
         string_hash_func(g->id, &state);
         g->hash = siphash24_finalize(&state);
 
+        g->interval = interval;
+
         journal_rate_limit_vacuum(r, ts);
 
         LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g);
@@ -189,7 +188,7 @@ static unsigned burst_modulate(unsigned burst, uint64_t available) {
         return burst;
 }
 
-int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available) {
         uint64_t h;
         JournalRateLimitGroup *g;
         JournalRateLimitPool *p;
@@ -209,11 +208,6 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
         if (!r)
                 return 1;
 
-        if (r->interval == 0 || r->burst == 0)
-                return 1;
-
-        burst = burst_modulate(r->burst, available);
-
         ts = now(CLOCK_MONOTONIC);
 
         siphash24_init(&state, r->hash_key);
@@ -226,10 +220,16 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
                         break;
 
         if (!g) {
-                g = journal_rate_limit_group_new(r, id, ts);
+                g = journal_rate_limit_group_new(r, id, rl_interval, ts);
                 if (!g)
                         return -ENOMEM;
-        }
+        } else
+                g->interval = rl_interval;
+
+        if (rl_interval == 0 || rl_burst == 0)
+                return 1;
+
+        burst = burst_modulate(rl_burst, available);
 
         p = &g->pools[priority_map[priority]];
 
@@ -240,7 +240,7 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
                 return 1;
         }
 
-        if (p->begin + r->interval < ts) {
+        if (p->begin + rl_interval < ts) {
                 unsigned s;
 
                 s = p->suppressed;
diff --git a/src/journal/journald-rate-limit.h b/src/journal/journald-rate-limit.h
index 3a7f106de0..a2992800fe 100644
--- a/src/journal/journald-rate-limit.h
+++ b/src/journal/journald-rate-limit.h
@@ -5,6 +5,6 @@
 
 typedef struct JournalRateLimit JournalRateLimit;
 
-JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst);
+JournalRateLimit *journal_rate_limit_new(void);
 void journal_rate_limit_free(JournalRateLimit *r);
-int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available);
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available);
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 8de45552f6..0c983e102a 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -943,7 +943,7 @@ void server_dispatch_message(
         if (c && c->unit) {
                 (void) determine_space(s, &available, NULL);
 
-                rl = journal_rate_limit_test(s->rate_limit, c->unit, priority & LOG_PRIMASK, available);
+                rl = journal_rate_limit_test(s->rate_limit, c->unit, c->log_rate_limit_interval, c->log_rate_limit_burst, priority & LOG_PRIMASK, available);
                 if (rl == 0)
                         return;
 
@@ -1852,7 +1852,7 @@ int server_init(Server *s) {
         if (!s->udev)
                 return -ENOMEM;
 
-        s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
+        s->rate_limit = journal_rate_limit_new();
         if (!s->rate_limit)
                 return -ENOMEM;
 
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 3238b442c0..271cc054da 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -755,6 +755,14 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
 
                 return bus_append_parse_nsec(m, field, eq);
 
+        if (STR_IN_SET(field, "LogRateLimitIntervalSec"))
+
+                return bus_append_parse_sec_rename(m, field, eq);
+
+        if (streq(field, "LogRateLimitBurst"))
+
+                return bus_append_safe_atou(m, field, eq);
+
         if (streq(field, "MountFlags"))
 
                 return bus_append_mount_propagation_flags_from_string(m, field, eq);
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index c2334d3b19..d8d1fc68b8 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -792,6 +792,8 @@ LineMax=
 LockPersonality=
 LogExtraFields=
 LogLevelMax=
+LogRateLimitIntervalSec=
+LogRateLimitBurst=
 LogsDirectory=
 LogsDirectoryMode=
 MACVLAN=
