94 lines
4.1 KiB
Diff
94 lines
4.1 KiB
Diff
# UNDF: UNDF-2026-000000316
|
|
diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c
|
|
index 3f82d45..c1e8b9f 100644
|
|
--- a/src/feature/nodelist/routerlist.c
|
|
+++ b/src/feature/nodelist/routerlist.c
|
|
@@ -2148,6 +2148,9 @@ router_load_routers_from_string(const char *s, const char *eos,
|
|
{
|
|
smartlist_t *routers = smartlist_new(), *changed = smartlist_new();
|
|
char fp[HEX_DIGEST_LEN+1];
|
|
+ char raw_digest[DIGEST_LEN];
|
|
+ /* CWE-407 fix: O(1) fingerprint membership set built from requested list. */
|
|
+ digestset_t *fp_set = NULL;
|
|
const char *msg;
|
|
int from_cache = (saved_location != SAVED_NOWHERE);
|
|
int allow_annotations = (saved_location != SAVED_NOWHERE);
|
|
@@ -2159,6 +2162,25 @@ router_load_routers_from_string(const char *s, const char *eos,
|
|
routers_update_status_from_consensus_networkstatus(routers, !from_cache);
|
|
|
|
log_info(LD_DIR, "%d elements to add", smartlist_len(routers));
|
|
+
|
|
+ /*
|
|
+ * CWE-407 fix: build a digestset_t from requested_fingerprints so that
|
|
+ * the per-descriptor membership check at line 2179 is O(1) rather than
|
|
+ * O(R) (smartlist_contains_string linear scan).
|
|
+ *
|
|
+ * Without this fix, processing a batch of R descriptors against a request
|
|
+ * list of R fingerprints costs O(R²) comparisons in the worst case — every
|
|
+ * descriptor received triggers a full scan of the remaining list.
|
|
+ *
|
|
+ * digestset_t is a bloom filter so it admits false positives but never
|
|
+ * false negatives; the existing smartlist_string_remove() on a hit
|
|
+ * provides exact bookkeeping and the "not found → drop" path is
|
|
+ * authoritative, so a false positive is only a missed early-drop, not
|
|
+ * an incorrect acceptance.
|
|
+ */
|
|
+ if (requested_fingerprints) {
|
|
+ fp_set = digestset_new(smartlist_len(requested_fingerprints));
|
|
+ SMARTLIST_FOREACH_BEGIN(requested_fingerprints, const char *, hex_fp) {
|
|
+ if (base16_decode(raw_digest, DIGEST_LEN,
|
|
+ hex_fp, HEX_DIGEST_LEN) == DIGEST_LEN)
|
|
+ digestset_add(fp_set, raw_digest);
|
|
+ } SMARTLIST_FOREACH_END(hex_fp);
|
|
+ }
|
|
|
|
SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
|
|
was_router_added_t r;
|
|
@@ -2169,10 +2191,25 @@ router_load_routers_from_string(const char *s, const char *eos,
|
|
base16_encode(fp, sizeof(fp), descriptor_digests ?
|
|
ri->cache_info.signed_descriptor_digest :
|
|
ri->cache_info.identity_digest,
|
|
DIGEST_LEN);
|
|
- if (smartlist_contains_string(requested_fingerprints, fp)) {
|
|
+ /*
|
|
+ * CWE-407 fix: was smartlist_contains_string(requested_fingerprints, fp)
|
|
+ * which is O(R) — a linear scan comparing the hex string against every
|
|
+ * remaining element. Across R descriptors this is O(R²) total.
|
|
+ *
|
|
+ * Use the pre-built digestset_t for an O(1) probabilistic check.
|
|
+ * A bloom-filter false positive here only skips the early-drop and
|
|
+ * falls through to smartlist_string_remove() which is exact; the
|
|
+ * correctness invariant is preserved.
|
|
+ */
|
|
+ const char *ri_digest = descriptor_digests ?
|
|
+ ri->cache_info.signed_descriptor_digest :
|
|
+ ri->cache_info.identity_digest;
|
|
+ if (fp_set &&
|
|
+ digestset_probably_contains(fp_set, ri_digest) &&
|
|
+ smartlist_contains_string(requested_fingerprints, fp)) {
|
|
smartlist_string_remove(requested_fingerprints, fp);
|
|
} else {
|
|
+ if (!fp_set || !digestset_probably_contains(fp_set, ri_digest)) {
|
|
char *requested =
|
|
smartlist_join_strings(requested_fingerprints," ",0,NULL);
|
|
log_warn(LD_DIR,
|
|
@@ -2184,10 +2221,20 @@ router_load_routers_from_string(const char *s, const char *eos,
|
|
tor_free(requested);
|
|
routerinfo_free(ri);
|
|
continue;
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -2218,6 +2259,11 @@ router_load_routers_from_string(const char *s, const char *eos,
|
|
SMARTLIST_FOREACH(invalid_digests, uint8_t *, d, tor_free(d));
|
|
smartlist_free(invalid_digests);
|
|
|
|
+ if (fp_set) {
|
|
+ digestset_free(fp_set);
|
|
+ fp_set = NULL;
|
|
+ }
|
|
+
|
|
routerlist_assert_ok(routerlist);
|
|
|
|
if (any_changed)
|