From d087a7e4ecbe72aeecd9b171fdf3fab95c65dfb5 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 20 Apr 2026 10:28:28 +0200 Subject: [PATCH] mime: only allow 40 levels of calls To avoid problems when doing insane things. Closes #21384 --- lib/mime.c | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/mime.c b/lib/mime.c index 27a2a85c8b..b7e51aae4e 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -44,11 +44,14 @@ struct Curl_easy; #include "slist.h" #include "curlx/dynbuf.h" +#define MAX_MIME_LEVELS 40 /* avoid deep nesting */ + #define READ_ERROR ((size_t)-1) #define STOP_FILLING ((size_t)-2) static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, - void *instream, bool *hasread); + void *instream, bool *hasread, + size_t call_depth); static curl_off_t mime_size(curl_mimepart *part); /* Quoted-printable character class table. @@ -687,11 +690,16 @@ 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) + +static size_t read_part_content(curl_mimepart *part, char *buffer, + size_t bufsize, bool *hasread, + size_t call_depth) { size_t sz = 0; + if(++call_depth > MAX_MIME_LEVELS) + return READ_ERROR; + switch(part->lastreadstatus) { case 0: case CURL_READFUNC_ABORT: @@ -714,7 +722,8 @@ static size_t read_part_content(curl_mimepart *part, * 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); + sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread, + call_depth); break; case MIMEKIND_FILE: if(part->fp && feof(part->fp)) @@ -753,13 +762,17 @@ static size_t read_part_content(curl_mimepart *part, /* Read and encode part content. */ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, - size_t bufsize, bool *hasread) + size_t bufsize, bool *hasread, + size_t call_depth) { 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. */ @@ -792,7 +805,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); + sizeof(st->buf) - st->bufend, hasread, call_depth); switch(sz) { case 0: ateof = TRUE; @@ -813,12 +826,15 @@ 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) + char *buffer, size_t bufsize, bool *hasread, + size_t call_depth) { size_t cursize = 0; - /* Readback from part. */ + if(++call_depth > MAX_MIME_LEVELS) + return READ_ERROR; + /* Readback from part. */ while(bufsize) { size_t sz = 0; struct curl_slist *hdr = (struct curl_slist *)part->state.ptr; @@ -861,9 +877,10 @@ static size_t readback_part(curl_mimepart *part, break; case MIMESTATE_CONTENT: if(part->encoder) - sz = read_encoded_part_content(part, buffer, bufsize, hasread); + sz = read_encoded_part_content(part, buffer, bufsize, hasread, + call_depth); else - sz = read_part_content(part, buffer, bufsize, hasread); + sz = read_part_content(part, buffer, bufsize, hasread, call_depth); switch(sz) { case 0: mimesetstate(&part->state, MIMESTATE_END, NULL); @@ -897,12 +914,16 @@ 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) + void *instream, bool *hasread, + size_t call_depth) { 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; @@ -937,7 +958,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); + sz = readback_part(part, buffer, nitems, hasread, call_depth); switch(sz) { case CURL_READFUNC_ABORT: case CURL_READFUNC_PAUSE: @@ -1506,7 +1527,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); + ret = readback_part(part, buffer, nitems, &hasread, 0); /* * 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