]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tool_formparse: tool2curlparts is no longer recursive
authorDaniel Stenberg <daniel@haxx.se>
Thu, 7 May 2026 06:33:46 +0000 (08:33 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 7 May 2026 07:10:24 +0000 (09:10 +0200)
It could otherwise trigger a stack overflow in extreme cases

Reported-by: Andrew Nesbit
Closes #21518

src/tool_formparse.c

index 19c0e1aa66665976aeccfb0e224750025cbaae50..f74cb77433e0b5296785701c4b28f7cea3727b6e 100644 (file)
@@ -251,71 +251,103 @@ static int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence)
 
 /* Translate an internal mime tree into a libcurl mime tree. */
 
+#define MAX_FORMPARTS 100000 /* arbitrarily picked */
+
 static CURLcode tool2curlparts(CURL *curl, struct tool_mime *m,
                                curl_mime *mime)
 {
   CURLcode result = CURLE_OK;
-  curl_mimepart *part = NULL;
-  curl_mime *submime = NULL;
-  const char *filename = NULL;
+  struct tool_mime *curr;
+  struct tool_mime **nodes = NULL;
+  int count;
+  int i;
+
+  if(!m)
+    return CURLE_OK;
+
+  for(curr = m, count = 0; curr; curr = curr->prev) {
+    if(count > MAX_FORMPARTS)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+    count++;
+  }
 
-  if(m) {
-    result = tool2curlparts(curl, m->prev, mime);
-    if(!result) {
-      part = curl_mime_addpart(mime);
-      if(!part)
-        result = CURLE_OUT_OF_MEMORY;
-    }
-    if(!result) {
-      filename = m->filename;
-      switch(m->kind) {
-      case TOOLMIME_PARTS:
-        result = tool2curlmime(curl, m, &submime);
-        if(!result) {
-          result = curl_mime_subparts(part, submime);
-          if(result)
-            curl_mime_free(submime);
-        }
-        break;
+  nodes = curlx_malloc(sizeof(struct tool_mime *) * count);
+  if(!nodes)
+    return CURLE_OUT_OF_MEMORY;
 
-      case TOOLMIME_DATA:
-        result = curl_mime_data(part, m->data, CURL_ZERO_TERMINATED);
-        break;
+  /* populate array from the end to the beginning */
+  curr = m;
+  for(i = count - 1; i >= 0; i--) {
+    nodes[i] = curr;
+    curr = curr->prev;
+  }
 
-      case TOOLMIME_FILE:
-      case TOOLMIME_FILEDATA:
-        result = curl_mime_filedata(part, m->data);
-        if(!result && m->kind == TOOLMIME_FILEDATA && !filename)
-          result = curl_mime_filename(part, NULL);
-        break;
+  for(i = 0; i < count; i++) {
+    struct tool_mime *node = nodes[i];
+    curl_mimepart *part = NULL;
+    curl_mime *submime = NULL;
+    const char *filename = node->filename;
 
-      case TOOLMIME_STDIN:
-        if(!filename)
-          filename = "-";
-        FALLTHROUGH();
-      case TOOLMIME_STDINDATA:
-        result = curl_mime_data_cb(part, m->size,
-                                   tool_mime_stdin_read,
-                                   tool_mime_stdin_seek,
-                                   NULL, m);
-        break;
+    part = curl_mime_addpart(mime);
+    if(!part) {
+      result = CURLE_OUT_OF_MEMORY;
+      break;
+    }
 
-      default:
-        /* Other cases not possible in this context. */
-        break;
+    switch(node->kind) {
+    case TOOLMIME_PARTS:
+      result = tool2curlmime(curl, node, &submime);
+      if(!result) {
+        result = curl_mime_subparts(part, submime);
+        if(result)
+          curl_mime_free(submime);
       }
+      break;
+
+    case TOOLMIME_DATA:
+      result = curl_mime_data(part, node->data, CURL_ZERO_TERMINATED);
+      break;
+
+    case TOOLMIME_FILE:
+    case TOOLMIME_FILEDATA:
+      result = curl_mime_filedata(part, node->data);
+      if(!result && node->kind == TOOLMIME_FILEDATA && !filename)
+        result = curl_mime_filename(part, NULL);
+      break;
+
+    case TOOLMIME_STDIN:
+      if(!filename)
+        filename = "-";
+      FALLTHROUGH();
+    case TOOLMIME_STDINDATA:
+      result = curl_mime_data_cb(part, node->size,
+                                 tool_mime_stdin_read,
+                                 tool_mime_stdin_seek,
+                                 NULL, node);
+      break;
+
+    default:
+      /* Other cases not possible in this context. */
+      break;
     }
+
+    /* Common part configuration */
     if(!result && filename)
       result = curl_mime_filename(part, filename);
     if(!result)
-      result = curl_mime_type(part, m->type);
+      result = curl_mime_type(part, node->type);
     if(!result)
-      result = curl_mime_headers(part, m->headers, 0);
+      result = curl_mime_headers(part, node->headers, 0);
     if(!result)
-      result = curl_mime_encoder(part, m->encoder);
+      result = curl_mime_encoder(part, node->encoder);
     if(!result)
-      result = curl_mime_name(part, m->name);
+      result = curl_mime_name(part, node->name);
+
+    if(result)
+      break;
   }
+
+  curlx_free(nodes);
   return result;
 }