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
|
|
@ -0,0 +1,95 @@
|
|||
# haproxy-0004 — http_capture_headers O(H×C) per-request header capture scan
|
||||
|
||||
## Ecosystem
|
||||
haproxy (C)
|
||||
|
||||
## Severity
|
||||
MEDIUM — hot path: called for every HTTP request and response with `capture request header` or `capture response header` directives configured
|
||||
|
||||
## Location
|
||||
`src/http_ana.c`
|
||||
- Function: `http_capture_headers` (~line 5096)
|
||||
- Inner loop: `for (h = cap_hdr; h; h = h->next)` at line 5113
|
||||
|
||||
## Description
|
||||
|
||||
`http_capture_headers` is called on every HTTP request and response when
|
||||
header capture is configured. It iterates over all H headers in the HTX
|
||||
message, and for each header performs a linear O(C) walk of the `cap_hdr`
|
||||
linked list to check for a name match:
|
||||
|
||||
```c
|
||||
static void http_capture_headers(struct htx *htx, char **cap, struct cap_hdr *cap_hdr)
|
||||
{
|
||||
for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
|
||||
// ... get header name n ...
|
||||
for (h = cap_hdr; h; h = h->next) { // O(C) per header
|
||||
if (h->namelen && (h->namelen == n.len) &&
|
||||
(strncasecmp(n.ptr, h->name, h->namelen) == 0)) {
|
||||
// capture value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Total cost per request: O(H × C) where:
|
||||
- H = number of headers in the request/response (typically 10-50, up to 100+)
|
||||
- C = number of configured capture headers (can be dozens with complex configs)
|
||||
|
||||
Since this runs on every HTTP request+response, it compounds:
|
||||
- Large H (100 headers) × large C (50 capture directives) = 5000 strncasecmp calls per request
|
||||
|
||||
## Fix
|
||||
|
||||
Build a hash map from `cap_hdr` name → `cap_hdr *` at config parse time. On each
|
||||
request, look up each header name in O(1):
|
||||
|
||||
```c
|
||||
--- a/include/haproxy/proxy-t.h
|
||||
+++ b/include/haproxy/proxy-t.h
|
||||
@@ -437,6 +437,8 @@
|
||||
struct cap_hdr *req_cap; /* chained list of request headers to be captured */
|
||||
struct cap_hdr *rsp_cap; /* chained list of response headers to be captured */
|
||||
+ struct eb_root req_cap_tree; /* name→cap_hdr for O(log C) lookup */
|
||||
+ struct eb_root rsp_cap_tree; /* name→cap_hdr for O(log C) lookup */
|
||||
|
||||
--- a/src/http_ana.c
|
||||
+++ b/src/http_ana.c
|
||||
@@ -5096,15 +5096,14 @@
|
||||
static void http_capture_headers(struct htx *htx, char **cap, struct cap_hdr *cap_hdr)
|
||||
{
|
||||
- struct cap_hdr *h;
|
||||
int32_t pos;
|
||||
|
||||
for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
|
||||
struct htx_blk *blk = htx_get_blk(htx, pos);
|
||||
if (htx_get_blk_type(blk) == HTX_BLK_EOH) break;
|
||||
if (htx_get_blk_type(blk) != HTX_BLK_HDR) continue;
|
||||
|
||||
struct ist n = htx_get_blk_name(htx, blk);
|
||||
|
||||
- for (h = cap_hdr; h; h = h->next) { // O(C) walk eliminated
|
||||
+ struct cap_hdr *h = cap_hdr_lookup(cap_hdr, n); // O(1) hash lookup
|
||||
+ if (h) {
|
||||
if (h->namelen && /* ... match ... */) {
|
||||
// capture value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
| Variant | Cost |
|
||||
|---------|------|
|
||||
| Before | O(H × C) per request+response pair |
|
||||
| After | O(H) per request+response pair — O(1) hash lookup per header |
|
||||
| Speedup | C× (number of capture headers) |
|
||||
|
||||
## Notes
|
||||
- With C=1 (one capture directive) this is trivially already O(H); the defect
|
||||
matters when C grows with complex proxy configurations
|
||||
- The cap_hdr list is immutable after config parse, making it safe to index
|
||||
- HAProxy already uses various tree structures (ceb-trees) for similar purposes
|
||||
126
defects/haproxy/unit/HaproxyCaptureHeadersAlgorithmTest.java
Normal file
126
defects/haproxy/unit/HaproxyCaptureHeadersAlgorithmTest.java
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* haproxy-0004 — CWE-407: http_capture_headers O(H×C) per request
|
||||
*
|
||||
* Models http_ana.c http_capture_headers():
|
||||
* Slow: for each of H request headers, walk C-entry cap_hdr linked list (O(C))
|
||||
* → O(H × C) per request
|
||||
* Fast: pre-build HashMap<name,cap_hdr> at config time → O(H) per request
|
||||
*
|
||||
* Hot path: called on every HTTP request and response when `capture request header`
|
||||
* directives are configured.
|
||||
*/
|
||||
public class HaproxyCaptureHeadersAlgorithmTest {
|
||||
|
||||
// Simulates a cap_hdr linked-list entry (from include/haproxy/capture-t.h)
|
||||
static class CapHdr {
|
||||
final String name;
|
||||
final int index;
|
||||
CapHdr next;
|
||||
CapHdr(String name, int index) { this.name = name; this.index = index; }
|
||||
}
|
||||
|
||||
// Simulates an HTTP header (name + value)
|
||||
static class HttpHeader {
|
||||
final String name;
|
||||
final String value;
|
||||
HttpHeader(String name, String value) { this.name = name; this.value = value; }
|
||||
}
|
||||
|
||||
// --- SLOW: O(H × C) per request (defect) ---
|
||||
// Models: for each htx header → for each cap_hdr → strncasecmp
|
||||
static long captureHeadersSlow(List<HttpHeader> headers, CapHdr capHdrHead) {
|
||||
long ops = 0;
|
||||
String[] cap = new String[64];
|
||||
|
||||
for (HttpHeader hdr : headers) {
|
||||
for (CapHdr h = capHdrHead; h != null; h = h.next) {
|
||||
ops++; // strncasecmp call
|
||||
if (h.name.equalsIgnoreCase(hdr.name)) {
|
||||
if (cap[h.index] == null)
|
||||
cap[h.index] = hdr.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// --- FAST: O(H) per request (fix) ---
|
||||
// Models: pre-built HashMap<lowercaseName, cap_hdr> at config time
|
||||
static long captureHeadersFast(List<HttpHeader> headers,
|
||||
Map<String, CapHdr> capHdrMap) {
|
||||
long ops = 0;
|
||||
String[] cap = new String[64];
|
||||
|
||||
for (HttpHeader hdr : headers) {
|
||||
ops++; // O(1) hash lookup
|
||||
CapHdr h = capHdrMap.get(hdr.name.toLowerCase());
|
||||
if (h != null && cap[h.index] == null)
|
||||
cap[h.index] = hdr.value;
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// Build cap_hdr linked list
|
||||
static CapHdr buildCapHdrList(int count) {
|
||||
CapHdr head = null;
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
CapHdr h = new CapHdr("X-Custom-Header-" + i, i);
|
||||
h.next = head;
|
||||
head = h;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
// Build pre-indexed cap_hdr map
|
||||
static Map<String, CapHdr> buildCapHdrMap(CapHdr head) {
|
||||
Map<String, CapHdr> map = new HashMap<>();
|
||||
for (CapHdr h = head; h != null; h = h.next)
|
||||
map.put(h.name.toLowerCase(), h);
|
||||
return map;
|
||||
}
|
||||
|
||||
// Build HTTP request headers list
|
||||
static List<HttpHeader> buildHeaders(int count, int captureHits) {
|
||||
List<HttpHeader> headers = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String name;
|
||||
if (i < captureHits)
|
||||
name = "X-Custom-Header-" + i; // will match cap_hdr
|
||||
else
|
||||
name = "Standard-Header-" + i; // won't match cap_hdr
|
||||
headers.add(new HttpHeader(name, "value-" + i));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("haproxy-0004 CWE-407: http_capture_headers O(H*C) vs O(H)");
|
||||
System.out.println("==========================================================");
|
||||
|
||||
// Parameters: H request headers, C capture headers configured
|
||||
int[][] params = { {30, 10}, {50, 20}, {100, 50} };
|
||||
|
||||
for (int[] p : params) {
|
||||
int H = p[0], C = p[1];
|
||||
CapHdr capHdrHead = buildCapHdrList(C);
|
||||
Map<String, CapHdr> capHdrMap = buildCapHdrMap(capHdrHead);
|
||||
List<HttpHeader> headers = buildHeaders(H, C / 2);
|
||||
|
||||
long slowOps = captureHeadersSlow(headers, capHdrHead);
|
||||
long fastOps = captureHeadersFast(headers, capHdrMap);
|
||||
double ratio = (double) slowOps / fastOps;
|
||||
|
||||
System.out.printf(" H=%3d headers, C=%2d captures: slow=%,5d ops fast=%,3d ops speedup=%.0fx%n",
|
||||
H, C, slowOps, fastOps, ratio);
|
||||
|
||||
assert ratio >= (double) C / 2 :
|
||||
"Expected speedup >= " + (C/2) + "x but got " + ratio;
|
||||
}
|
||||
|
||||
System.out.println("\nPASS");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue