wave17: postgres-0001/asterisk-0001/haproxy-0001/nginx-0001; postfix+bevy CLEAN
postgres-0001: pg_inherits.c typeInheritsFrom() BFS visited List → HTAB O(1) asterisk-0001: app_queue.c interface_exists() ao2_iterator walk → ao2_find O(1) haproxy-0001: http_ana.c cookie-server scan linked-list → eb-tree index O(log S) nginx-0001: ngx_http_link_multi_headers() O(H²) double-scan → O(H) hash pass postfix: CLEAN (htable throughout; two marginal LOW admin-bounded candidates) bevy: CLEAN (FixedBitSet/HashSet throughout; only hardware-bounded marginals)
This commit is contained in:
parent
da8b4a41d6
commit
ff6292d067
10 changed files with 627 additions and 252 deletions
108
defects/nginx/patch/nginx-0001-link-multi-headers-hash.patch
Normal file
108
defects/nginx/patch/nginx-0001-link-multi-headers-hash.patch
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
--- a/src/http/ngx_http_core_module.c
|
||||
+++ b/src/http/ngx_http_core_module.c
|
||||
@@ -2871,38 +2871,50 @@ ngx_http_link_multi_headers(ngx_http_request_t *r)
|
||||
{
|
||||
- ngx_uint_t i, j;
|
||||
- ngx_list_part_t *part, *ppart;
|
||||
- ngx_table_elt_t *header, *pheader, **ph;
|
||||
+ ngx_uint_t i;
|
||||
+ ngx_list_part_t *part;
|
||||
+ ngx_table_elt_t *header, **ph;
|
||||
+ ngx_hash_t name_map;
|
||||
+ ngx_hash_init_t hash;
|
||||
+ ngx_hash_key_t *keys;
|
||||
+ ngx_uint_t nkeys;
|
||||
|
||||
if (r->headers_in.multi_linked) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
r->headers_in.multi_linked = 1;
|
||||
|
||||
part = &r->headers_in.headers.part;
|
||||
header = part->elts;
|
||||
|
||||
- for (i = 0; /* void */; i++) {
|
||||
-
|
||||
- if (i >= part->nelts) {
|
||||
- if (part->next == NULL) {
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
- part = part->next;
|
||||
- header = part->elts;
|
||||
- i = 0;
|
||||
- }
|
||||
-
|
||||
- header[i].next = NULL;
|
||||
-
|
||||
- ppart = &r->headers_in.headers.part;
|
||||
- pheader = ppart->elts;
|
||||
-
|
||||
- for (j = 0; /* void */; j++) {
|
||||
-
|
||||
- if (j >= ppart->nelts) {
|
||||
- if (ppart->next == NULL) {
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
- ppart = ppart->next;
|
||||
- pheader = ppart->elts;
|
||||
- j = 0;
|
||||
- }
|
||||
-
|
||||
- if (part == ppart && i == j) {
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
- if (header[i].key.len == pheader[j].key.len
|
||||
- && ngx_strncasecmp(header[i].key.data, pheader[j].key.data,
|
||||
- header[i].key.len)
|
||||
- == 0)
|
||||
- {
|
||||
- ph = &pheader[j].next;
|
||||
- while (*ph) { ph = &(*ph)->next; }
|
||||
- *ph = &header[i];
|
||||
-
|
||||
- r->headers_in.multi = 1;
|
||||
-
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
+ /*
|
||||
+ * Build a hash table mapping lowercased header name → first ngx_table_elt_t *.
|
||||
+ * Single O(H) forward pass: for each header, if the name is already in the
|
||||
+ * hash, append to that entry's ->next chain. No backward re-scan needed.
|
||||
+ * Replaces the O(H²) double-traversal that compared every (i,j) pair.
|
||||
+ */
|
||||
+ ngx_hash_keys_array_t ha;
|
||||
+ if (ngx_hash_keys_array_init(&ha, NGX_HASH_SMALL, r->pool) != NGX_OK) {
|
||||
+ return NGX_ERROR;
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; /* void */; i++) {
|
||||
+ if (i >= part->nelts) {
|
||||
+ if (part->next == NULL) break;
|
||||
+ part = part->next;
|
||||
+ header = part->elts;
|
||||
+ i = 0;
|
||||
+ }
|
||||
+
|
||||
+ header[i].next = NULL;
|
||||
+
|
||||
+ ngx_table_elt_t **existing = ngx_hash_find(&name_map,
|
||||
+ header[i].lowcase_key ?
|
||||
+ ngx_hash_key(header[i].lowcase_key, header[i].key.len) :
|
||||
+ ngx_hash_key_lc(header[i].key.data, header[i].key.len),
|
||||
+ header[i].key.data,
|
||||
+ header[i].key.len);
|
||||
+ if (existing) {
|
||||
+ ph = &(*existing)->next;
|
||||
+ while (*ph) { ph = &(*ph)->next; }
|
||||
+ *ph = &header[i];
|
||||
+ r->headers_in.multi = 1;
|
||||
+ } else {
|
||||
+ ngx_hash_add_key(&ha, &header[i].key, &header[i], NGX_HASH_READONLY_KEY);
|
||||
+ }
|
||||
+ }
|
||||
87
defects/nginx/unit/NginxTest.java
Normal file
87
defects/nginx/unit/NginxTest.java
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit test for nginx ngx_http_core_module.c defect.
|
||||
*
|
||||
* nginx-0001: src/http/ngx_http_core_module.c ngx_http_link_multi_headers()
|
||||
* Nested double-traversal of r->headers_in.headers to chain duplicate
|
||||
* header names: outer loop at position i, inner loop scans 0..i-1.
|
||||
* O(H²) where H = number of incoming request headers.
|
||||
* Fix: single O(H) pass using a hash map of name → first header pointer;
|
||||
* append to chain on first match, no backward scan needed.
|
||||
*/
|
||||
public class NginxTest {
|
||||
|
||||
// Simulate defect: O(H²) double-traversal
|
||||
static Map<String, List<Integer>> linkMultiHeaders_quadratic(String[] headers) {
|
||||
// Returns map of name -> list of positions (the "chain")
|
||||
Map<String, List<Integer>> chains = new LinkedHashMap<>();
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
boolean linked = false;
|
||||
for (int j = 0; j < i; j++) { // O(H²) total — defect
|
||||
if (headers[j].equalsIgnoreCase(headers[i])) {
|
||||
chains.get(headers[j].toLowerCase()).add(i);
|
||||
linked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!linked) {
|
||||
List<Integer> chain = new ArrayList<>();
|
||||
chain.add(i);
|
||||
chains.put(headers[i].toLowerCase(), chain);
|
||||
}
|
||||
}
|
||||
return chains;
|
||||
}
|
||||
|
||||
// Simulate fix: O(H) single pass with hash map
|
||||
static Map<String, List<Integer>> linkMultiHeaders_hash(String[] headers) {
|
||||
Map<String, List<Integer>> chains = new LinkedHashMap<>();
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
String lower = headers[i].toLowerCase();
|
||||
chains.computeIfAbsent(lower, k -> new ArrayList<>()).add(i); // O(1)
|
||||
}
|
||||
return chains;
|
||||
}
|
||||
|
||||
static void testNginx0001() throws Exception {
|
||||
// Simulate H headers with many duplicates (worst case for O(H²))
|
||||
// nginx default: ~50 unique header names, many repeated → H=300 total
|
||||
int H = 300;
|
||||
int UNIQUE = 20;
|
||||
String[] headers = new String[H];
|
||||
String[] names = new String[UNIQUE];
|
||||
for (int i = 0; i < UNIQUE; i++) names[i] = "X-Header-" + i;
|
||||
for (int i = 0; i < H; i++) headers[i] = names[i % UNIQUE];
|
||||
|
||||
// correctness
|
||||
Map<String, List<Integer>> r1 = linkMultiHeaders_quadratic(headers);
|
||||
Map<String, List<Integer>> r2 = linkMultiHeaders_hash(headers);
|
||||
assert r1.keySet().equals(r2.keySet()) : "must produce same key set";
|
||||
for (String k : r1.keySet()) {
|
||||
assert r1.get(k).equals(r2.get(k)) :
|
||||
"chain mismatch for " + k + ": " + r1.get(k) + " vs " + r2.get(k);
|
||||
}
|
||||
|
||||
// performance
|
||||
int REPS = 10_000;
|
||||
long t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) linkMultiHeaders_quadratic(headers);
|
||||
long tQuad = System.nanoTime() - t0;
|
||||
|
||||
t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) linkMultiHeaders_hash(headers);
|
||||
long tHash = System.nanoTime() - t0;
|
||||
|
||||
double ratio = (double) tQuad / tHash;
|
||||
System.out.printf("nginx-0001: quadratic=%.3fs hash=%.3fs ratio=%.1f×%n",
|
||||
tQuad / 1e9, tHash / 1e9, ratio);
|
||||
assert ratio > 3 : "Expected >3× speedup, got " + ratio;
|
||||
System.out.println("PASS nginx-0001");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testNginx0001();
|
||||
System.out.println("ALL PASS");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue