mime: only allow 40 levels of calls

To avoid problems when doing insane things.

Closes #21384
This commit is contained in:
Daniel Stenberg 2026-04-20 10:28:28 +02:00
parent 5448495cfd
commit d087a7e4ec
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -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