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
|
|
@ -0,0 +1,24 @@
|
|||
--- a/apps/app_queue.c
|
||||
+++ b/apps/app_queue.c
|
||||
@@ -7686,20 +7686,9 @@ static struct member *interface_exists(struct call_queue *q, const char *interfa
|
||||
{
|
||||
- struct member *mem;
|
||||
- struct ao2_iterator mem_iter;
|
||||
-
|
||||
if (!q) {
|
||||
return NULL;
|
||||
}
|
||||
- mem_iter = ao2_iterator_init(q->members, 0);
|
||||
- while ((mem = ao2_iterator_next(&mem_iter))) {
|
||||
- if (!strcasecmp(interface, mem->interface)) {
|
||||
- ao2_iterator_destroy(&mem_iter);
|
||||
- return mem;
|
||||
- }
|
||||
- ao2_ref(mem, -1);
|
||||
- }
|
||||
- ao2_iterator_destroy(&mem_iter);
|
||||
-
|
||||
- return NULL;
|
||||
+ /* q->members is an ao2 container keyed by interface string; use O(1) hash lookup */
|
||||
+ return ao2_find(q->members, interface, OBJ_KEY); /* was O(M) iterator walk */
|
||||
}
|
||||
|
|
@ -1,255 +1,69 @@
|
|||
package unit;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* AsteriskTest — CWE-407 benchmark for asterisk-0001 and asterisk-0002
|
||||
* CWE-407 unit test for Asterisk app_queue.c defect.
|
||||
*
|
||||
* asterisk-0001 (ASTERISK_MEETME_CONF_FIND):
|
||||
* Models find_conf(): conference lookup in global confs linked list.
|
||||
* SLOW: AST_LIST_TRAVERSE — O(n_conferences) per lookup
|
||||
* FAST: ao2_container hash lookup — O(1)
|
||||
*
|
||||
* asterisk-0002 (ASTERISK_CONFBRIDGE_USER_FIND):
|
||||
* Models user-by-channel-name lookup in active_list linked list.
|
||||
* SLOW: AST_LIST_TRAVERSE + strcasecmp — O(n_participants) per operation
|
||||
* FAST: HashMap keyed by channel name — O(1)
|
||||
* asterisk-0001: apps/app_queue.c interface_exists()
|
||||
* ao2_iterator_init + while(ao2_iterator_next) + strcasecmp — O(M) walk
|
||||
* over all queue members to find one by interface string.
|
||||
* Fix: ao2_find(q->members, interface, OBJ_KEY) — O(1) hash lookup using
|
||||
* the container's existing key function (members already keyed by interface).
|
||||
*/
|
||||
public class AsteriskTest {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Data model
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class Conference {
|
||||
final String confno;
|
||||
Conference(String confno) { this.confno = confno; }
|
||||
}
|
||||
|
||||
static class ConfUser {
|
||||
final String channelName;
|
||||
ConfUser(String channelName) { this.channelName = channelName; }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// asterisk-0001: find_conf — linked list O(n) vs HashMap O(1)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** SLOW: linear scan of all conferences. Returns ops = comparisons made. */
|
||||
static long findConf_slow(List<Conference> confs, String confno) {
|
||||
long ops = 0;
|
||||
for (Conference cnf : confs) {
|
||||
ops++;
|
||||
if (cnf.confno.equals(confno)) return ops;
|
||||
// Simulate defect: O(M) iterator walk
|
||||
static String interfaceExists_iterator(List<String> members, String iface) {
|
||||
for (String m : members) { // O(M) linear scan
|
||||
if (m.equalsIgnoreCase(iface)) return m;
|
||||
}
|
||||
return ops; // not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/** FAST: HashMap lookup. Returns ops = 1 (hash probe). */
|
||||
static long findConf_fast(Map<String, Conference> confsMap, String confno) {
|
||||
confsMap.get(confno);
|
||||
return 1;
|
||||
// Simulate fix: O(1) hash lookup
|
||||
static String interfaceExists_ao2find(Map<String, String> membersMap, String iface) {
|
||||
return membersMap.get(iface.toLowerCase()); // O(1)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// asterisk-0002: confbridge user find — linked list O(n) vs HashMap O(1)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** SLOW: linear traverse of active_list + waiting_list by channel name. */
|
||||
static long findUserByChannel_slow(List<ConfUser> activeList,
|
||||
List<ConfUser> waitingList,
|
||||
String channelName) {
|
||||
long ops = 0;
|
||||
for (ConfUser u : activeList) {
|
||||
ops++;
|
||||
if (u.channelName.equalsIgnoreCase(channelName)) return ops;
|
||||
}
|
||||
for (ConfUser u : waitingList) {
|
||||
ops++;
|
||||
if (u.channelName.equalsIgnoreCase(channelName)) return ops;
|
||||
}
|
||||
return ops; // not found
|
||||
}
|
||||
|
||||
/** FAST: HashMap keyed by channel name — O(1). */
|
||||
static long findUserByChannel_fast(Map<String, ConfUser> usersByName,
|
||||
String channelName) {
|
||||
usersByName.get(channelName.toLowerCase());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Benchmark harness
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
|
||||
slow.run(); fast.run();
|
||||
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
|
||||
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
|
||||
double r = fOps > 0 ? (double) sOps / fOps : 0;
|
||||
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
|
||||
label, sMs, sOps, fMs, fOps, r);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static List<Conference> makeConfs(int n) {
|
||||
List<Conference> list = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) list.add(new Conference("conf" + i));
|
||||
return list;
|
||||
}
|
||||
|
||||
static Map<String, Conference> makeConfsMap(List<Conference> confs) {
|
||||
Map<String, Conference> map = new HashMap<>(confs.size() * 2);
|
||||
for (Conference c : confs) map.put(c.confno, c);
|
||||
return map;
|
||||
}
|
||||
|
||||
static List<ConfUser> makeUsers(String prefix, int n) {
|
||||
List<ConfUser> list = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) list.add(new ConfUser("SIP/" + prefix + i + "-0000" + i));
|
||||
return list;
|
||||
}
|
||||
|
||||
static Map<String, ConfUser> makeUserMap(List<ConfUser> active,
|
||||
List<ConfUser> waiting) {
|
||||
Map<String, ConfUser> map = new HashMap<>((active.size() + waiting.size()) * 2);
|
||||
for (ConfUser u : active) map.put(u.channelName.toLowerCase(), u);
|
||||
for (ConfUser u : waiting) map.put(u.channelName.toLowerCase(), u);
|
||||
return map;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("AsteriskTest — CWE-407 benchmarks: asterisk-0001 + asterisk-0002");
|
||||
System.out.println("=================================================================");
|
||||
int passed = 0, total = 0;
|
||||
|
||||
// ----- asterisk-0001: find_conf -----
|
||||
System.out.println("\n[asterisk-0001] app_meetme: find_conf linked-list traversal");
|
||||
{
|
||||
// C=500 concurrent conferences; target is last in list (worst case)
|
||||
int C = 500;
|
||||
List<Conference> confs = makeConfs(C);
|
||||
Map<String, Conference> confsMap = makeConfsMap(confs);
|
||||
String target = confs.get(C - 1).confno; // last element = worst case
|
||||
|
||||
int LOOKUPS = 100_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_slow(confs, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_fast(confsMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("find_conf C=500 last-match (100k lookups)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 100
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
}
|
||||
{
|
||||
// C=1000, target not found (full scan every time)
|
||||
int C = 1000;
|
||||
List<Conference> confs = makeConfs(C);
|
||||
Map<String, Conference> confsMap = makeConfsMap(confs);
|
||||
String target = "confNOTEXIST";
|
||||
|
||||
int LOOKUPS = 50_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_slow(confs, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < LOOKUPS; i++)
|
||||
ops += findConf_fast(confsMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("find_conf C=1000 not-found (50k lookups)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 500
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
static void testAsterisk0001() throws Exception {
|
||||
int M = 2000; // queue members
|
||||
List<String> members = new ArrayList<>();
|
||||
Map<String, String> membersMap = new HashMap<>();
|
||||
for (int i = 0; i < M; i++) {
|
||||
String iface = "SIP/agent-" + String.format("%04d", i);
|
||||
members.add(iface);
|
||||
membersMap.put(iface.toLowerCase(), iface);
|
||||
}
|
||||
|
||||
// ----- asterisk-0002: confbridge user find -----
|
||||
System.out.println("\n[asterisk-0002] app_confbridge: user find in active_list");
|
||||
{
|
||||
// P=500 active participants; target is last (worst case for kick/mute)
|
||||
int P = 500;
|
||||
List<ConfUser> active = makeUsers("active", P);
|
||||
List<ConfUser> waiting = makeUsers("waiting", 50);
|
||||
Map<String, ConfUser> userMap = makeUserMap(active, waiting);
|
||||
String target = active.get(P - 1).channelName; // last = worst case
|
||||
// target near end of list (worst case for linear scan)
|
||||
String target = "SIP/agent-" + String.format("%04d", M - 1);
|
||||
|
||||
int OPS = 50_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_slow(active, waiting, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_fast(userMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("confbridge user-find P=500 last-match (50k)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 100
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
}
|
||||
{
|
||||
// P=2000 active, target not found at all (e.g. stale AMI kick)
|
||||
int P = 2000;
|
||||
List<ConfUser> active = makeUsers("big", P);
|
||||
List<ConfUser> waiting = Collections.emptyList();
|
||||
Map<String, ConfUser> userMap = makeUserMap(active, waiting);
|
||||
String target = "SIP/ghost-00000000"; // not present
|
||||
// correctness
|
||||
String r1 = interfaceExists_iterator(members, target);
|
||||
String r2 = interfaceExists_ao2find(membersMap, target);
|
||||
assert r1 != null && r1.equalsIgnoreCase(target) : "iterator must find target";
|
||||
assert r2 != null && r2.equalsIgnoreCase(target) : "ao2find must find target";
|
||||
assert interfaceExists_iterator(members, "SIP/nonexistent") == null;
|
||||
assert interfaceExists_ao2find(membersMap, "SIP/nonexistent") == null;
|
||||
|
||||
int OPS = 20_000;
|
||||
long[] sOps = {0}, fOps = {0};
|
||||
Runnable slow = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_slow(active, waiting, target);
|
||||
sOps[0] = ops;
|
||||
};
|
||||
Runnable fast = () -> {
|
||||
long ops = 0;
|
||||
for (int i = 0; i < OPS; i++)
|
||||
ops += findUserByChannel_fast(userMap, target);
|
||||
fOps[0] = ops;
|
||||
};
|
||||
slow.run(); fast.run();
|
||||
bench("confbridge user-find P=2000 not-found (20k)", slow, fast, sOps[0], fOps[0]);
|
||||
total++;
|
||||
assert sOps[0] > fOps[0] * 1000
|
||||
: "FAIL: slow=" + sOps[0] + " fast=" + fOps[0];
|
||||
passed++;
|
||||
}
|
||||
// performance: simulate C calls per second
|
||||
int REPS = 20_000;
|
||||
long t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) interfaceExists_iterator(members, target);
|
||||
long tIter = System.nanoTime() - t0;
|
||||
|
||||
System.out.println("\n" + passed + "/" + total + " PASS");
|
||||
if (passed < total) System.exit(1);
|
||||
t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) interfaceExists_ao2find(membersMap, target);
|
||||
long tHash = System.nanoTime() - t0;
|
||||
|
||||
double ratio = (double) tIter / tHash;
|
||||
System.out.printf("asterisk-0001: iter=%.3fs ao2find=%.3fs ratio=%.1f×%n",
|
||||
tIter / 1e9, tHash / 1e9, ratio);
|
||||
assert ratio > 10 : "Expected >10× speedup, got " + ratio;
|
||||
System.out.println("PASS asterisk-0001");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testAsterisk0001();
|
||||
System.out.println("ALL PASS");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
# CLEAN — bevy
|
||||
# bevy — CLEAN
|
||||
|
||||
Scanned 2026-03-29 for CWE-407 (algorithmic complexity).
|
||||
Scanned 2026-03-30. No CWE-407 defects warranting a patch.
|
||||
|
||||
## Findings
|
||||
Bevy's engineering choices specifically prevent O(N²) patterns:
|
||||
- ECS schedule tracking: `FixedBitSet` throughout — O(1) contains
|
||||
- Asset system: `HashSet<AssetPath>` — O(1)
|
||||
- Plugin deduplication: `HashSet<String>` / `TypeIdMap` (HashMap) — O(1)
|
||||
- Query/filter building: `FixedBitSet` — O(1)
|
||||
- GLTF loader: `HashSet` for animation_roots, visited, etc.
|
||||
|
||||
- `bevy_ecs/src/schedule/graph/graph_map.rs` — cycle detection uses `HashSet` for `blocked` and `maybe_in_more_cycles`. CLEAN.
|
||||
- `bevy_ecs/src/schedule/graph/dag.rs` — transitive reduction uses `FixedBitSet` for `visited`. CLEAN.
|
||||
- `bevy_ecs/src/schedule/auto_insert_apply_deferred.rs` — `no_sync_edges` is `BTreeSet`. CLEAN.
|
||||
- `bevy_ecs/src/schedule/node.rs` — `ambiguous_with_all: &HashSet<NodeId>`, `ignored_ambiguities: &BTreeSet`. CLEAN.
|
||||
- `bevy_gltf/src/loader/gltf_ext/scene.rs` — uses `FixedBitSet` and `HashSet` for visited tracking. CLEAN.
|
||||
- `bevy_ui/src/stack.rs` — `visited_root_nodes: Local<HashSet<Entity>>`. CLEAN.
|
||||
- `bevy_picking/src/hover.rs` — `hover_ancestors: EntityHashSet`. CLEAN.
|
||||
- `bevy_render/src/view/window/screenshot.rs` — `seen_targets: Local<HashSet<...>>`. CLEAN.
|
||||
- `bevy_pbr/src/render/light.rs` — `all_cascades_seen: HashSet`. CLEAN.
|
||||
- `bevy_ecs/src/bundle/info.rs` — `explicit_component_ids: IndexSet<_, FixedHasher>`. CLEAN.
|
||||
- `bevy_ecs/src/world/entity_access/world_mut.rs` — `contributed_components()` slice `.contains()` called during archetype migration (not per-frame hot path), N bounded to component count per entity. Not actionable.
|
||||
|
||||
**Result: No actionable CWE-407 defects.**
|
||||
Marginal candidates all bounded by hardware constants or startup-time:
|
||||
- `bevy_render/slab_allocator.rs:904`: `Vec.iter().position()` in cleanup path; S,V typically 1–5
|
||||
- `bevy_app/plugin_group.rs:300`: `Vec<TypeId>.iter().find()` on duplicate plugins; startup-only
|
||||
- `bevy_picking/hover.rs:139`: `Vec<PointerId>.contains()` in retain; P ≤ ~11 hardware pointers
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
--- a/include/haproxy/server-t.h
|
||||
+++ b/include/haproxy/server-t.h
|
||||
@@ -343,6 +343,7 @@ struct server {
|
||||
int cklen; /* the len of the cookie, to speed up checks */
|
||||
unsigned int flags; /* server permanent flags */
|
||||
char *cookie; /* the id set in the cookie */
|
||||
+ struct ebpt_node cookie_node; /* eb-tree node keyed by cookie string; used by proxy->cookies_tree */
|
||||
|
||||
--- a/include/haproxy/proxy-t.h
|
||||
+++ b/include/haproxy/proxy-t.h
|
||||
@@ -364,6 +364,7 @@ struct proxy {
|
||||
struct server *srv, *defsrv; /* known servers; default server configuration */
|
||||
+ struct eb_root cookies_tree; /* eb-tree of srv->cookie_node for O(1) cookie lookup; populated after config parse */
|
||||
|
||||
--- a/src/server.c
|
||||
+++ b/src/server.c
|
||||
@@ -XXX,0 +XXX,12 @@
|
||||
+/*
|
||||
+ * Build or rebuild the proxy's cookies_tree index.
|
||||
+ * Called after srv_set_dyncookie() and after initial config parse.
|
||||
+ * O(S log S) one-time cost; amortises the O(C×S) per-request cookie scan.
|
||||
+ */
|
||||
+void proxy_build_cookie_tree(struct proxy *px)
|
||||
+{
|
||||
+ struct server *srv;
|
||||
+ px->cookies_tree = EB_ROOT_UNIQUE;
|
||||
+ for (srv = px->srv; srv; srv = srv->next) {
|
||||
+ if (srv->cookie) {
|
||||
+ srv->cookie_node.key = srv->cookie;
|
||||
+ ebis_insert(&px->cookies_tree, &srv->cookie_node);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
|
||||
--- a/src/http_ana.c
|
||||
+++ b/src/http_ana.c
|
||||
@@ -3514,10 +3514,18 @@ static void http_manage_client_side_cookies(...)
|
||||
if ((delim == val_beg) || (s->flags & (SF_IGNORE_PRST | SF_ASSIGNED)))
|
||||
srv = NULL;
|
||||
|
||||
- while (srv) {
|
||||
- if (srv->cookie && (srv->cklen == delim - val_beg) &&
|
||||
- !memcmp(val_beg, srv->cookie, delim - val_beg)) {
|
||||
- /* ... */
|
||||
- }
|
||||
- srv = srv->next;
|
||||
- }
|
||||
+ /* Replace O(S) linked-list walk with O(log S) eb-tree lookup.
|
||||
+ * proxy_build_cookie_tree() indexes all srv->cookie strings at
|
||||
+ * config-time; lookup is safe at runtime (cookies are immutable). */
|
||||
+ if (srv) {
|
||||
+ char cookie_key[delim - val_beg + 1];
|
||||
+ memcpy(cookie_key, val_beg, delim - val_beg);
|
||||
+ cookie_key[delim - val_beg] = '\0';
|
||||
+
|
||||
+ struct ebpt_node *node = ebis_lookup(&s->be->cookies_tree, cookie_key);
|
||||
+ srv = node ? container_of(node, struct server, cookie_node) : NULL;
|
||||
+ if (srv) {
|
||||
+ if ((srv->cur_state != SRV_ST_STOPPED) ||
|
||||
+ (s->be->options & PR_O_PERSIST) ||
|
||||
+ (s->flags & SF_FORCE_PRST)) {
|
||||
+ txn->flags &= ~TX_CK_MASK;
|
||||
+ txn->flags |= (srv->cur_state != SRV_ST_STOPPED) ? TX_CK_VALID : TX_CK_DOWN;
|
||||
+ s->flags |= SF_DIRECT | SF_ASSIGNED;
|
||||
+ stream_set_srv_target(s, srv);
|
||||
+ } else {
|
||||
+ txn->flags &= ~TX_CK_MASK;
|
||||
+ txn->flags |= TX_CK_DOWN;
|
||||
+ srv = NULL;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
73
defects/haproxy/unit/HaproxyTest.java
Normal file
73
defects/haproxy/unit/HaproxyTest.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit test for HAProxy http_ana.c defect.
|
||||
*
|
||||
* haproxy-0001: src/http_ana.c http_manage_client_side_cookies()
|
||||
* while (srv) { if memcmp(val_beg, srv->cookie, ...) } — O(S) linked-list
|
||||
* walk per HTTP request to match a cookie value to a backend server.
|
||||
* Fix: build struct eb_root cookies_tree at config-time (same as cfgdiag.c
|
||||
* already does for diagnostic purposes); use ebis_lookup() for O(log S) lookup.
|
||||
*/
|
||||
public class HaproxyTest {
|
||||
|
||||
// Simulate defect: O(S) linked-list walk per request
|
||||
static String cookieLookup_list(List<String[]> servers, String cookieVal) {
|
||||
// servers: list of {name, cookie}
|
||||
for (String[] srv : servers) { // O(S) — defect
|
||||
if (srv[1] != null && srv[1].equals(cookieVal)) {
|
||||
return srv[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Simulate fix: O(1) hash map (models eb-tree O(log S))
|
||||
static String cookieLookup_tree(Map<String, String> cookieIndex, String cookieVal) {
|
||||
return cookieIndex.get(cookieVal); // O(1) / O(log S) with eb-tree
|
||||
}
|
||||
|
||||
static void testHaproxy0001() throws Exception {
|
||||
int S = 1000; // backend servers
|
||||
List<String[]> servers = new ArrayList<>();
|
||||
Map<String, String> cookieIndex = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < S; i++) {
|
||||
String name = "backend" + i;
|
||||
String cookie = "srv" + String.format("%04d", i);
|
||||
servers.add(new String[]{name, cookie});
|
||||
cookieIndex.put(cookie, name);
|
||||
}
|
||||
|
||||
// Request targets last server (worst case for linear scan)
|
||||
String target = "srv" + String.format("%04d", S - 1);
|
||||
|
||||
// correctness
|
||||
String r1 = cookieLookup_list(servers, target);
|
||||
String r2 = cookieLookup_tree(cookieIndex, target);
|
||||
assert r1 != null && r1.equals(r2) : "list and tree must agree: " + r1 + " vs " + r2;
|
||||
assert cookieLookup_list(servers, "srvXXXX") == null;
|
||||
assert cookieLookup_tree(cookieIndex, "srvXXXX") == null;
|
||||
|
||||
// performance: simulate R HTTP requests
|
||||
int R = 50_000;
|
||||
long t0 = System.nanoTime();
|
||||
for (int r = 0; r < R; r++) cookieLookup_list(servers, target);
|
||||
long tList = System.nanoTime() - t0;
|
||||
|
||||
t0 = System.nanoTime();
|
||||
for (int r = 0; r < R; r++) cookieLookup_tree(cookieIndex, target);
|
||||
long tTree = System.nanoTime() - t0;
|
||||
|
||||
double ratio = (double) tList / tTree;
|
||||
System.out.printf("haproxy-0001: list=%.3fs tree=%.3fs ratio=%.1f×%n",
|
||||
tList / 1e9, tTree / 1e9, ratio);
|
||||
assert ratio > 20 : "Expected >20× speedup, got " + ratio;
|
||||
System.out.println("PASS haproxy-0001");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testHaproxy0001();
|
||||
System.out.println("ALL PASS");
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
16
defects/postfix/patch/CLEAN.md
Normal file
16
defects/postfix/patch/CLEAN.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# postfix — CLEAN
|
||||
|
||||
Scanned 2026-03-30. No CWE-407 defects warranting a patch.
|
||||
|
||||
Postfix systematically uses `htable` (hash table) for all hot-path membership
|
||||
tests: `been_here()` for recipient dedup, `delivered_hdr_find()` for loop
|
||||
detection, `qmgr_transport_find()` for transport lookup.
|
||||
|
||||
Two marginal candidates found, both LOW severity and admin-bounded:
|
||||
- `src/qmgr/qmgr_message.c:1214`: O(R×T) defer_transports check — T is
|
||||
admin-configured, not inflatable by remote senders.
|
||||
- `src/global/sasl_mech_filter.c:90`: O(M×P) SASL filter — M ≈ 5–20 mechs,
|
||||
P admin-configured.
|
||||
|
||||
Neither creates a situation where a remote party can inflate both dimensions
|
||||
of a nested loop.
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
--- a/src/backend/catalog/pg_inherits.c
|
||||
+++ b/src/backend/catalog/pg_inherits.c
|
||||
@@ -406,12 +406,16 @@ bool
|
||||
typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
|
||||
{
|
||||
bool result = false;
|
||||
Oid subclassRelid;
|
||||
Oid superclassRelid;
|
||||
Relation inhrel;
|
||||
- List *visited,
|
||||
- *queue;
|
||||
+ HTAB *visited_set; /* O(1) OID lookup; replaces O(V) list_member_oid */
|
||||
+ HASHCTL ctl;
|
||||
+ List *queue;
|
||||
ListCell *queue_item;
|
||||
|
||||
/* We need to work with the associated relation OIDs */
|
||||
subclassRelid = typeOrDomainTypeRelid(subclassTypeId);
|
||||
if (subclassRelid == InvalidOid)
|
||||
@@ -428,7 +432,14 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
|
||||
queue = list_make1_oid(subclassRelid);
|
||||
- visited = NIL;
|
||||
+
|
||||
+ ctl.keysize = sizeof(Oid);
|
||||
+ ctl.entrysize = sizeof(Oid);
|
||||
+ ctl.hcxt = CurrentMemoryContext;
|
||||
+ visited_set = hash_create("typeInheritsFrom visited set",
|
||||
+ 32,
|
||||
+ &ctl,
|
||||
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
|
||||
|
||||
inhrel = table_open(InheritsRelationId, AccessShareLock);
|
||||
|
||||
@@ -443,11 +454,12 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
|
||||
foreach(queue_item, queue)
|
||||
{
|
||||
Oid this_relid = lfirst_oid(queue_item);
|
||||
ScanKeyData skey;
|
||||
SysScanDesc inhscan;
|
||||
HeapTuple inhtup;
|
||||
+ bool found;
|
||||
|
||||
/*
|
||||
* If we've seen this relid already, skip it. This avoids extra work
|
||||
* in multiple-inheritance scenarios, and also protects us from an
|
||||
* infinite loop in case there is a cycle in pg_inherits (though
|
||||
* theoretically that shouldn't happen).
|
||||
*/
|
||||
- if (list_member_oid(visited, this_relid))
|
||||
+ hash_search(visited_set, &this_relid, HASH_ENTER, &found);
|
||||
+ if (found)
|
||||
continue;
|
||||
|
||||
- /*
|
||||
- * Okay, this is a not-yet-seen relid. Add it to the list of
|
||||
- * already-visited OIDs, then find all the types this relid inherits
|
||||
- * from and add them to the queue.
|
||||
- */
|
||||
- visited = lappend_oid(visited, this_relid);
|
||||
-
|
||||
ScanKeyInit(&skey,
|
||||
Anum_pg_inherits_inhrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
@@ -493,7 +497,7 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
|
||||
/* clean up ... */
|
||||
table_close(inhrel, AccessShareLock);
|
||||
|
||||
- list_free(visited);
|
||||
+ hash_destroy(visited_set);
|
||||
list_free(queue);
|
||||
|
||||
return result;
|
||||
113
defects/postgres/unit/PostgresTest.java
Normal file
113
defects/postgres/unit/PostgresTest.java
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit test for PostgreSQL pg_inherits.c defect.
|
||||
*
|
||||
* postgres-0001: src/backend/catalog/pg_inherits.c typeInheritsFrom()
|
||||
* BFS over pg_inherits using List *visited + list_member_oid() — O(V) per node,
|
||||
* O(V²) total for a deep/wide hierarchy.
|
||||
* Fix: replace List *visited with HTAB *visited_set (same pattern as
|
||||
* find_all_inheritors in the same file) for O(1) per-node visited check.
|
||||
*/
|
||||
public class PostgresTest {
|
||||
|
||||
// Simulate defect: List visited + linear scan
|
||||
static boolean typeInheritsFrom_list(int[][] parentEdges, int subclass, int superclass) {
|
||||
// parentEdges[i] = {child_oid, parent_oid}
|
||||
// BFS from subclass upward; visited is a list (O(V) scan per entry)
|
||||
List<Integer> queue = new ArrayList<>();
|
||||
List<Integer> visited = new ArrayList<>();
|
||||
queue.add(subclass);
|
||||
|
||||
int head = 0;
|
||||
while (head < queue.size()) {
|
||||
int current = queue.get(head++);
|
||||
boolean alreadySeen = false;
|
||||
for (int v : visited) { // O(V) — defect
|
||||
if (v == current) { alreadySeen = true; break; }
|
||||
}
|
||||
if (alreadySeen) continue;
|
||||
visited.add(current);
|
||||
|
||||
for (int[] edge : parentEdges) {
|
||||
if (edge[0] == current) {
|
||||
int parent = edge[1];
|
||||
if (parent == superclass) return true;
|
||||
queue.add(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simulate fix: HashSet visited
|
||||
static boolean typeInheritsFrom_htab(int[][] parentEdges, int subclass, int superclass) {
|
||||
List<Integer> queue = new ArrayList<>();
|
||||
Set<Integer> visited = new HashSet<>(); // O(1) lookup
|
||||
queue.add(subclass);
|
||||
|
||||
int head = 0;
|
||||
while (head < queue.size()) {
|
||||
int current = queue.get(head++);
|
||||
if (!visited.add(current)) continue; // O(1)
|
||||
|
||||
for (int[] edge : parentEdges) {
|
||||
if (edge[0] == current) {
|
||||
int parent = edge[1];
|
||||
if (parent == superclass) return true;
|
||||
queue.add(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void testPostgres0001() throws Exception {
|
||||
// Build a diamond-inheritance graph: V types in a chain with cross-links
|
||||
// Use the negative case (not found) — forces full BFS traversal of all V nodes,
|
||||
// maximising the visited-list size reached during each member check.
|
||||
int V = 800;
|
||||
// All-to-all fan-out: node i has edges to i+1..i+10 (wide BFS tree)
|
||||
List<int[]> edges = new ArrayList<>();
|
||||
for (int i = 0; i < V; i++) {
|
||||
for (int k = 1; k <= 10 && i + k < V; k++) {
|
||||
edges.add(new int[]{i, i + k});
|
||||
}
|
||||
}
|
||||
int[][] parentEdges = edges.toArray(new int[0][]);
|
||||
|
||||
int subclass = 0;
|
||||
int superclass = V - 1; // reachable (positive case)
|
||||
int missing = 999999; // not reachable (negative case — full traversal)
|
||||
|
||||
// correctness
|
||||
boolean r1 = typeInheritsFrom_list(parentEdges, subclass, superclass);
|
||||
boolean r2 = typeInheritsFrom_htab(parentEdges, subclass, superclass);
|
||||
assert r1 == r2 : "list and htab must agree (positive): " + r1 + " vs " + r2;
|
||||
|
||||
boolean n1 = typeInheritsFrom_list(parentEdges, subclass, missing);
|
||||
boolean n2 = typeInheritsFrom_htab(parentEdges, subclass, missing);
|
||||
assert n1 == n2 && !n1 : "negative case must agree";
|
||||
|
||||
// performance on negative case: forces complete traversal, visited grows to V
|
||||
int REPS = 100;
|
||||
long t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) typeInheritsFrom_list(parentEdges, subclass, missing);
|
||||
long tList = System.nanoTime() - t0;
|
||||
|
||||
t0 = System.nanoTime();
|
||||
for (int r = 0; r < REPS; r++) typeInheritsFrom_htab(parentEdges, subclass, missing);
|
||||
long tHash = System.nanoTime() - t0;
|
||||
|
||||
double ratio = (double) tList / tHash;
|
||||
System.out.printf("postgres-0001: list=%.3fs htab=%.3fs ratio=%.1f×%n",
|
||||
tList / 1e9, tHash / 1e9, ratio);
|
||||
assert ratio > 1.2 : "Expected >1.2× speedup, got " + ratio;
|
||||
System.out.println("PASS postgres-0001");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testPostgres0001();
|
||||
System.out.println("ALL PASS");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue