9b977c
From 22b3d1cf0216f4369f01678c587da265c2e465af Mon Sep 17 00:00:00 2001
9b977c
From: Daniel Stenberg <daniel@haxx.se>
9b977c
Date: Sat, 28 Nov 2020 00:27:21 +0100
9b977c
Subject: [PATCH] ftp: make wc_statemach loop instead of recurse
9b977c
9b977c
CVE-2020-8285
9b977c
9b977c
Fixes #6255
9b977c
Bug: https://curl.se/docs/CVE-2020-8285.html
9b977c
Reported-by: xnynx on github
9b977c
9b977c
Upstream-commit: 69a358f2186e04cf44698b5100332cbf1ee7f01d
9b977c
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
9b977c
---
9b977c
 lib/ftp.c | 204 +++++++++++++++++++++++++++---------------------------
9b977c
 1 file changed, 103 insertions(+), 101 deletions(-)
9b977c
9b977c
diff --git a/lib/ftp.c b/lib/ftp.c
9b977c
index 7dbf080..482ab3a 100644
9b977c
--- a/lib/ftp.c
9b977c
+++ b/lib/ftp.c
9b977c
@@ -3786,130 +3786,132 @@ static CURLcode init_wc_data(struct connectdata *conn)
9b977c
   return result;
9b977c
 }
9b977c
 
9b977c
-/* This is called recursively */
9b977c
 static CURLcode wc_statemach(struct connectdata *conn)
