161 lines
4.6 KiB
Diff
161 lines
4.6 KiB
Diff
# UNDF: UNDF-2026-000000013
|
||
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;
|
||
}
|