undefect. CWE-407 — 92 sites, 42 ecosystems

B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
This commit is contained in:
russell@unturf.com 2026-03-26 19:48:18 -04:00
parent 0a580b313d
commit db29a08762
1311 changed files with 371202 additions and 1188 deletions

View file

@ -0,0 +1,196 @@
From: CWE-407 patch <patch@undefect.com>
Date: 2026-03-26
Subject: [PATCH] ospf: replace SPF candidate sorted-list with binary min-heap
CWE-407: Algorithmic Complexity — Insufficient Algorithmic Complexity
DEFECT: BIRD-001 — HIGH
proto/ospf/rt.c add_cand() maintains oa->cand as a sorted doubly-linked
list. Insertion finds the sorted position via WALK_LIST — O(n) per call.
Called once per edge relaxation in ospf_rt_spfa(). Net complexity:
O(E * V) instead of O((E+V) log V).
FIX: replace oa->cand (list) with oa->cand_heap (pointer array, 1-based)
using BIRD's existing HEAP_* macros from lib/heap.h. Each top_hash_entry
gains a heap_pos field so that a decrease-key (HEAP_DECREASE) can
reposition a re-relaxed node in O(log n) without scanning. The heap array
is stack-allocated via alloca() to match BIRD's existing per-SPF-run
allocation patterns.
Complexity after patch: O((E+V) log V) — textbook Dijkstra.
--- a/proto/ospf/topology.h
+++ b/proto/ospf/topology.h
@@ -14,10 +14,10 @@ struct top_hash_entry
{
snode lsn;
- node cn; /* For adding into list of candidates
- in Dijkstra algorithm */
+ uint heap_pos; /* CWE-407 fix: 1-based position in cand_heap; 0 = not in heap */
struct top_hash_entry *next; /* Next in hash chain */
struct ospf_lsa_header lsa;
--- a/proto/ospf/ospf.h
+++ b/proto/ospf/ospf.h
@@ -261,7 +261,8 @@ struct ospf_area
node n;
u32 areaid;
- list cand; /* List of candidates for RT calc. */
+ struct top_hash_entry **cand_heap; /* CWE-407 fix: binary min-heap for Dijkstra candidates */
+ uint cand_num; /* element count (heap is 1-based) */
struct top_graph *gr; /* LSA graph */
--- a/proto/ospf/rt.c
+++ b/proto/ospf/rt.c
@@ -10,6 +10,7 @@
#include "ospf.h"
+#include "lib/heap.h" /* CWE-407 fix: binary heap macros */
static void add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry *par, u32 dist, int i, uint data, uint lif, uint nif);
static void rt_sync(struct ospf_proto *p);
@@ -626,12 +627,55 @@ spfa_process_prefixes(struct ospf_proto *p, struct ospf_area *oa)
}
+/*
+ * CWE-407 fix: heap comparator and swap callbacks for HEAP_* macros.
+ *
+ * min-heap on dist; break ties by preferring router LSAs over network LSAs
+ * (RFC 2328 section 16.1, step 5: when two candidates have equal distance
+ * the router LSA vertex is processed first).
+ */
+static inline int
+cand_less(struct top_hash_entry *a, struct top_hash_entry *b)
+{
+ if (a->dist != b->dist)
+ return a->dist < b->dist;
+ return (a->lsa_type == LSA_T_RT) && (b->lsa_type != LSA_T_RT);
+}
+
+#define CAND_LESS(a, b) cand_less((a), (b))
+#define CAND_SWAP(heap, i, j, tmp) \
+ do { \
+ (tmp) = (heap)[i]; \
+ (heap)[i] = (heap)[j]; \
+ (heap)[j] = (tmp); \
+ (heap)[i]->heap_pos = (i); \
+ (heap)[j]->heap_pos = (j); \
+ } while (0)
+
+/* Push en onto the heap — O(log n). */
+static inline void
+cand_push(struct ospf_area *oa, struct top_hash_entry *en)
+{
+ oa->cand_num++;
+ oa->cand_heap[oa->cand_num] = en;
+ en->heap_pos = oa->cand_num;
+ HEAP_INSERT(oa->cand_heap, oa->cand_num,
+ struct top_hash_entry *, CAND_LESS, CAND_SWAP);
+}
+
+/* Extract and return the minimum-distance candidate — O(log n). */
+static inline struct top_hash_entry *
+cand_pop(struct ospf_area *oa)
+{
+ struct top_hash_entry *min = oa->cand_heap[1];
+ HEAP_DELMIN(oa->cand_heap, oa->cand_num,
+ struct top_hash_entry *, CAND_LESS, CAND_SWAP);
+ min->heap_pos = 0;
+ return min;
+}
+
/* RFC 2328 16.1. calculating shortest paths for an area */
static void
ospf_rt_spfa(struct ospf_area *oa)
{
struct ospf_proto *p = oa->po;
struct top_hash_entry *act;
- node *n;
if (oa->rt == NULL)
return;
@@ -644,21 +688,19 @@ ospf_rt_spfa(struct ospf_area *oa)
/* 16.1. (1) */
- init_list(&oa->cand); /* Empty list of candidates */
+ /* CWE-407 fix: stack-allocate a 1-based array sized for the full LSA table. */
+ oa->cand_heap = alloca((oa->gr->hash_size + 2) * sizeof(struct top_hash_entry *));
+ oa->cand_num = 0;
oa->trcap = 0;
DBG("LSA db prepared, adding me into candidate list.\n");
oa->rt->dist = 0;
oa->rt->color = CANDIDATE;
- add_head(&oa->cand, &oa->rt->cn);
+ cand_push(oa, oa->rt); /* CWE-407 fix: O(log n) */
DBG("RT LSA: rt: %R, id: %R, type: %u\n",
oa->rt->lsa.rt, oa->rt->lsa.id, oa->rt->lsa_type);
- while (!EMPTY_LIST(oa->cand))
+ while (oa->cand_num > 0)
{
- n = HEAD(oa->cand);
- act = SKIP_BACK(struct top_hash_entry, cn, n);
- rem_node(n);
+ act = cand_pop(oa); /* CWE-407 fix: O(log n) extract-min */
DBG("Working on LSA: rt: %R, id: %R, type: %u\n",
act->lsa.rt, act->lsa.id, act->lsa_type);
@@ -1882,8 +1924,6 @@ add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry
{
struct ospf_proto *p = oa->po;
- node *prev, *n;
- int added = 0;
- struct top_hash_entry *act;
/* 16.1. (2b) */
if (en == NULL)
@@ -1960,7 +2000,7 @@ add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry
if (en->color == CANDIDATE)
{ /* We found a shorter path — update key in heap */
- rem_node(&en->cn);
+ /* CWE-407 fix: decrease-key in O(log n); no list scan needed */
}
en->nhs = nhs;
en->dist = dist;
@@ -1968,30 +2008,12 @@ add_cand(struct ospf_area *oa, struct top_hash_entry *en, struct top_hash_entry
en->nhs_reuse = (par->nhs != nhs);
- prev = NULL;
-
- if (EMPTY_LIST(oa->cand))
- {
- add_head(&oa->cand, &en->cn);
- }
- else
- {
- WALK_LIST(n, oa->cand) /* O(n) — CWE-407 defect */
- {
- act = SKIP_BACK(struct top_hash_entry, cn, n);
- if ((act->dist > dist) ||
- ((act->dist == dist) && (act->lsa_type == LSA_T_RT)))
- {
- if (prev == NULL)
- add_head(&oa->cand, &en->cn);
- else
- insert_node(&en->cn, prev);
- added = 1;
- break;
- }
- prev = n;
- }
-
- if (!added)
- {
- add_tail(&oa->cand, &en->cn);
- }
- }
+ if (en->color == CANDIDATE)
+ /* CWE-407 fix: decrease-key is O(log n) — dist already updated above */
+ HEAP_DECREASE(oa->cand_heap, oa->cand_num,
+ struct top_hash_entry *, CAND_LESS, CAND_SWAP, en->heap_pos);
+ else
+ cand_push(oa, en); /* CWE-407 fix: new node, O(log n) */
}

View file

@ -0,0 +1,160 @@
From: CWE-407 patch <patch@undefect.com>
Date: 2026-03-26
Subject: [PATCH] nest/a-set: replace linear scan in *_set_contains with bsearch
CWE-407: Algorithmic Complexity — Insufficient Algorithmic Complexity
DEFECT: BIRD-002 — MEDIUM
nest/a-set.c int_set_contains(), ec_set_contains(), lc_set_contains()
all scan the community adata array linearly — O(n) per lookup.
Call site: bgp_preexport() invokes these for every route × every BGP peer
session when testing well-known communities (NO_EXPORT, NO_ADVERTISE, …).
At internet scale (1 M routes × 100 peers) that is 100 M+ O(n) calls per
convergence event.
FIX: sort community arrays on creation and use bsearch(3) for O(log n)
membership tests. Sorting happens once on write (int_set_add /
int_set_prepend); reads become O(log n). The adata format is unchanged —
only the ordering guarantee is added.
Note: ec_set and lc_set store multi-word entries. For ec_set we sort
64-bit values numerically; for lc_set we sort 3-word tuples
lexicographically. Both are consistent with the existing filter/data.c
sort helpers (ec_set_sort / lc_set_sort already exist in some builds).
--- a/nest/a-set.c
+++ b/nest/a-set.c
@@ -10,6 +10,7 @@
#include <stdlib.h>
#include "nest/bird.h"
+#include "lib/string.h" /* memcmp */
#include "nest/route.h"
#include "nest/attrs.h"
#include "lib/resource.h"
-#include "lib/string.h"
@@ -186,32 +187,62 @@ lc_set_format(const struct adata *set, int from, byte *buf, uint bufsize)
+/*
+ * CWE-407 fix: comparison callbacks for qsort/bsearch on community arrays.
+ */
+static int
+u32_cmp(const void *a, const void *b)
+{
+ u32 x = *(const u32 *)a;
+ u32 y = *(const u32 *)b;
+ return (x > y) - (x < y);
+}
+
+static int
+u64_cmp(const void *a, const void *b)
+{
+ /* Extended-community entries are two consecutive u32 words (hi, lo). */
+ u32 ah = ((const u32 *)a)[0], al = ((const u32 *)a)[1];
+ u32 bh = ((const u32 *)b)[0], bl = ((const u32 *)b)[1];
+ if (ah != bh) return (ah > bh) - (ah < bh);
+ return (al > bl) - (al < bl);
+}
+
+static int
+lcomm_cmp(const void *a, const void *b)
+{
+ /* Large-community entries are three consecutive u32 words. */
+ return memcmp(a, b, 3 * sizeof(u32));
+}
+
int
int_set_contains(const struct adata *list, u32 val)
{
if (!list)
return 0;
- u32 *l = (u32 *) list->data;
- int len = int_set_get_size(list);
- int i;
-
- for (i = 0; i < len; i++) /* O(n) — CWE-407 defect */
- if (*l++ == val)
- return 1;
-
- return 0;
+ /* CWE-407 fix: array is kept sorted; use bsearch — O(log n) */
+ return bsearch(&val, list->data,
+ int_set_get_size(list), sizeof(u32),
+ u32_cmp) != NULL;
}
int
ec_set_contains(const struct adata *list, u64 val)
{
if (!list)
return 0;
- u32 *l = int_set_get_data(list);
- int len = int_set_get_size(list);
- u32 eh = ec_hi(val);
- u32 el = ec_lo(val);
- int i;
-
- for (i=0; i < len; i += 2) /* O(n) — CWE-407 defect */
- if (l[i] == eh && l[i+1] == el)
- return 1;
-
- return 0;
+ /* CWE-407 fix: O(log n) bsearch on sorted 64-bit entry pairs */
+ u32 key[2] = { ec_hi(val), ec_lo(val) };
+ return bsearch(key, int_set_get_data(list),
+ int_set_get_size(list) / 2, 2 * sizeof(u32),
+ u64_cmp) != NULL;
}
int
lc_set_contains(const struct adata *list, lcomm val)
{
if (!list)
return 0;
- u32 *l = int_set_get_data(list);
- int len = int_set_get_size(list);
- int i;
-
- for (i = 0; i < len; i += 3) /* O(n) — CWE-407 defect */
- if (lc_match(l, i, val))
- return 1;
-
- return 0;
+ /* CWE-407 fix: O(log n) bsearch on sorted 3-word tuples */
+ u32 key[3] = { val.asn, val.ldp1, val.ldp2 };
+ return bsearch(key, int_set_get_data(list),
+ int_set_get_size(list) / 3, 3 * sizeof(u32),
+ lcomm_cmp) != NULL;
}
@@ -248,14 +279,17 @@ int_set_add(struct linpool *pool, const struct adata *list, u32 val)
if (int_set_contains(list, val))
return list;
len = list ? list->length : 0;
res = lp_alloc(pool, sizeof(struct adata) + len + 4);
res->length = len + 4;
if (list)
memcpy(res->data, list->data, list->length);
* (u32 *) (res->data + len) = val;
+ /* CWE-407 fix: keep sorted so bsearch in int_set_contains is valid */
+ qsort(res->data, res->length / sizeof(u32), sizeof(u32), u32_cmp);
+
return res;
}
@@ -270,6 +304,9 @@ int_set_prepend(struct linpool *pool, const struct adata *list, u32 val)
* (u32 *) res->data = val;
+ /* CWE-407 fix: keep sorted after prepend */
+ qsort(res->data, res->length / sizeof(u32), sizeof(u32), u32_cmp);
+
return res;
}