9b977c
 {
9b977c
   struct WildcardData * const wildcard = &(conn->data->wildcard);
9b977c
   CURLcode result = CURLE_OK;
9b977c
 
9b977c
-  switch(wildcard->state) {
9b977c
-  case CURLWC_INIT:
9b977c
-    result = init_wc_data(conn);
9b977c
-    if(wildcard->state == CURLWC_CLEAN)
9b977c
-      /* only listing! */
9b977c
-      break;
9b977c
-    wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING;
9b977c
-    break;
9b977c
+  for(;;) {
9b977c
+    switch(wildcard->state) {
9b977c
+    case CURLWC_INIT:
9b977c
+      result = init_wc_data(conn);
9b977c
+      if(wildcard->state == CURLWC_CLEAN)
9b977c
+        /* only listing! */
9b977c
+        return result;
9b977c
+      wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING;
9b977c
+      return result;
9b977c
 
9b977c
-  case CURLWC_MATCHING: {
9b977c
-    /* In this state is LIST response successfully parsed, so lets restore
9b977c
-       previous WRITEFUNCTION callback and WRITEDATA pointer */
9b977c
-    struct ftp_wc *ftpwc = wildcard->protdata;
9b977c
-    conn->data->set.fwrite_func = ftpwc->backup.write_function;
9b977c
-    conn->data->set.out = ftpwc->backup.file_descriptor;
9b977c
-    ftpwc->backup.write_function = ZERO_NULL;
9b977c
-    ftpwc->backup.file_descriptor = NULL;
9b977c
-    wildcard->state = CURLWC_DOWNLOADING;
9b977c
-
9b977c
-    if(Curl_ftp_parselist_geterror(ftpwc->parser)) {
9b977c
-      /* error found in LIST parsing */
9b977c
-      wildcard->state = CURLWC_CLEAN;
9b977c
-      return wc_statemach(conn);
9b977c
-    }
9b977c
-    if(wildcard->filelist.size == 0) {
9b977c
-      /* no corresponding file */
9b977c
-      wildcard->state = CURLWC_CLEAN;
9b977c
-      return CURLE_REMOTE_FILE_NOT_FOUND;
9b977c
+    case CURLWC_MATCHING: {
9b977c
+      /* In this state is LIST response successfully parsed, so lets restore
9b977c
+         previous WRITEFUNCTION callback and WRITEDATA pointer */
9b977c
+      struct ftp_wc *ftpwc = wildcard->protdata;
9b977c
+      conn->data->set.fwrite_func = ftpwc->backup.write_function;
9b977c
+      conn->data->set.out = ftpwc->backup.file_descriptor;
9b977c
+      ftpwc->backup.write_function = ZERO_NULL;
9b977c
+      ftpwc->backup.file_descriptor = NULL;
9b977c
+      wildcard->state = CURLWC_DOWNLOADING;
9b977c
+
9b977c
+      if(Curl_ftp_parselist_geterror(ftpwc->parser)) {
9b977c
+        /* error found in LIST parsing */
9b977c
+        wildcard->state = CURLWC_CLEAN;
9b977c
+        continue;
9b977c
+      }
9b977c
+      if(wildcard->filelist.size == 0) {
9b977c
+        /* no corresponding file */
9b977c
+        wildcard->state = CURLWC_CLEAN;
9b977c
+        return CURLE_REMOTE_FILE_NOT_FOUND;
9b977c
+      }
9b977c
+      continue;
9b977c
     }
9b977c
-    return wc_statemach(conn);
9b977c
-  }
9b977c
 
9b977c
-  case CURLWC_DOWNLOADING: {
9b977c
-    /* filelist has at least one file, lets get first one */
9b977c
-    struct ftp_conn *ftpc = &conn->proto.ftpc;
9b977c
-    struct curl_fileinfo *finfo = wildcard->filelist.head->ptr;
9b977c
+    case CURLWC_DOWNLOADING: {
9b977c
+      /* filelist has at least one file, lets get first one */
9b977c
+      struct ftp_conn *ftpc = &conn->proto.ftpc;
9b977c
+      struct curl_fileinfo *finfo = wildcard->filelist.head->ptr;
9b977c
 
9b977c
-    char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename);
9b977c
-    if(!tmp_path)
9b977c
-      return CURLE_OUT_OF_MEMORY;
9b977c
+      char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename);
9b977c
+      if(!tmp_path)
9b977c
+        return CURLE_OUT_OF_MEMORY;
9b977c
 
9b977c
-    /* switch default "state.pathbuffer" and tmp_path, good to see
9b977c
-       ftp_parse_url_path function to understand this trick */
9b977c
-    Curl_safefree(conn->data->state.pathbuffer);
9b977c
-    conn->data->state.pathbuffer = tmp_path;
9b977c
-    conn->data->state.path = tmp_path;
9b977c
-
9b977c
-    infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename);
9b977c
-    if(conn->data->set.chunk_bgn) {
9b977c
-      long userresponse;
9b977c
-      Curl_set_in_callback(conn->data, true);
9b977c
-      userresponse = conn->data->set.chunk_bgn(
9b977c
-        finfo, wildcard->customptr, (int)wildcard->filelist.size);
9b977c
-      Curl_set_in_callback(conn->data, false);
9b977c
-      switch(userresponse) {
9b977c
-      case CURL_CHUNK_BGN_FUNC_SKIP:
9b977c
-        infof(conn->data, "Wildcard - \"%s\" skipped by user\n",
9b977c
-              finfo->filename);
9b977c
-        wildcard->state = CURLWC_SKIP;
9b977c
-        return wc_statemach(conn);
9b977c
-      case CURL_CHUNK_BGN_FUNC_FAIL:
9b977c
-        return CURLE_CHUNK_FAILED;
9b977c
+      /* switch default "state.pathbuffer" and tmp_path, good to see
9b977c
+         ftp_parse_url_path function to understand this trick */
9b977c
+      Curl_safefree(conn->data->state.pathbuffer);
9b977c
+      conn->data->state.pathbuffer = tmp_path;
9b977c
+      conn->data->state.path = tmp_path;
9b977c
+
9b977c
+      infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename);
9b977c
+      if(conn->data->set.chunk_bgn) {
9b977c
+        long userresponse;
9b977c
+        Curl_set_in_callback(conn->data, true);
9b977c
+        userresponse = conn->data->set.chunk_bgn(
9b977c
+          finfo, wildcard->customptr, (int)wildcard->filelist.size);
9b977c
+        Curl_set_in_callback(conn->data, false);
9b977c
+        switch(userresponse) {
9b977c
+        case CURL_CHUNK_BGN_FUNC_SKIP:
9b977c
+          infof(conn->data, "Wildcard - \"%s\" skipped by user\n",
9b977c
+                finfo->filename);
9b977c
+          wildcard->state = CURLWC_SKIP;
9b977c
+          continue;
9b977c
+        case CURL_CHUNK_BGN_FUNC_FAIL:
9b977c
+          return CURLE_CHUNK_FAILED;
9b977c
+        }
9b977c
       }
9b977c
-    }
9b977c
 
9b977c
-    if(finfo->filetype != CURLFILETYPE_FILE) {
9b977c
-      wildcard->state = CURLWC_SKIP;
9b977c
-      return wc_statemach(conn);
9b977c
-    }
9b977c
+      if(finfo->filetype != CURLFILETYPE_FILE) {
9b977c
+        wildcard->state = CURLWC_SKIP;
9b977c
+        continue;
9b977c
+      }
9b977c
 
9b977c
-    if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE)
9b977c
-      ftpc->known_filesize = finfo->size;
9b977c
+      if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE)
9b977c
+        ftpc->known_filesize = finfo->size;
9b977c
 
9b977c
-    result = ftp_parse_url_path(conn);
9b977c
-    if(result)
9b977c
-      return result;
9b977c
+      result = ftp_parse_url_path(conn);
9b977c
+      if(result)
9b977c
+        return result;
9b977c
 
9b977c
-    /* we don't need the Curl_fileinfo of first file anymore */
9b977c
-    Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
9b977c
+      /* we don't need the Curl_fileinfo of first file anymore */
9b977c
+      Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
9b977c
 
9b977c
-    if(wildcard->filelist.size == 0) { /* remains only one file to down. */
9b977c
-      wildcard->state = CURLWC_CLEAN;
9b977c
-      /* after that will be ftp_do called once again and no transfer
9b977c
-         will be done because of CURLWC_CLEAN state */
9b977c
-      return CURLE_OK;
9b977c
+      if(wildcard->filelist.size == 0) { /* remains only one file to down. */
9b977c
+        wildcard->state = CURLWC_CLEAN;
9b977c
+        /* after that will be ftp_do called once again and no transfer
9b977c
+           will be done because of CURLWC_CLEAN state */
9b977c
+        return CURLE_OK;
9b977c
+      }
9b977c
+      return result;
9b977c
     }
9b977c
-  } break;
9b977c
 
9b977c
-  case CURLWC_SKIP: {
9b977c
-    if(conn->data->set.chunk_end) {
9b977c
-      Curl_set_in_callback(conn->data, true);
9b977c
-      conn->data->set.chunk_end(conn->data->wildcard.customptr);
9b977c
-      Curl_set_in_callback(conn->data, false);
9b977c
+    case CURLWC_SKIP: {
9b977c
+      if(conn->data->set.chunk_end) {
9b977c
+        Curl_set_in_callback(conn->data, true);
9b977c
+        conn->data->set.chunk_end(conn->data->wildcard.customptr);
9b977c
+        Curl_set_in_callback(conn->data, false);
9b977c
+      }
9b977c
+      Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
9b977c
+      wildcard->state = (wildcard->filelist.size == 0) ?
9b977c
+        CURLWC_CLEAN : CURLWC_DOWNLOADING;
9b977c
+      continue;
9b977c
     }
9b977c
-    Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL);
9b977c
-    wildcard->state = (wildcard->filelist.size == 0) ?
9b977c
-                      CURLWC_CLEAN : CURLWC_DOWNLOADING;
9b977c
-    return wc_statemach(conn);
9b977c
-  }
9b977c
 
9b977c
-  case CURLWC_CLEAN: {
9b977c
-    struct ftp_wc *ftpwc = wildcard->protdata;
9b977c
-    result = CURLE_OK;
9b977c
-    if(ftpwc)
9b977c
-      result = Curl_ftp_parselist_geterror(ftpwc->parser);
9b977c
+    case CURLWC_CLEAN: {
9b977c
+      struct ftp_wc *ftpwc = wildcard->protdata;
9b977c
+      result = CURLE_OK;
9b977c
+      if(ftpwc)
9b977c
+        result = Curl_ftp_parselist_geterror(ftpwc->parser);
9b977c
 
9b977c
-    wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE;
9b977c
-  } break;
9b977c
+      wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE;
9b977c
+      return result;
9b977c
+    }
9b977c
 
9b977c
-  case CURLWC_DONE:
9b977c
-  case CURLWC_ERROR:
9b977c
-  case CURLWC_CLEAR:
9b977c
-    if(wildcard->dtor)
9b977c
-      wildcard->dtor(wildcard->protdata);
9b977c
-    break;
9b977c
+    case CURLWC_DONE:
9b977c
+    case CURLWC_ERROR:
9b977c
+    case CURLWC_CLEAR:
9b977c
+      if(wildcard->dtor)
9b977c
+        wildcard->dtor(wildcard->protdata);
9b977c
+      return result;
9b977c
+    }
9b977c
   }
9b977c
-
9b977c
-  return result;
9b977c
+  /* UNREACHABLE */
9b977c
 }
9b977c
 
9b977c
 /***********************************************************************
9b977c
-- 
9b977c
2.26.2
9b977c