mime: bound Curl_mime_duppart() recursion depth
No binary (build failed)
—
(log yok)
| @@ -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. | |
| */ | |
| @@ -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; | |