← Commit list

c427e91dde

Ilias Aberkane
Ilias Aberkane
2026-09-17
SUCCESS Duration: 384s

Subject

mime: bound Curl_mime_prepare_headers() 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+32 -37
@@ -50,8 +50,7 @@ struct Curl_easy;
#define STOP_FILLING ((size_t)-2)
static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems,
- void *instream, bool *hasread,
- size_t call_depth);
+ void *instream, bool *hasread);
static curl_off_t mime_size(curl_mimepart *part);
/* Quoted-printable character class table.
@@ -701,14 +700,10 @@ static size_t readback_bytes(struct mime_state *state,
/* Read a non-encoded part content. */
static size_t read_part_content(curl_mimepart *part, char *buffer,
- size_t bufsize, bool *hasread,
- size_t call_depth)
+ size_t bufsize, bool *hasread)
{
size_t sz = 0;
- if(++call_depth > MAX_MIME_LEVELS)
- return READ_ERROR;
-
switch(part->lastreadstatus) {
case 0:
case CURL_READFUNC_ABORT:
@@ -731,8 +726,7 @@ static size_t read_part_content(curl_mimepart *part, char *buffer,
* Cannot be processed as other kinds since read function requires
* an additional parameter and is highly recursive.
*/
- sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread,
- call_depth);
+ sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread);
break;
case MIMEKIND_FILE:
if(part->fp && feof(part->fp))
@@ -771,17 +765,13 @@ static size_t read_part_content(curl_mimepart *part, char *buffer,
/* Read and encode part content. */
static size_t read_encoded_part_content(curl_mimepart *part, char *buffer,
- size_t bufsize, bool *hasread,
- size_t call_depth)
+ size_t bufsize, bool *hasread)
{
struct mime_encoder_state *st = &part->encstate;
size_t cursize = 0;
size_t sz;
bool ateof = FALSE;
- if(++call_depth > MAX_MIME_LEVELS)
- return READ_ERROR;
-
for(;;) {
if(st->bufbeg < st->bufend || ateof) {
/* Encode buffered data. */
@@ -814,7 +804,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer,
if(st->bufend >= sizeof(st->buf))
return cursize ? cursize : READ_ERROR; /* Buffer full. */
sz = read_part_content(part, st->buf + st->bufend,
- sizeof(st->buf) - st->bufend, hasread, call_depth);
+ sizeof(st->buf) - st->bufend, hasread);
switch(sz) {
case 0:
ateof = TRUE;
@@ -835,14 +825,10 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer,
/* Readback a mime part. */
static size_t readback_part(curl_mimepart *part,
- char *buffer, size_t bufsize, bool *hasread,
- size_t call_depth)
+ char *buffer, size_t bufsize, bool *hasread)
{
size_t cursize = 0;
- if(++call_depth > MAX_MIME_LEVELS)
- return READ_ERROR;
-
/* Readback from part. */
while(bufsize) {
size_t sz = 0;
@@ -886,10 +872,9 @@ static size_t readback_part(curl_mimepart *part,
break;
case MIMESTATE_CONTENT:
if(part->encoder)
- sz = read_encoded_part_content(part, buffer, bufsize, hasread,
- call_depth);
+ sz = read_encoded_part_content(part, buffer, bufsize, hasread);
else
- sz = read_part_content(part, buffer, bufsize, hasread, call_depth);
+ sz = read_part_content(part, buffer, bufsize, hasread);
switch(sz) {
case 0:
mimesetstate(&part->state, MIMESTATE_END, NULL);
@@ -923,16 +908,12 @@ static size_t readback_part(curl_mimepart *part,
/* Readback from mime. Warning: not a read callback function. */
static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems,
- void *instream, bool *hasread,
- size_t call_depth)
+ void *instream, bool *hasread)
{
curl_mime *mime = (curl_mime *)instream;
size_t cursize = 0;
(void)size; /* Always 1 */
- if(++call_depth > MAX_MIME_LEVELS)
- return READ_ERROR;
-
while(nitems) {
size_t sz = 0;
curl_mimepart *part = mime->state.ptr;
@@ -967,7 +948,7 @@ static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems,
mimesetstate(&mime->state, MIMESTATE_END, NULL);
break;
}
- sz = readback_part(part, buffer, nitems, hasread, call_depth);
+ sz = readback_part(part, buffer, nitems, hasread);
switch(sz) {
case CURL_READFUNC_ABORT:
case CURL_READFUNC_PAUSE:
@@ -1551,7 +1532,7 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream)
* adding any data and this loops infinitely. */
do {
hasread = FALSE;
- ret = readback_part(part, buffer, nitems, &hasread, 0);
+ ret = readback_part(part, buffer, nitems, &hasread);
/*
* If this is not possible to get some data without calling more than
* one read callback (probably because a content encoder is not able to
@@ -1774,11 +1755,12 @@ static CURLcode add_content_disposition(struct Curl_easy *data,
return CURLE_OK;
}
-CURLcode Curl_mime_prepare_headers(struct Curl_easy *data,
- curl_mimepart *part,
- const char *contenttype,
- const char *disposition,
- enum mimestrategy strategy)
+static CURLcode mime_prepare_headers(struct Curl_easy *data,
+ curl_mimepart *part,
+ const char *contenttype,
+ const char *disposition,
+ enum mimestrategy strategy,
+ size_t call_depth)
{
curl_mime *mime = NULL;
const char *boundary = NULL;
@@ -1786,6 +1768,9 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data,
const char *cte = NULL;
CURLcode result = CURLE_OK;
+ if(++call_depth > MAX_MIME_LEVELS)
+ return CURLE_TOO_LARGE;
+
/* Get rid of previously prepared headers. */
curl_slist_free_all(part->curlheaders);
part->curlheaders = NULL;
@@ -1874,8 +1859,8 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data,
if(content_type_match(contenttype, STRCONST("multipart/form-data")))
disposition = "form-data";
for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) {
- result = Curl_mime_prepare_headers(data, subpart, NULL,
- disposition, strategy);
+ result = mime_prepare_headers(data, subpart, NULL,
+ disposition, strategy, call_depth);
if(result)
return result;
}
@@ -1883,6 +1868,16 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data,
return result;
}
+CURLcode Curl_mime_prepare_headers(struct Curl_easy *data,
+ curl_mimepart *part,
+ const char *contenttype,
+ const char *disposition,
+ enum mimestrategy strategy)
+{
+ return mime_prepare_headers(data, part, contenttype, disposition,
+ strategy, 0);
+}
+
/* Recursively reset paused status in the given part. */
static void mime_unpause(curl_mimepart *part)
{
tests/data/Makefile.am+1 -0
@@ -2110,6 +2110,7 @@ TESTCASES = \
test3500 \
test3555 \
test3595 \
+ test3596 \
test4000 \
test4001 \
test4644 \
tests/data/test3596+36 -0
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+mime
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+# Client-side
+<client>
+<features>
+Mime
+</features>
+<server>
+http
+</server>
+<tool>
+lib%TESTNUMBER
+</tool>
+<name>
+Curl_mime_prepare_headers
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+<verify>
+</verify>
+</testcase>
tests/libtest/Makefile.inc+1 -0
@@ -334,5 +334,6 @@ TESTS_C = \
lib3208.c \
lib3231.c \
lib3595.c \
+ lib3596.c \
lib5000.c \
lib5004.c
tests/libtest/lib3596.c+84 -0
@@ -0,0 +1,84 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "unitcheck.h"
+
+static CURLcode test_lib3596(const char *arg)
+{
+ UNITTEST_BEGIN_SIMPLE
+
+#if !defined(CURL_DISABLE_MIME) && !defined(CURL_DISABLE_HTTP)
+
+ CURL *curl = NULL;
+ CURLcode result = CURLE_OK;
+ curl_mime *root = NULL;
+ curl_mime *current = NULL;
+ curl_mime *sub = NULL;
+ curl_mimepart *part = NULL;
+ int i;
+
+ if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+ curl_mfprintf(stderr, "curl_global_init() failed\n");
+ return TEST_ERR_MAJOR_BAD;
+ }
+
+ curl = curl_easy_init();
+ fail_unless(curl != NULL, "curl_easy_init() failed");
+
+ /* Create a deeper than MAX_MIME_LEVELS nested mime structure to verify
+ that header preparation bounds recursion instead of exhausting the
+ stack. */
+ root = curl_mime_init(curl);
+ fail_unless(root != NULL, "curl_mime_init root failed");
+ current = root;
+
+ for(i = 0; i < 50; i++) {
+ part = curl_mime_addpart(current);
+ fail_unless(part != NULL, "curl_mime_addpart failed");
+ sub = curl_mime_init(curl);
+ fail_unless(sub != NULL, "curl_mime_init sub failed");
+ curl_mime_subparts(part, sub);
+ current = sub;
+ }
+
+ result = curl_easy_setopt(curl, CURLOPT_URL, arg);
+ fail_unless(result == CURLE_OK, "CURLOPT_URL failed");
+ if(!result)
+ 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. */
+ if(!result)
+ result = curl_easy_perform(curl);
+ fail_unless(result == CURLE_TOO_LARGE,
+ "deeply nested mime did not fail with CURLE_TOO_LARGE");
+
+ curl_easy_cleanup(curl);
+ curl_mime_free(root);
+ curl_global_cleanup();
+#else
+ (void)arg;
+#endif
+
+ UNITTEST_END_SIMPLE
+}