← Commit list

4c67658f95

Filippo Tedeschi
Filippo Tedeschi
2026-09-22
SUCCESS Duration: 402s

Subject

mime: bound Curl_mime_duppart() recursion depth

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

lib/mime.c+40 -3
@@ -1123,8 +1123,29 @@ void curl_mime_free(curl_mime *mime)
}
}
-CURLcode Curl_mime_duppart(struct Curl_easy *data,
- curl_mimepart *dst, const curl_mimepart *src)
+static bool mime_is_too_deep(const curl_mimepart *part, size_t call_depth)
+{
+ DEBUGASSERT(part);
+
+ if(++call_depth > MAX_MIME_LEVELS)
+ return TRUE;
+
+ if(part->kind == MIMEKIND_MULTIPART && part->arg) {
+ const curl_mime *mime = (const curl_mime *)part->arg;
+ const curl_mimepart *s;
+
+ for(s = mime->firstpart; s; s = s->nextpart) {
+ if(mime_is_too_deep(s, call_depth))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static CURLcode mime_duppart(struct Curl_easy *data,
+ curl_mimepart *dst, const curl_mimepart *src,
+ size_t call_depth)
{
curl_mime *mime;
curl_mimepart *d;
@@ -1132,6 +1153,10 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data,
CURLcode result = CURLE_OK;
DEBUGASSERT(dst);
+ DEBUGASSERT(src);
+
+ if(++call_depth > MAX_MIME_LEVELS)
+ return CURLE_TOO_LARGE;
/* Duplicate content. */
switch(src->kind) {
@@ -1160,7 +1185,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data,
for(s = ((curl_mime *)src->arg)->firstpart; !result && s;
s = s->nextpart) {
d = curl_mime_addpart(mime);
- result = d ? Curl_mime_duppart(data, d, s) : CURLE_OUT_OF_MEMORY;
+ result = d ? mime_duppart(data, d, s, call_depth) : CURLE_OUT_OF_MEMORY;
}
break;
default: /* Invalid kind: should not occur. */
@@ -1201,6 +1226,18 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data,
return result;
}
+CURLcode Curl_mime_duppart(struct Curl_easy *data,
+ curl_mimepart *dst, const curl_mimepart *src)
+{
+ if(!dst || !src)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+
+ if(mime_is_too_deep(src, 0))
+ return CURLE_TOO_LARGE;
+
+ return mime_duppart(data, dst, src, 0);
+}
+
/*
* Mime build functions.
*/
tests/libtest/lib3596.c+50 -1
@@ -23,6 +23,25 @@
***************************************************************************/
#include "unitcheck.h"
+#if !defined(CURL_DISABLE_MIME) && !defined(CURL_DISABLE_HTTP)
+static int callback_free_count = 0;
+
+static void test_cb_free(void *ptr)
+{
+ (void)ptr;
+ callback_free_count++;
+}
+
+static size_t test_cb_read(char *buffer, size_t size, size_t nitems, void *arg)
+{
+ (void)buffer;
+ (void)size;
+ (void)nitems;
+ (void)arg;
+ return 0;
+}
+#endif
+
static CURLcode test_lib3596(const char *arg)
{
UNITTEST_BEGIN_SIMPLE
@@ -37,6 +56,8 @@ static CURLcode test_lib3596(const char *arg)
curl_mimepart *part = NULL;
int i;
+ callback_free_count = 0;
+
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
curl_mfprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
@@ -50,6 +71,15 @@ static CURLcode test_lib3596(const char *arg)
stack. */
root = curl_mime_init(curl);
fail_unless(root != NULL, "curl_mime_init root failed");
+
+ /* Add a sibling part with a custom free callback before the deep chain
+ to verify that preflight depth rejection does not duplicate callbacks
+ or invoke them during error rollback. */
+ part = curl_mime_addpart(root);
+ fail_unless(part != NULL, "curl_mime_addpart sibling failed");
+ result = curl_mime_data_cb(part, 0, test_cb_read, NULL, test_cb_free, NULL);
+ fail_unless(result == CURLE_OK, "curl_mime_data_cb failed");
+
current = root;
for(i = 0; i < 50; i++) {
@@ -67,14 +97,33 @@ static CURLcode test_lib3596(const char *arg)
result = curl_easy_setopt(curl, CURLOPT_MIMEPOST, root);
fail_unless(result == CURLE_OK, "CURLOPT_MIMEPOST failed");
- /* Deep nesting must fail gracefully (CURLE_TOO_LARGE), not crash. */
+ /* Deep nesting must fail gracefully (CURLE_TOO_LARGE), not crash, and
+ must not invoke copied user free callbacks during error rollback. */
+ if(!result) {
+ CURL *dup = curl_easy_duphandle(curl);
+ fail_unless(dup == NULL,
+ "curl_easy_duphandle unexpectedly succeeded for nested mime");
+ fail_unless(callback_free_count == 0,
+ "free callback invoked during failed duphandle rollback");
+ }
+
if(!result)
result = curl_easy_perform(curl);
fail_unless(result == CURLE_TOO_LARGE,
"deeply nested mime did not fail with CURLE_TOO_LARGE");
+ {
+ CURL *dup = curl_easy_duphandle(curl);
+ fail_unless(dup == NULL,
+ "curl_easy_duphandle unexpectedly succeeded after perform");
+ fail_unless(callback_free_count == 0,
+ "free callback invoked after perform duphandle");
+ }
+
curl_easy_cleanup(curl);
curl_mime_free(root);
+ fail_unless(callback_free_count == 1,
+ "free callback not invoked during root cleanup");
curl_global_cleanup();
#else
(void)arg;