nmap-0002 + haproxy-0004 + nginx-0004 + weechat-0003 + zeek-0002 + curl-0004: 6 new CWE-407 defects in network tools; count 693→699
nmap-0002: nmap.cc merge_port_lists O(N²) port dedup → unordered_set O(N); ~65000x at max range haproxy-0004: http_ana.c http_capture_headers O(H×C) cap_hdr walk per request → pre-built HashMap O(H) nginx-0004: ngx_http_upstream_keepalive_module.c keepalive_get_peer O(C) sockaddr scan per upstream request → HashMap O(1) weechat-0003: irc-channel.c irc_channel_search O(C) linked-list scan per message handler → channels_hashtable O(1) zeek-0002: Attr.cc Attributes::AddAttrs O(A²) triple-Find/RemoveAttr per attr → unordered_map index O(A) curl-0004: mime.c search_header O(P×H) 3x per part per mime_add_headers → pre-indexed header name set O(P)
This commit is contained in:
parent
421f3352c7
commit
ba818693db
21 changed files with 2213 additions and 0 deletions
98
defects/curl/patch/curl-0004-mime-search-header-O-PxH.md
Normal file
98
defects/curl/patch/curl-0004-mime-search-header-O-PxH.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# curl-0004 — curl_mime multipart header search O(P×H) per request build
|
||||
|
||||
## Ecosystem
|
||||
curl (C)
|
||||
|
||||
## Severity
|
||||
LOW-MEDIUM — affects multipart POST requests; `mime_add_headers` is called
|
||||
once per MIME part during request construction; scales with P (parts) × H
|
||||
(user headers per part)
|
||||
|
||||
## Location
|
||||
`lib/mime.c`
|
||||
- `search_header(struct curl_slist *hdrlist, ...)` (~line 252): O(H) linear scan
|
||||
- `mime_add_headers` / `Curl_mime_add_header` (~line 1696, 1730, 1777):
|
||||
calls `search_header` 3 times per part during request construction
|
||||
|
||||
## Description
|
||||
|
||||
When building a multipart POST request, `mime_add_headers` checks whether the
|
||||
user has already provided `Content-Type`, `Content-Disposition`, and
|
||||
`Content-Transfer-Encoding` headers by calling `search_header` on the user's
|
||||
header list `part->userheaders`:
|
||||
|
||||
```c
|
||||
// mime.c:252
|
||||
static char *search_header(struct curl_slist *hdrlist,
|
||||
const char *hdr, size_t len)
|
||||
{
|
||||
char *value = NULL;
|
||||
for(; !value && hdrlist; hdrlist = hdrlist->next) // O(H) scan
|
||||
value = match_header(hdrlist, hdr, len);
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
Called during request construction:
|
||||
```c
|
||||
// mime.c:1696 — check Content-Type
|
||||
customct = search_header(part->userheaders, STRCONST("Content-Type"));
|
||||
|
||||
// mime.c:1730 — check Content-Disposition
|
||||
if(!search_header(part->userheaders, STRCONST("Content-Disposition"))) { ... }
|
||||
|
||||
// mime.c:1777 — check Content-Transfer-Encoding
|
||||
if(!search_header(part->userheaders, ...)) { ... }
|
||||
```
|
||||
|
||||
For a multipart request with P parts each having H user headers, the cost
|
||||
per request construction is O(P × H × 3) = O(P × H).
|
||||
|
||||
With P=100 parts × H=20 custom headers per part = 6000 header comparisons
|
||||
per request build. The `curl_slist` is a singly-linked list so there is no
|
||||
O(1) name lookup.
|
||||
|
||||
## Fix
|
||||
|
||||
Pre-index per-part user headers into an `unordered_set` or `unordered_map`
|
||||
keyed by header name (case-insensitive). Built once when the part's header
|
||||
list is finalized, looked up O(1):
|
||||
|
||||
```c
|
||||
--- a/lib/mime.h
|
||||
+++ b/lib/mime.h
|
||||
@@ struct curl_mimepart {
|
||||
struct curl_slist *userheaders; /* list of user-set headers */
|
||||
+ /* O(1) header name presence check, built lazily in mime_add_headers */
|
||||
+ /* For C, use a small sorted array or hash table of header names */
|
||||
|
||||
--- a/lib/mime.c
|
||||
+++ b/lib/mime.c
|
||||
- customct = search_header(part->userheaders, STRCONST("Content-Type"));
|
||||
- if(!search_header(part->userheaders, STRCONST("Content-Disposition"))) ...
|
||||
- if(!search_header(part->userheaders, STRCONST("Content-Transfer-Encoding"))) ...
|
||||
|
||||
+ /* Build set once if not already cached */
|
||||
+ struct header_set *hset = build_header_set(part->userheaders);
|
||||
+ customct = header_set_find(hset, "Content-Type");
|
||||
+ if(!header_set_contains(hset, "Content-Disposition")) ...
|
||||
+ if(!header_set_contains(hset, "Content-Transfer-Encoding")) ...
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
| Variant | Cost per part | Total for P parts, H headers |
|
||||
|---------|--------------|------------------------------|
|
||||
| Before | O(H × 3) | O(P × H) |
|
||||
| After | O(1 × 3) | O(P + H) for set build |
|
||||
| Speedup | H× per part | |
|
||||
|
||||
## Notes
|
||||
- The defect is only measurable for large multipart uploads with many
|
||||
user-supplied headers per part (e.g., upload pipelines, multipart forms
|
||||
with per-field metadata)
|
||||
- For typical use (1-5 headers per part), H is small and the impact is minimal
|
||||
- `part->userheaders` is set once via `curl_mime_headers()` before the first
|
||||
request; the cache can be built on first use and invalidated on modification
|
||||
- curl-0002 covers the similar `Curl_checkheaders` pattern for request-level
|
||||
headers; this defect is the MIME-part-level equivalent
|
||||
129
defects/curl/unit/CurlMimeBuildHeaderSearchTest.java
Normal file
129
defects/curl/unit/CurlMimeBuildHeaderSearchTest.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* curl-0004 — CWE-407: curl_mime search_header O(P×H) per multipart request build
|
||||
*
|
||||
* Models lib/mime.c search_header() called 3× per MIME part in mime_add_headers():
|
||||
* Slow: for each part, walk user headers singly-linked list 3× → O(P × H)
|
||||
* Fast: pre-indexed header name set → O(P) total
|
||||
*
|
||||
* Trigger: curl_mime_formadd with many parts each having custom userheaders.
|
||||
*/
|
||||
public class CurlMimeBuildHeaderSearchTest {
|
||||
|
||||
// Simulates struct curl_slist (singly-linked list of strings)
|
||||
static class CurlSlist {
|
||||
final String data;
|
||||
CurlSlist next;
|
||||
CurlSlist(String data) { this.data = data; }
|
||||
}
|
||||
|
||||
// Simulates curl_mimepart
|
||||
static class CurlMimePart {
|
||||
CurlSlist userheaders; // linked list of user-supplied headers
|
||||
Set<String> headerNameCache; // fast: pre-built from userheaders
|
||||
}
|
||||
|
||||
// Append to slist
|
||||
static CurlSlist slistAppend(CurlSlist head, String data) {
|
||||
if (head == null) return new CurlSlist(data);
|
||||
CurlSlist cur = head;
|
||||
while (cur.next != null) cur = cur.next;
|
||||
cur.next = new CurlSlist(data);
|
||||
return head;
|
||||
}
|
||||
|
||||
// Build a mime part with H user headers
|
||||
static CurlMimePart buildPart(int numHeaders, String[] fixedHeaders) {
|
||||
CurlMimePart part = new CurlMimePart();
|
||||
for (int i = 0; i < numHeaders; i++) {
|
||||
String hdr = (i < fixedHeaders.length)
|
||||
? (fixedHeaders[i] + ": value" + i)
|
||||
: ("X-Custom-" + i + ": value" + i);
|
||||
part.userheaders = slistAppend(part.userheaders, hdr);
|
||||
}
|
||||
return part;
|
||||
}
|
||||
|
||||
// Simulate search_header: O(H) scan - returns true if header name found
|
||||
static boolean searchHeaderSlow(CurlSlist hdrlist, String headerName, long[] opsOut) {
|
||||
String target = headerName.toLowerCase() + ":";
|
||||
for (CurlSlist h = hdrlist; h != null; h = h.next) {
|
||||
opsOut[0]++;
|
||||
if (h.data.toLowerCase().startsWith(target)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simulate fast header check: O(1) set lookup
|
||||
static boolean searchHeaderFast(Set<String> cache, String headerName, long[] opsOut) {
|
||||
opsOut[0]++;
|
||||
return cache.contains(headerName.toLowerCase());
|
||||
}
|
||||
|
||||
// Pre-build header name cache for a part
|
||||
static Set<String> buildHeaderCache(CurlSlist userheaders) {
|
||||
Set<String> cache = new HashSet<>();
|
||||
for (CurlSlist h = userheaders; h != null; h = h.next) {
|
||||
int colon = h.data.indexOf(':');
|
||||
if (colon > 0) cache.add(h.data.substring(0, colon).toLowerCase().trim());
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
// Simulate mime_add_headers calling search_header 3× per part
|
||||
static long mimeAddHeadersSlow(List<CurlMimePart> parts) {
|
||||
long[] ops = { 0 };
|
||||
String[] checks = { "Content-Type", "Content-Disposition", "Content-Transfer-Encoding" };
|
||||
for (CurlMimePart part : parts) {
|
||||
for (String check : checks)
|
||||
searchHeaderSlow(part.userheaders, check, ops);
|
||||
}
|
||||
return ops[0];
|
||||
}
|
||||
|
||||
static long mimeAddHeadersFast(List<CurlMimePart> parts) {
|
||||
long[] ops = { 0 };
|
||||
String[] checks = { "Content-Type", "Content-Disposition", "Content-Transfer-Encoding" };
|
||||
for (CurlMimePart part : parts) {
|
||||
// Build cache once per part (amortized O(H) for the part, O(1) per lookup)
|
||||
if (part.headerNameCache == null)
|
||||
part.headerNameCache = buildHeaderCache(part.userheaders);
|
||||
for (String check : checks)
|
||||
searchHeaderFast(part.headerNameCache, check, ops);
|
||||
}
|
||||
return ops[0];
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("curl-0004 CWE-407: curl_mime search_header O(P*H) vs O(P)");
|
||||
System.out.println("===========================================================");
|
||||
|
||||
String[] knownHeaders = { "Content-Type", "Content-Disposition", "Content-Transfer-Encoding",
|
||||
"Content-ID", "Content-Description" };
|
||||
|
||||
int[][] params = { {10, 10}, {50, 20}, {100, 30} };
|
||||
|
||||
for (int[] p : params) {
|
||||
int P = p[0], H = p[1];
|
||||
List<CurlMimePart> parts = new ArrayList<>();
|
||||
for (int i = 0; i < P; i++)
|
||||
parts.add(buildPart(H, knownHeaders));
|
||||
|
||||
long slowOps = mimeAddHeadersSlow(parts);
|
||||
long fastOps = mimeAddHeadersFast(parts);
|
||||
double ratio = (double) slowOps / fastOps;
|
||||
|
||||
System.out.printf(" P=%3d parts, H=%2d headers/part: slow=%,6d ops fast=%,5d ops speedup=%.1fx%n",
|
||||
P, H, slowOps, fastOps, ratio);
|
||||
|
||||
// Fast should be ~H/3 times cheaper (3 lookups but we only scan up to H headers)
|
||||
assert slowOps > fastOps :
|
||||
"Expected slow > fast but slow=" + slowOps + " fast=" + fastOps;
|
||||
}
|
||||
|
||||
System.out.println("\nPASS");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue