tool_urlglob: clean up used memory on errors better

Previously it had to realloc the pattern array to store the last entry
even when that last entry triggered an error and could be only half
filled in.

Also cleaned up for readability and better reallocs for sets.

Reported-by: letshack9707 on hackerone
Closes #19614
This commit is contained in:
Daniel Stenberg 2025-11-20 09:58:14 +01:00
parent c5ef882e90
commit 5f273fdddf
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
2 changed files with 121 additions and 84 deletions

View file

@ -40,21 +40,24 @@ static CURLcode globerror(struct URLGlob *glob, const char *err,
static CURLcode glob_fixed(struct URLGlob *glob, char *fixed, size_t len) static CURLcode glob_fixed(struct URLGlob *glob, char *fixed, size_t len)
{ {
struct URLPattern *pat = &glob->pattern[glob->size]; struct URLPattern *pat = &glob->pattern[glob->pnum];
pat->type = GLOB_SET; pat->type = GLOB_SET;
pat->c.set.size = 1;
pat->c.set.idx = 0;
pat->globindex = -1; pat->globindex = -1;
pat->c.set.size = 0;
pat->c.set.idx = 0;
pat->c.set.elem = malloc(sizeof(char *)); pat->c.set.elem = malloc(sizeof(char *));
if(!pat->c.set.elem) if(!pat->c.set.elem)
return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY); return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY);
pat->c.set.elem[0] = memdup0(fixed, len); pat->c.set.elem[0] = memdup0(fixed, len);
if(!pat->c.set.elem[0]) if(!pat->c.set.elem[0]) {
tool_safefree(pat->c.set.elem);
return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY); return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY);
}
pat->c.set.palloc = 1;
pat->c.set.size = 1;
return CURLE_OK; return CURLE_OK;
} }
@ -98,59 +101,66 @@ static CURLcode glob_set(struct URLGlob *glob, const char **patternp,
const char *pattern = *patternp; const char *pattern = *patternp;
const char *opattern = pattern; const char *opattern = pattern;
size_t opos = *posp-1; size_t opos = *posp-1;
CURLcode result = CURLE_OK;
pat = &glob->pattern[glob->size]; size_t size = 0;
/* patterns 0,1,2,... correspond to size=1,3,5,... */ char **elem = NULL;
pat->type = GLOB_SET; size_t palloc = 0; /* start with this */
pat->c.set.size = 0;
pat->c.set.idx = 0;
pat->c.set.elem = NULL;
pat->globindex = globindex;
while(!done) { while(!done) {
switch(*pattern) { switch(*pattern) {
case '\0': /* URL ended while set was still open */ case '\0': /* URL ended while set was still open */
return globerror(glob, "unmatched brace", opos, CURLE_URL_MALFORMAT); result = globerror(glob, "unmatched brace", opos, CURLE_URL_MALFORMAT);
goto error;
case '{': case '{':
case '[': /* no nested expressions at this time */ case '[': /* no nested expressions at this time */
return globerror(glob, "nested brace", *posp, CURLE_URL_MALFORMAT); result = globerror(glob, "nested brace", *posp, CURLE_URL_MALFORMAT);
goto error;
case '}': /* set element completed */ case '}': /* set element completed */
if(opattern == pattern) if(opattern == pattern) {
return globerror(glob, "empty string within braces", *posp, result = globerror(glob, "empty string within braces", *posp,
CURLE_URL_MALFORMAT); CURLE_URL_MALFORMAT);
goto error;
}
/* add 1 to size since it will be incremented below */ /* add 1 to size since it will be incremented below */
if(multiply(amount, pat->c.set.size + 1)) if(multiply(amount, size + 1)) {
return globerror(glob, "range overflow", 0, CURLE_URL_MALFORMAT); result = globerror(glob, "range overflow", 0, CURLE_URL_MALFORMAT);
goto error;
}
done = TRUE; done = TRUE;
FALLTHROUGH(); FALLTHROUGH();
case ',': case ',':
if(pat->c.set.elem) { if(size >= 100000)
char **arr; return globerror(glob, "range overflow", 0, CURLE_URL_MALFORMAT);
if(pat->c.set.size >= (curl_off_t)(SIZE_MAX/(sizeof(char *)))) if(!palloc) {
return globerror(glob, "range overflow", 0, CURLE_URL_MALFORMAT); palloc = 5; /* a reasonable default */
elem = malloc(palloc * sizeof(char *));
arr = realloc(pat->c.set.elem, (size_t)(pat->c.set.size + 1) * }
sizeof(char *)); else if(size >= palloc) {
if(!arr) char **arr = realloc(elem, palloc * 2 * sizeof(char *));
return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY); if(!arr) {
result = globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY);
pat->c.set.elem = arr; goto error;
}
elem = arr;
palloc *= 2;
} }
else
pat->c.set.elem = malloc(sizeof(char *));
if(!pat->c.set.elem) if(!elem) {
return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY); result = globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY);
goto error;
}
pat->c.set.elem[pat->c.set.size] = strdup(curlx_dyn_ptr(&glob->buf) ? elem[size] =
curlx_dyn_ptr(&glob->buf): ""); strdup(curlx_dyn_ptr(&glob->buf) ? curlx_dyn_ptr(&glob->buf) : "");
if(!pat->c.set.elem[pat->c.set.size]) if(!elem[size]) {
return globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY); result = globerror(glob, NULL, 0, CURLE_OUT_OF_MEMORY);
++pat->c.set.size; goto error;
}
++size;
curlx_dyn_reset(&glob->buf); curlx_dyn_reset(&glob->buf);
++pattern; ++pattern;
@ -159,8 +169,9 @@ static CURLcode glob_set(struct URLGlob *glob, const char **patternp,
break; break;
case ']': /* illegal closing bracket */ case ']': /* illegal closing bracket */
return globerror(glob, "unexpected close bracket", *posp, result = globerror(glob, "unexpected close bracket", *posp,
CURLE_URL_MALFORMAT); CURLE_URL_MALFORMAT);
goto error;
case '\\': /* escaped character, skip '\' */ case '\\': /* escaped character, skip '\' */
if(pattern[1]) { if(pattern[1]) {
@ -170,14 +181,33 @@ static CURLcode glob_set(struct URLGlob *glob, const char **patternp,
FALLTHROUGH(); FALLTHROUGH();
default: default:
/* copy character to set element */ /* copy character to set element */
if(curlx_dyn_addn(&glob->buf, pattern++, 1)) if(curlx_dyn_addn(&glob->buf, pattern++, 1)) {
return CURLE_OUT_OF_MEMORY; result = CURLE_OUT_OF_MEMORY;
goto error;
}
++(*posp); ++(*posp);
} }
} }
*patternp = pattern; /* return with the new position */ *patternp = pattern; /* return with the new position */
pat = &glob->pattern[glob->pnum];
pat->type = GLOB_SET;
pat->globindex = globindex;
pat->c.set.elem = elem;
pat->c.set.size = size;
pat->c.set.idx = 0;
pat->c.set.palloc = palloc;
return CURLE_OK; return CURLE_OK;
error:
{
size_t i;
for(i = 0; i < size; i++)
tool_safefree(elem[i]);
}
free(elem);
return result;
} }
static CURLcode glob_range(struct URLGlob *glob, const char **patternp, static CURLcode glob_range(struct URLGlob *glob, const char **patternp,
@ -194,7 +224,7 @@ static CURLcode glob_range(struct URLGlob *glob, const char **patternp,
const char *pattern = *patternp; const char *pattern = *patternp;
const char *c; const char *c;
pat = &glob->pattern[glob->size]; pat = &glob->pattern[glob->pnum];
pat->globindex = globindex; pat->globindex = globindex;
if(ISALPHA(*pattern)) { if(ISALPHA(*pattern)) {
@ -349,6 +379,25 @@ static bool peek_ipv6(const char *str, size_t *skip)
return rc ? FALSE : TRUE; return rc ? FALSE : TRUE;
} }
static CURLcode add_glob(struct URLGlob *glob, size_t pos)
{
DEBUGASSERT(glob->pattern[glob->pnum].type);
if(++glob->pnum >= glob->palloc) {
struct URLPattern *np = NULL;
glob->palloc *= 2;
if(glob->pnum < 255) { /* avoid ridiculous amounts */
np = realloc(glob->pattern, glob->palloc * sizeof(struct URLPattern));
if(!np)
return globerror(glob, NULL, pos, CURLE_OUT_OF_MEMORY);
}
else
return globerror(glob, "too many {} sets", pos, CURLE_URL_MALFORMAT);
glob->pattern = np;
}
return CURLE_OK;
}
static CURLcode glob_parse(struct URLGlob *glob, const char *pattern, static CURLcode glob_parse(struct URLGlob *glob, const char *pattern,
size_t pos, curl_off_t *amount) size_t pos, curl_off_t *amount)
{ {
@ -381,8 +430,8 @@ static CURLcode glob_parse(struct URLGlob *glob, const char *pattern,
/* only allow \ to escape known "special letters" */ /* only allow \ to escape known "special letters" */
if(*pattern == '\\' && if(*pattern == '\\' &&
(*(pattern + 1) == '{' || *(pattern + 1) == '[' || (pattern[1] == '{' || pattern[1] == '[' ||
*(pattern + 1) == '}' || *(pattern + 1) == ']') ) { pattern[1] == '}' || pattern[1] == ']') ) {
/* escape character, skip '\' */ /* escape character, skip '\' */
++pattern; ++pattern;
@ -397,41 +446,30 @@ static CURLcode glob_parse(struct URLGlob *glob, const char *pattern,
/* we got a literal string, add it as a single-item list */ /* we got a literal string, add it as a single-item list */
res = glob_fixed(glob, curlx_dyn_ptr(&glob->buf), res = glob_fixed(glob, curlx_dyn_ptr(&glob->buf),
curlx_dyn_len(&glob->buf)); curlx_dyn_len(&glob->buf));
if(!res)
res = add_glob(glob, pos);
curlx_dyn_reset(&glob->buf); curlx_dyn_reset(&glob->buf);
} }
else { else {
switch(*pattern) { if(!*pattern) /* done */
case '\0': /* done */
break; break;
else if(*pattern =='{') {
case '{':
/* process set pattern */ /* process set pattern */
pattern++; pattern++;
pos++; pos++;
res = glob_set(glob, &pattern, &pos, amount, globindex++); res = glob_set(glob, &pattern, &pos, amount, globindex++);
break; if(!res)
res = add_glob(glob, pos);
case '[': }
else if(*pattern == '[') {
/* process range pattern */ /* process range pattern */
pattern++; pattern++;
pos++; pos++;
res = glob_range(glob, &pattern, &pos, amount, globindex++); res = glob_range(glob, &pattern, &pos, amount, globindex++);
break; if(!res)
res = add_glob(glob, pos);
} }
} }
if(++glob->size >= glob->palloc) {
struct URLPattern *np = NULL;
glob->palloc *= 2;
if(glob->size < 255) { /* avoid ridiculous amounts */
np = realloc(glob->pattern, glob->palloc * sizeof(struct URLPattern));
if(!np)
return globerror(glob, NULL, pos, CURLE_OUT_OF_MEMORY);
}
else
return globerror(glob, "too many {} sets", pos, CURLE_URL_MALFORMAT);
glob->pattern = np;
}
} }
return res; return res;
} }
@ -459,9 +497,7 @@ CURLcode glob_url(struct URLGlob *glob, char *url, curl_off_t *urlnum,
glob->palloc = 2; glob->palloc = 2;
res = glob_parse(glob, url, 1, &amount); res = glob_parse(glob, url, 1, &amount);
if(!res) if(res) {
*urlnum = amount;
else {
if(error && glob->error) { if(error && glob->error) {
char text[512]; char text[512];
const char *t; const char *t;
@ -477,25 +513,24 @@ CURLcode glob_url(struct URLGlob *glob, char *url, curl_off_t *urlnum,
/* send error description to the error-stream */ /* send error description to the error-stream */
curl_mfprintf(error, "curl: (%d) %s\n", res, t); curl_mfprintf(error, "curl: (%d) %s\n", res, t);
} }
/* it failed, we cleanup */
glob_cleanup(glob);
*urlnum = 1; *urlnum = 1;
return res; return res;
} }
*urlnum = amount;
return CURLE_OK; return CURLE_OK;
} }
void glob_cleanup(struct URLGlob *glob) void glob_cleanup(struct URLGlob *glob)
{ {
size_t i; size_t i;
curl_off_t elem;
if(glob->pattern) { if(glob->pattern) {
for(i = 0; i < glob->size; i++) { for(i = 0; i < glob->pnum; i++) {
DEBUGASSERT(glob->pattern[i].type);
if((glob->pattern[i].type == GLOB_SET) && if((glob->pattern[i].type == GLOB_SET) &&
(glob->pattern[i].c.set.elem)) { (glob->pattern[i].c.set.elem)) {
for(elem = glob->pattern[i].c.set.size - 1; elem >= 0; --elem) curl_off_t elem;
for(elem = 0; elem < glob->pattern[i].c.set.size; elem++)
tool_safefree(glob->pattern[i].c.set.elem[elem]); tool_safefree(glob->pattern[i].c.set.elem[elem]);
tool_safefree(glob->pattern[i].c.set.elem); tool_safefree(glob->pattern[i].c.set.elem);
} }
@ -521,9 +556,9 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob)
/* implement a counter over the index ranges of all patterns, starting /* implement a counter over the index ranges of all patterns, starting
with the rightmost pattern */ with the rightmost pattern */
for(i = 0; carry && (i < glob->size); i++) { for(i = 0; carry && (i < glob->pnum); i++) {
carry = FALSE; carry = FALSE;
pat = &glob->pattern[glob->size - 1 - i]; pat = &glob->pattern[glob->pnum - 1 - i];
switch(pat->type) { switch(pat->type) {
case GLOB_SET: case GLOB_SET:
if((pat->c.set.elem) && (++pat->c.set.idx == pat->c.set.size)) { if((pat->c.set.elem) && (++pat->c.set.idx == pat->c.set.size)) {
@ -555,7 +590,7 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob)
} }
} }
for(i = 0; i < glob->size; ++i) { for(i = 0; i < glob->pnum; ++i) {
pat = &glob->pattern[i]; pat = &glob->pattern[i];
switch(pat->type) { switch(pat->type) {
case GLOB_SET: case GLOB_SET:
@ -581,7 +616,8 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob)
} }
} }
*globbed = strdup(curlx_dyn_ptr(&glob->buf)); *globbed =
strdup(curlx_dyn_ptr(&glob->buf) ? curlx_dyn_ptr(&glob->buf) : "");
if(!*globbed) if(!*globbed)
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
@ -605,11 +641,11 @@ CURLcode glob_match_url(char **output, const char *filename,
curl_off_t num; curl_off_t num;
struct URLPattern *pat = NULL; struct URLPattern *pat = NULL;
filename++; filename++;
if(!curlx_str_number(&filename, &num, glob->size) && num) { if(!curlx_str_number(&filename, &num, glob->pnum) && num) {
size_t i; size_t i;
num--; /* make it zero based */ num--; /* make it zero based */
/* find the correct glob entry */ /* find the correct glob entry */
for(i = 0; i < glob->size; i++) { for(i = 0; i < glob->pnum; i++) {
if(glob->pattern[i].globindex == (int)num) { if(glob->pattern[i].globindex == (int)num) {
pat = &glob->pattern[i]; pat = &glob->pattern[i];
break; break;

View file

@ -40,6 +40,7 @@ struct URLPattern {
char **elem; char **elem;
curl_off_t size; curl_off_t size;
curl_off_t idx; curl_off_t idx;
size_t palloc; /* elem entries allocated */
} set; } set;
struct { struct {
int min; int min;
@ -64,7 +65,7 @@ struct URLGlob {
struct dynbuf buf; struct dynbuf buf;
struct URLPattern *pattern; struct URLPattern *pattern;
size_t palloc; /* number of pattern entries allocated */ size_t palloc; /* number of pattern entries allocated */
size_t size; size_t pnum; /* number of patterns used */
char beenhere; char beenhere;
const char *error; /* error message */ const char *error; /* error message */
size_t pos; /* column position of error or 0 */ size_t pos; /* column position of error or 0 */