1
0
mirror of https://github.com/moparisthebest/curl synced 2024-08-13 17:03:50 -04:00

ftp: make wc_statemach loop instead of recurse

CVE-2020-8285

Fixes #6255
Bug: https://curl.se/docs/CVE-2020-8285.html
Reported-by: xnynx on github
This commit is contained in:
Daniel Stenberg 2020-11-28 00:27:21 +01:00
parent ec9cc725d5
commit 69a358f218
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2

224
lib/ftp.c
View File

@ -3800,129 +3800,131 @@ static CURLcode init_wc_data(struct connectdata *conn)
return result;
}
/* This is called recursively */
static CURLcode wc_statemach(struct connectdata *conn)
{
struct WildcardData * const wildcard = &(conn->data->wildcard);
CURLcode result = CURLE_OK;
switch(wildcard->state) {
case CURLWC_INIT:
result = init_wc_data(conn);
if(wildcard->state == CURLWC_CLEAN)
/* only listing! */
break;
wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING;
break;
case CURLWC_MATCHING: {
/* In this state is LIST response successfully parsed, so lets restore
previous WRITEFUNCTION callback and WRITEDATA pointer */
struct ftp_wc *ftpwc = wildcard->protdata;
conn->data->set.fwrite_func = ftpwc->backup.write_function;
conn->data->set.out = ftpwc->backup.file_descriptor;
ftpwc->backup.write_function = ZERO_NULL;
ftpwc->backup.file_descriptor = NULL;
wildcard->state = CURLWC_DOWNLOADING;
if(Curl_ftp_parselist_geterror(ftpwc->parser)) {
/* error found in LIST parsing */
wildcard->state = CURLWC_CLEAN;
return wc_statemach(conn);
}
if(wildcard->filelist.size == 0) {
/* no corresponding file */
wildcard->state = CURLWC_CLEAN;
return CURLE_REMOTE_FILE_NOT_FOUND;
}
return wc_statemach(conn);
}
case CURLWC_DOWNLOADING: {
/* filelist has at least one file, lets get first one */
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct curl_fileinfo *finfo = wildcard->filelist.head->ptr;
struct FTP *ftp = conn->data->req.p.ftp;
char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename);
if(!tmp_path)
return CURLE_OUT_OF_MEMORY;
/* switch default ftp->path and tmp_path */
free(ftp->pathalloc);
ftp->pathalloc = ftp->path = tmp_path;
infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename);
if(conn->data->set.chunk_bgn) {
long userresponse;
Curl_set_in_callback(conn->data, true);
userresponse = conn->data->set.chunk_bgn(
finfo, wildcard->customptr, (int)wildcard->filelist.size);
Curl_set_in_callback(conn->data, false);
switch(userresponse) {
case CURL_CHUNK_BGN_FUNC_SKIP:
infof(conn->data, "Wildcard - \"%s\" skipped by user\n",
finfo->filename);
wildcard->state = CURLWC_SKIP;
return wc_statemach(conn);
case CURL_CHUNK_BGN_FUNC_FAIL:
return CURLE_CHUNK_FAILED;
}
}
if(finfo->filetype != CURLFILETYPE_FILE) {
wildcard->state = CURLWC_SKIP;
return wc_statemach(conn);
}
if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE)
ftpc->known_filesize = finfo->size;
result = ftp_parse_url_path(conn);
if(result)
for(;;) {
switch(wildcard->state) {
case CURLWC_INIT:
result = init_wc_data(conn);
if(wildcard->state == CURLWC_CLEAN)
/* only listing! */
return result;
wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING;
return result;
/* we don't need the Curl_fileinfo of first file anymore */
Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
case CURLWC_MATCHING: {
/* In this state is LIST response successfully parsed, so lets restore
previous WRITEFUNCTION callback and WRITEDATA pointer */
struct ftp_wc *ftpwc = wildcard->protdata;
conn->data->set.fwrite_func = ftpwc->backup.write_function;
conn->data->set.out = ftpwc->backup.file_descriptor;
ftpwc->backup.write_function = ZERO_NULL;
ftpwc->backup.file_descriptor = NULL;
wildcard->state = CURLWC_DOWNLOADING;
if(wildcard->filelist.size == 0) { /* remains only one file to down. */
wildcard->state = CURLWC_CLEAN;
/* after that will be ftp_do called once again and no transfer
will be done because of CURLWC_CLEAN state */
return CURLE_OK;
if(Curl_ftp_parselist_geterror(ftpwc->parser)) {
/* error found in LIST parsing */
wildcard->state = CURLWC_CLEAN;
continue;
}
if(wildcard->filelist.size == 0) {
/* no corresponding file */
wildcard->state = CURLWC_CLEAN;
return CURLE_REMOTE_FILE_NOT_FOUND;
}
continue;
}
} break;
case CURLWC_SKIP: {
if(conn->data->set.chunk_end) {
Curl_set_in_callback(conn->data, true);
conn->data->set.chunk_end(conn->data->wildcard.customptr);
Curl_set_in_callback(conn->data, false);
case CURLWC_DOWNLOADING: {
/* filelist has at least one file, lets get first one */
struct ftp_conn *ftpc = &conn->proto.ftpc;
struct curl_fileinfo *finfo = wildcard->filelist.head->ptr;
struct FTP *ftp = conn->data->req.p.ftp;
char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename);
if(!tmp_path)
return CURLE_OUT_OF_MEMORY;
/* switch default ftp->path and tmp_path */
free(ftp->pathalloc);
ftp->pathalloc = ftp->path = tmp_path;
infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename);
if(conn->data->set.chunk_bgn) {
long userresponse;
Curl_set_in_callback(conn->data, true);
userresponse = conn->data->set.chunk_bgn(
finfo, wildcard->customptr, (int)wildcard->filelist.size);
Curl_set_in_callback(conn->data, false);
switch(userresponse) {
case CURL_CHUNK_BGN_FUNC_SKIP:
infof(conn->data, "Wildcard - \"%s\" skipped by user\n",
finfo->filename);
wildcard->state = CURLWC_SKIP;
continue;
case CURL_CHUNK_BGN_FUNC_FAIL:
return CURLE_CHUNK_FAILED;
}
}
if(finfo->filetype != CURLFILETYPE_FILE) {
wildcard->state = CURLWC_SKIP;
continue;
}
if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE)
ftpc->known_filesize = finfo->size;
result = ftp_parse_url_path(conn);
if(result)
return result;
/* we don't need the Curl_fileinfo of first file anymore */
Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
if(wildcard->filelist.size == 0) { /* remains only one file to down. */
wildcard->state = CURLWC_CLEAN;
/* after that will be ftp_do called once again and no transfer
will be done because of CURLWC_CLEAN state */
return CURLE_OK;
}
return result;
}
case CURLWC_SKIP: {
if(conn->data->set.chunk_end) {
Curl_set_in_callback(conn->data, true);
conn->data->set.chunk_end(conn->data->wildcard.customptr);
Curl_set_in_callback(conn->data, false);
}
Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
wildcard->state = (wildcard->filelist.size == 0) ?
CURLWC_CLEAN : CURLWC_DOWNLOADING;
continue;
}
case CURLWC_CLEAN: {
struct ftp_wc *ftpwc = wildcard->protdata;
result = CURLE_OK;
if(ftpwc)
result = Curl_ftp_parselist_geterror(ftpwc->parser);
wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE;
return result;
}
case CURLWC_DONE:
case CURLWC_ERROR:
case CURLWC_CLEAR:
if(wildcard->dtor)
wildcard->dtor(wildcard->protdata);
return result;
}
Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
wildcard->state = (wildcard->filelist.size == 0) ?
CURLWC_CLEAN : CURLWC_DOWNLOADING;
return wc_statemach(conn);
}
case CURLWC_CLEAN: {
struct ftp_wc *ftpwc = wildcard->protdata;
result = CURLE_OK;
if(ftpwc)
result = Curl_ftp_parselist_geterror(ftpwc->parser);
wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE;
} break;
case CURLWC_DONE:
case CURLWC_ERROR:
case CURLWC_CLEAR:
if(wildcard->dtor)
wildcard->dtor(wildcard->protdata);
break;
}
return result;
/* UNREACHABLE */
}
/***********************************************************************