From: Vsevolod Stakhov Date: Mon, 16 Feb 2026 15:36:16 +0000 (+0000) Subject: [Minor] Backport ucl_object_iterate_end() from libucl to fix iterator leak X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=71b32137c619dde6b39a2c9d9dc47ffc164c8f58;p=thirdparty%2Frspamd.git [Minor] Backport ucl_object_iterate_end() from libucl to fix iterator leak Add ucl_object_iterate_end() function to free resources associated with an inline iterator when iteration is abandoned before completion. This fixes a potential memory leak when iterating over UCL_OBJECT types where internal heap state is allocated. Changes backported from libucl 0.9.3: - Add ucl_hash_iterate_free() to ucl_hash.c/h - Add ucl_object_iterate_end() to ucl_util.c and ucl.h - Add ucl_iterate_object_end macro alias Note: Rspamd already had the security fixes for: - heap-buffer-overflow in ucl_maybe_parse_number - heap-buffer-overflow in ucl_parse_multiline_string - Use-After-Free in ucl_hash_insert - Invalid JSON emission when merging objects --- diff --git a/contrib/libucl/ucl.h b/contrib/libucl/ucl.h index 65361ae192..91fd28adcb 100644 --- a/contrib/libucl/ucl.h +++ b/contrib/libucl/ucl.h @@ -881,6 +881,29 @@ UCL_EXTERN const ucl_object_t *ucl_object_iterate_with_error(const ucl_object_t #define ucl_iterate_object ucl_object_iterate #define ucl_object_iterate(ob, it, ev) ucl_object_iterate_with_error((ob), (it), (ev), NULL) +/** + * Free resources associated with an inline iterator when iteration is + * abandoned before completion. Only needed for UCL_OBJECT iteration where + * internal heap state is allocated. Safe to call with a NULL iterator or + * on non-object types. + * + * Example usage: + * ucl_object_iter_t it = NULL; + * while ((cur = ucl_iterate_object(obj, &it, true))) { + * if (error) { + * ucl_object_iterate_end(obj, &it); + * return; + * } + * } + * + * @param obj the object being iterated + * @param iter pointer to the iterator to free + */ +UCL_EXTERN void ucl_object_iterate_end(const ucl_object_t *obj, + ucl_object_iter_t *iter); + +#define ucl_iterate_object_end ucl_object_iterate_end + /** * Create new safe iterator for the specified object * @param obj object to iterate diff --git a/contrib/libucl/ucl_hash.c b/contrib/libucl/ucl_hash.c index eb2053ba1b..cda230d911 100644 --- a/contrib/libucl/ucl_hash.c +++ b/contrib/libucl/ucl_hash.c @@ -355,6 +355,15 @@ bool ucl_hash_iter_has_next(ucl_hash_t *hashlin, ucl_hash_iter_t iter) return it->cur != NULL; } +void ucl_hash_iterate_free(ucl_hash_iter_t iter) +{ + struct ucl_hash_real_iter *it = (struct ucl_hash_real_iter *) (iter); + + if (it != NULL) { + UCL_FREE(sizeof(*it), it); + } +} + const ucl_object_t * ucl_hash_search(ucl_hash_t *hashlin, const char *key, unsigned keylen) diff --git a/contrib/libucl/ucl_hash.h b/contrib/libucl/ucl_hash.h index c2d5517bbb..42e2247a98 100644 --- a/contrib/libucl/ucl_hash.h +++ b/contrib/libucl/ucl_hash.h @@ -97,6 +97,12 @@ const void* ucl_hash_iterate2 (ucl_hash_t *hashlin, ucl_hash_iter_t *iter, int * */ bool ucl_hash_iter_has_next (ucl_hash_t *hashlin, ucl_hash_iter_t iter); +/** + * Free resources associated with an iterator when iteration is abandoned + * before completion. Safe to call with NULL iterator. + */ +void ucl_hash_iterate_free(ucl_hash_iter_t iter); + /** * Reserves space in hash * @return true on sucess, false on failure (e.g. ENOMEM) diff --git a/contrib/libucl/ucl_util.c b/contrib/libucl/ucl_util.c index 5ea9f89ffc..b17d0b63ac 100644 --- a/contrib/libucl/ucl_util.c +++ b/contrib/libucl/ucl_util.c @@ -2784,6 +2784,19 @@ ucl_object_iterate_with_error (const ucl_object_t *obj, ucl_object_iter_t *iter, return NULL; } +void ucl_object_iterate_end(const ucl_object_t *obj, ucl_object_iter_t *iter) +{ + if (iter == NULL || *iter == NULL) { + return; + } + + if (obj != NULL && obj->type == UCL_OBJECT) { + ucl_hash_iterate_free(*iter); + } + + *iter = NULL; +} + enum ucl_safe_iter_flags { UCL_ITERATE_FLAG_UNDEFINED = 0, UCL_ITERATE_FLAG_INSIDE_ARRAY,