java-topology/defects/bird/patch/bird-0002-bgp-community-bsearch.patch
russell@unturf.com db29a08762 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.
2026-03-26 19:48:18 -04:00

160 lines
4.6 KiB
Diff
Raw 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.

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;
}