]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 4/7] lib-sieve: Switch sieve_max_cpu_time enforcement to time-decayed re-enable
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 5 May 2026 16:30:23 +0000 (16:30 +0000)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
When a user's cumulative CPU time exceeded sieve_max_cpu_time, the
script was permanently disabled. SIEVE_BINARY_FLAG_RESOURCE_LIMIT was
set in the binary header on first overrun and was never cleared by
the resource_usage_timeout decay (which only zeroed the cpu_time
field), so sieve_binary_check_executable() refused execution forever
unless the binary was recompiled or an admin manually wiped the file.

Replace the sticky-flag model with one driven purely by the persisted
cumulative cpu_time, decayed by resource_usage_timeout:

 - Drop SIEVE_BINARY_FLAG_RESOURCE_LIMIT and the enum that defined it;
   nothing reads sbin->header.flags anymore.
 - sieve_binary_check_executable() and sieve_binary_check_resource_usage()
   call sieve_resource_usage_is_excessive() on the current cumulative
   cpu_time. Raising sieve_max_cpu_time now correctly re-enables a
   previously over-limit user without further intervention.
 - Gate per-user file persistence on the per-run delta, not the
   cumulative total. A refused-without-running attempt has a zero
   delta, skips the file write, and leaves update_time at the moment
   work was actually consumed. After resource_usage_timeout seconds
   of inactivity (or only-trivial deliveries) the next load decays
   the cpu_time to zero and the user is allowed to run again.

The on-disk per-user sieve-rusage file format becomes:

    V1 <cpu-secs> <update-time>

Gbp-Pq: Name 0004-lib-sieve-Switch-sieve_max_cpu_time-enforcement-to-t.patch

pigeonhole/src/lib-sieve/sieve-binary-file.c
pigeonhole/src/lib-sieve/sieve-binary-private.h
pigeonhole/src/lib-sieve/sieve-binary.c
pigeonhole/src/lib-sieve/sieve-rusage.c
pigeonhole/src/lib-sieve/sieve-rusage.h

index 58e5158527029826f50d07bda13e547a6ec80395..a379828156e97f31fdc46cd454215e5a51bb7658 100644 (file)
@@ -315,7 +315,7 @@ sieve_binary_save_to_stream(struct sieve_binary *sbin, struct ostream *stream)
        header->blocks = blk_count;
        header->hdr_size = sizeof(*header);
 
