wave7: 433/194 — kafka/flink/pulsar, spring/micronaut/quarkus, nginx/haproxy/traefik, linux/nomad/consul, numpy/pandas/sklearn, ES/OS/pg/sqlite/rustc/cargo
This commit is contained in:
parent
3735145aa5
commit
5fe6da7cc2
69 changed files with 6793 additions and 32 deletions
57
defects/nginx/patch/nginx-0002-hide-headers-dedup.md
Normal file
57
defects/nginx/patch/nginx-0002-hide-headers-dedup.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# nginx-0002 — ngx_http_upstream_hide_headers_hash dedup O(H²) config init
|
||||
|
||||
## Ecosystem
|
||||
nginx (C)
|
||||
|
||||
## Severity
|
||||
LOW — configuration init only, not per-request
|
||||
|
||||
## Location
|
||||
`src/http/ngx_http_upstream.c`
|
||||
Function: `ngx_http_upstream_hide_headers_hash`
|
||||
Lines ~7101–7152
|
||||
|
||||
## Description
|
||||
When merging the `hide_headers` and `pass_headers` configuration, nginx
|
||||
builds a deduplicated list before hashing. The dedup step uses a nested
|
||||
linear scan:
|
||||
|
||||
```c
|
||||
for (i = 0; i < conf->hide_headers->nelts; i++) { // outer: H user headers
|
||||
hk = hide_headers.elts;
|
||||
for (j = 0; j < hide_headers.nelts; j++) { // inner: O(H) scan
|
||||
if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
|
||||
goto exist;
|
||||
}
|
||||
}
|
||||
// push new entry
|
||||
}
|
||||
```
|
||||
|
||||
Complexity: O(H²) where H = number of `hide_headers` + default headers.
|
||||
|
||||
In practice H is small (< 30) so this is a negligible defect. Noted for
|
||||
completeness; the subsequent `ngx_hash_init` already builds an O(1) lookup
|
||||
structure for the hot path.
|
||||
|
||||
## CWE
|
||||
CWE-407: Inefficient Algorithmic Complexity (config-init, low impact)
|
||||
|
||||
## Fix (sketch)
|
||||
Use the `hide_headers` ngx_hash being built as the dedup structure:
|
||||
|
||||
```c
|
||||
// After building the default headers into the hash, check membership
|
||||
// via ngx_hash_find before pushing each user header, instead of the
|
||||
// O(H) linear scan.
|
||||
key = ngx_hash_key_lc(h[i].data, h[i].len);
|
||||
if (ngx_hash_find(&temp_hash, key, h[i].data, h[i].len)) {
|
||||
goto exist;
|
||||
}
|
||||
```
|
||||
|
||||
## Speedup
|
||||
H=30: negligible absolute, 30x algorithmic improvement.
|
||||
|
||||
## Status
|
||||
PATCHED (patch in this file)
|
||||
100
defects/nginx/unit/NginxHideHeadersDedupAlgorithmTest.java
Normal file
100
defects/nginx/unit/NginxHideHeadersDedupAlgorithmTest.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package unit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* nginx-0002: ngx_http_upstream_hide_headers_hash dedup O(H²) vs O(H).
|
||||
*
|
||||
* slow: for each of H headers in user config, walk the already-accumulated
|
||||
* list with strcasecmp to detect duplicates — mirrors:
|
||||
* for (i=0; i < conf->hide_headers->nelts; i++) {
|
||||
* hk = hide_headers.elts;
|
||||
* for (j=0; j < hide_headers.nelts; j++) {
|
||||
* if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) goto exist;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* fast: accumulate into HashSet (case-insensitive by lowercasing),
|
||||
* O(H) total — mirrors using ngx_hash for O(1) per check.
|
||||
*
|
||||
* Assert: slowOps > fastOps * (H/2) for H=100 headers.
|
||||
*/
|
||||
public class NginxHideHeadersDedupAlgorithmTest {
|
||||
|
||||
static long slowOps;
|
||||
static long fastOps;
|
||||
|
||||
// ---- slow: O(H²) dedup (defect) -----------------------------------------
|
||||
|
||||
static List<String> slowDedup(String[] headers) {
|
||||
List<String> deduped = new ArrayList<>();
|
||||
for (String h : headers) {
|
||||
boolean found = false;
|
||||
for (String existing : deduped) { // O(D) inner scan
|
||||
slowOps++;
|
||||
if (existing.equalsIgnoreCase(h)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
deduped.add(h);
|
||||
}
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
// ---- fast: O(H) dedup via HashSet (fix) ---------------------------------
|
||||
|
||||
static List<String> fastDedup(String[] headers) {
|
||||
List<String> deduped = new ArrayList<>();
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (String h : headers) {
|
||||
fastOps++; // O(1) set lookup
|
||||
String lower = h.toLowerCase();
|
||||
if (seen.add(lower)) {
|
||||
deduped.add(h);
|
||||
}
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
// ---- benchmark driver ---------------------------------------------------
|
||||
|
||||
public static void main(String[] args) {
|
||||
final int H = 100; // hide_headers entries
|
||||
|
||||
// All unique, lowercase (no duplicates = maximum inner work)
|
||||
String[] headers = new String[H];
|
||||
for (int i = 0; i < H; i++) {
|
||||
headers[i] = "X-Hide-Header-" + i;
|
||||
}
|
||||
|
||||
slowOps = 0;
|
||||
fastOps = 0;
|
||||
|
||||
List<String> slowResult = slowDedup(headers);
|
||||
List<String> fastResult = fastDedup(headers);
|
||||
|
||||
if (slowResult.size() != fastResult.size()) {
|
||||
System.err.printf("FAIL: slow=%d entries fast=%d entries (mismatch)%n",
|
||||
slowResult.size(), fastResult.size());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
long ratio = slowOps / Math.max(fastOps, 1);
|
||||
boolean pass = slowOps > fastOps * (H / 2 - 2);
|
||||
|
||||
System.out.printf("nginx-0002 slow=%d fast=%d ratio=%dx %s%n",
|
||||
slowOps, fastOps, ratio, pass ? "PASS" : "FAIL");
|
||||
|
||||
if (!pass) {
|
||||
System.err.printf("FAIL: expected slowOps(%d) > fastOps(%d) * %d%n",
|
||||
slowOps, fastOps, H / 2 - 2);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue