From: Timo Sirainen Date: Tue, 5 May 2026 16:30:23 +0000 (+0000) Subject: [PATCH 4/7] lib-sieve: Switch sieve_max_cpu_time enforcement to time-decayed re-enable X-Git-Tag: archive/raspbian/1%2.4.1+dfsg1-6+rpi1+deb13u7^2~111 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=443535260213435bc89e878a6c805a35bea3894b;p=dovecot.git [PATCH 4/7] lib-sieve: Switch sieve_max_cpu_time enforcement to time-decayed re-enable 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 Gbp-Pq: Name 0004-lib-sieve-Switch-sieve_max_cpu_time-enforcement-to-t.patch --- diff --git a/pigeonhole/src/lib-sieve/sieve-binary-file.c b/pigeonhole/src/lib-sieve/sieve-binary-file.c index 58e5158..a379828 100644 --- a/pigeonhole/src/lib-sieve/sieve-binary-file.c +++ b/pigeonhole/src/lib-sieve/sieve-binary-file.c @@ -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; } diff --git a/pigeonhole/src/lib-sieve/sieve-binary-private.h b/pigeonhole/src/lib-sieve/sieve-binary-private.h index 526ce19..c66ea80 100644 --- a/pigeonhole/src/lib-sieve/sieve-binary-private.h +++ b/pigeonhole/src/lib-sieve/sieve-binary-private.h @@ -13,10 +13,6 @@ * Binary file */ -enum SIEVE_BINARY_FLAGS { - SIEVE_BINARY_FLAG_RESOURCE_LIMIT = BIT(0), -}; - struct sieve_binary_header { uint32_t magic; uint16_t version_major; diff --git a/pigeonhole/src/lib-sieve/sieve-binary.c b/pigeonhole/src/lib-sieve/sieve-binary.c index f6aedf0..2a239cf 100644 --- a/pigeonhole/src/lib-sieve/sieve-binary.c +++ b/pigeonhole/src/lib-sieve/sieve-binary.c @@ -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( diff --git a/pigeonhole/src/lib-sieve/sieve-rusage.c b/pigeonhole/src/lib-sieve/sieve-rusage.c index 9e01cc4..63e5da0 100644 --- a/pigeonhole/src/lib-sieve/sieve-rusage.c +++ b/pigeonhole/src/lib-sieve/sieve-rusage.c @@ -26,12 +26,11 @@ #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)) { diff --git a/pigeonhole/src/lib-sieve/sieve-rusage.h b/pigeonhole/src/lib-sieve/sieve-rusage.h index 823ded3..4f1a508 100644 --- a/pigeonhole/src/lib-sieve/sieve-rusage.h +++ b/pigeonhole/src/lib-sieve/sieve-rusage.h @@ -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