-       header->flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+       header->flags = 0;
        sieve_binary_file_zero_header_rusage(sbin);
 
        if (!_save_aligned(sbin, stream, header, sizeof(*header), NULL)) {
@@ -837,7 +837,7 @@ _sieve_binary_open(struct sieve_binary *sbin, enum sieve_error *error_code_r)
           discard any data from older versions. */
        sbin->header.unused_update_time = 0;
        sbin->header.unused_cpu_time_msecs = 0;
-       sbin->header.flags &= ENUM_NEGATE(SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
+       sbin->header.flags = 0;
 
        /* Load block index */
 
@@ -934,15 +934,16 @@ int sieve_binary_check_executable(struct sieve_binary *sbin,
                                  enum sieve_error *error_code_r,
                                  const char **client_error_r)
 {
+       struct sieve_resource_usage rusage;
+
        *client_error_r = NULL;
        sieve_error_args_init(&error_code_r, NULL);
 
-       if (HAS_ALL_BITS(sbin->header.flags,
-                        SIEVE_BINARY_FLAG_RESOURCE_LIMIT)) {
+       sieve_binary_get_resource_usage(sbin, &rusage);
+       if (sieve_resource_usage_is_excessive(sbin->svinst, &rusage)) {
                e_debug(sbin->event,
                        "Binary execution is blocked: "
-                       "Cumulative resource usage limit exceeded "
-                       "(resource limit flag is set)");
+                       "Cumulative resource usage limit exceeded");
                *error_code_r = SIEVE_ERROR_RESOURCE_LIMIT;
                *client_error_r = "cumulative resource usage limit exceeded";
                return 0;
@@ -959,8 +960,6 @@ int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
 {
        struct sieve_storage *storage;
        struct sieve_resource_usage delta;
-       struct sieve_resource_usage total;
-       uint32_t flags;
 
        sieve_error_args_init(&error_code_r, NULL);
 
@@ -976,18 +975,19 @@ int sieve_binary_file_update_resource_usage(struct sieve_binary *sbin,
        storage = sbin->script->storage;
 
        delta = sbin->rusage;
-       sieve_binary_get_resource_usage(sbin, &total);
-       flags = sbin->header.flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
 
-       if (!HAS_ALL_BITS(flags, SIEVE_BINARY_FLAG_RESOURCE_LIMIT) &&
-           !sieve_resource_usage_is_high(sbin->svinst, &total)) {
-               /* Nothing meaningful to persist */
+       /* Persist only when this run consumed a meaningful amount of CPU.
+          Critically, refused-without-running attempts (delta == 0) must not
+          touch the file - otherwise the file's update_time would be refreshed
+          on every blocked attempt, preventing the resource_usage_timeout from
+          ever decaying the persisted CPU time and re-enabling the user. */
+       if (!sieve_resource_usage_is_high(sbin->svinst, &delta)) {
                sieve_resource_usage_init(&sbin->rusage);
                sbin->rusage_updated = FALSE;
                return 0;
        }
 
-       if (sieve_rusage_storage_add(storage, &delta, flags) < 0) {
+       if (sieve_rusage_storage_add(storage, &delta) < 0) {
                *error_code_r = SIEVE_ERROR_TEMP_FAILURE;
                return -1;
        }
index 526ce195c4d46353c3ad1e5ee5a0582f361ef08a..c66ea80528870304fd0f3b1b4212409072d9fa9b 100644 (file)
  * Binary file
  */
 
-enum SIEVE_BINARY_FLAGS {
-       SIEVE_BINARY_FLAG_RESOURCE_LIMIT = BIT(0),
-};
-
 struct sieve_binary_header {
        uint32_t magic;
        uint16_t version_major;
index f6aedf027095a29cfe058b2b34457b80510573fe..2a239cfa5e1f4a072b49fbfd7a976441f685a484 100644 (file)
@@ -210,17 +210,14 @@ void sieve_binary_apply_persisted_rusage(struct sieve_binary *sbin)
 {
        struct sieve_storage *storage = sieve_binary_get_user_storage(sbin);
        struct sieve_resource_usage rusage;
-       struct sieve_binary_header *header = &sbin->header;
-       uint32_t flags = 0;
 
        if (storage == NULL)
                return;
-       if (sieve_rusage_storage_load(storage, &rusage, &flags) <= 0)
+       if (sieve_rusage_storage_load(storage, &rusage) <= 0)
                return;
 
        sbin->persisted_rusage.cpu_time_msecs = rusage.cpu_time_msecs;
        sbin->persisted_rusage.update_time = ioloop_time;
-       header->flags |= (flags & SIEVE_BINARY_FLAG_RESOURCE_LIMIT);
        sieve_resource_usage_init(&sbin->rusage);
        sbin->rusage_updated = FALSE;
 }
@@ -241,16 +238,10 @@ void sieve_binary_get_resource_usage(struct sieve_binary *sbin,
 
 bool sieve_binary_check_resource_usage(struct sieve_binary *sbin)
 {
-       struct sieve_binary_header *header = &sbin->header;
        struct sieve_resource_usage rusage;
 
        sieve_binary_get_resource_usage(sbin, &rusage);
-
-       if (sieve_resource_usage_is_excessive(sbin->svinst, &rusage)) {
-               header->flags |= SIEVE_BINARY_FLAG_RESOURCE_LIMIT;
-               return FALSE;
-       }
-       return TRUE;
+       return !sieve_resource_usage_is_excessive(sbin->svinst, &rusage);
 }
 
 bool sieve_binary_record_resource_usage(
index 9e01cc47fc86d3db45bd2a68a0bcbb9eb95ba62a..63e5da0de727ad36d1053fe8d24c7e315dd0e0cd 100644 (file)
 #define SIEVE_RUSAGE_LOCK_TIMEOUT 10
 #define SIEVE_RUSAGE_LOCK_STALE_TIMEOUT 60
 
-/* Maximum size of the on-disk file. Single-line ASCII format with four
+/* Maximum size of the on-disk file. Single-line ASCII format with three
    space-separated tokens; anything larger is treated as corrupt. */
 #define SIEVE_RUSAGE_MAX_FILE_SIZE 128
 
 struct sieve_rusage_record {
-       uint32_t flags;
        uint32_t cpu_time_msecs;
        time_t update_time;
 };
@@ -72,13 +71,11 @@ sieve_rusage_parse(const char *line, struct sieve_rusage_record *rec)
                    strcmp(tokens[0], SIEVE_RUSAGE_VERSION_TAG) != 0)
                        break;
                if (tokens[1] == NULL || tokens[2] == NULL ||
-                   tokens[3] == NULL || tokens[4] != NULL)
+                   tokens[3] != NULL)
                        break;
-               if (str_to_uint32(tokens[1], &rec->flags) < 0)
+               if (str_to_uint32(tokens[1], &cpu_secs) < 0)
                        break;
-               if (str_to_uint32(tokens[2], &cpu_secs) < 0)
-                       break;
-               if (str_to_time(tokens[3], &rec->update_time) < 0)
+               if (str_to_time(tokens[2], &rec->update_time) < 0)
                        break;
                if (cpu_secs > UINT32_MAX / 1000)
                        rec->cpu_time_msecs = UINT32_MAX;
@@ -148,14 +145,12 @@ sieve_rusage_dotlock_settings(struct dotlock_settings *set_r)
 }
 
 int sieve_rusage_storage_load(struct sieve_storage *storage,
-                             struct sieve_resource_usage *rusage_r,
-                             uint32_t *flags_r)
+                             struct sieve_resource_usage *rusage_r)
 {
        struct sieve_rusage_record rec;
        unsigned int timeout;
 
        sieve_resource_usage_init(rusage_r);
-       *flags_r = 0;
 
        if (storage->rusage_path == NULL)
                return 0;
@@ -171,13 +166,11 @@ int sieve_rusage_storage_load(struct sieve_storage *storage,
        }
 
        rusage_r->cpu_time_msecs = rec.cpu_time_msecs;
-       *flags_r = rec.flags;
        return 1;
 }
 
 int sieve_rusage_storage_add(struct sieve_storage *storage,
-                            const struct sieve_resource_usage *delta_rusage,
-                            uint32_t flags_to_set)
+                            const struct sieve_resource_usage *delta_rusage)
 {
        struct sieve_rusage_record rec;
        struct dotlock_settings dlset;
@@ -226,7 +219,6 @@ int sieve_rusage_storage_add(struct sieve_storage *storage,
            (ioloop_time - rec.update_time) > (time_t)timeout) {
                /* decayed: discard previous values */
                rec.cpu_time_msecs = 0;
-               rec.flags = 0;
        }
 
        old_cpu = rec.cpu_time_msecs;
@@ -234,7 +226,6 @@ int sieve_rusage_storage_add(struct sieve_storage *storage,
                rec.cpu_time_msecs = UINT32_MAX;
        else
                rec.cpu_time_msecs = old_cpu + delta_rusage->cpu_time_msecs;
-       rec.flags |= flags_to_set;
        rec.update_time = ioloop_time;
 
        /* Round CPU time up to whole seconds for persistence. */
@@ -244,8 +235,8 @@ int sieve_rusage_storage_add(struct sieve_storage *storage,
                cpu_secs = (rec.cpu_time_msecs + 999) / 1000;
 
        out = t_str_new(64);
-       str_printfa(out, "%s %u %u %lld\n", SIEVE_RUSAGE_VERSION_TAG,
-                   rec.flags, cpu_secs, (long long)rec.update_time);
+       str_printfa(out, "%s %u %lld\n", SIEVE_RUSAGE_VERSION_TAG,
+                   cpu_secs, (long long)rec.update_time);
 
        wret = write(fd, str_data(out), str_len(out));
        if (wret != (ssize_t)str_len(out)) {
index 823ded399f37a48d851189edc035c33a319c6cf1..4f1a508601ef9ed155be8e87ddf59632e4a87fba 100644 (file)
@@ -16,17 +16,13 @@ void sieve_rusage_storage_init(struct sieve_storage *storage,
 
 /* Load persisted resource usage from the per-user file. Returns 1 on success,
    0 if the storage has no rusage file (no INBOX namespace path), -1 on error.
-   Applies the resource_usage_timeout decay. flags_r receives sieve binary
-   header flags (RESOURCE_LIMIT) that were persisted. */
+   Applies the resource_usage_timeout decay. */
 int sieve_rusage_storage_load(struct sieve_storage *storage,
-                             struct sieve_resource_usage *rusage_r,
-                             uint32_t *flags_r);
+                             struct sieve_resource_usage *rusage_r);
 
-/* Atomically add delta_rusage to and OR flags_to_set into the persisted
-   per-user rusage file. Read-modify-write under fcntl WRLCK. Returns 1 on
-   success, 0 if no rusage file is configured, -1 on error. */
+/* Atomically add delta_rusage to the persisted per-user rusage file.
+   Returns 1 on success, 0 if no rusage file is configured, -1 on error. */
 int sieve_rusage_storage_add(struct sieve_storage *storage,
-                            const struct sieve_resource_usage *delta_rusage,
-                            uint32_t flags_to_set);
+                            const struct sieve_resource_usage *delta_rusage);
 
 #endif