java-topology/defects/haproxy/patch/haproxy-0004-http-capture-headers-O-HxC.md
russell@unturf.com 25c2bafdee undf: assign 694-720; stamp patches; ruby-0003/elixir-0002/r-source-0002/victoria-metrics-0002
New UNDF assignments (693→720):
  elixir-0002 → UNDF-2026-000000698 (typespec used_type_pairs O(T²))
  r-source-0002 → UNDF-2026-000000711 (.walkClassGraph match dedup O(S²))
  ruby-0003 → UNDF-2026-000000712 (RubyGems dependent_gems O(N²×D))
  victoria-metrics-0002 → UNDF-2026-000000717 (MetricName tag-filter O(T×I))

Total: 720 UNDF assigned
2026-03-29 22:28:31 -04:00

96 lines
3.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000702
# 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