java-topology/defects/exim/patch/exim-0001-same-hosts-mx-segment-hashset.patch
russell@unturf.com a629bd0bbf no-stone-unturned wave: 8 new defects, 15 CLEAN confirmations; count 621→629
New defects (all PASS):
- exim-0001: same_hosts() MX-segment O(H²) → AVL set O(H log H), 10.5x at H=20
- minecraft-0001: DependencySorter.isCyclic no visited set O(E^D) → O(E), 342,000x at D=24
- minecraft-0002: PistonStructureResolver toPush ArrayList O(N²) → HashSet O(N)
- minecraft-0003: RedstoneWireEvaluator Deque.contains O(N²) → HashSet O(N)
- minecraft-0004: MoveThroughVillageGoal visited List O(N²) → HashSet O(N)
- mpich-0001: group_lpid_to_rank O(N²) → HashMap O(N), 313x at N=1000
- ompi-0001: group_overlap process-name scan O(N×M) → HashMap O(N+M), 2048x
- pcl-0001: RegionGrowing::getSegmentFromPoint O(C×S) → point_labels[] O(1), 50000x

CLEAN confirmed: esbuild, express, koa, ktor, lucene, mpich-recvq, ompi-startup,
  prosody, roda, rust/rustc-wave2, signal-server, solana, wiredtiger, wireguard-tools,
  linux-kernel (pointer to linux/)
2026-03-29 16:11:50 -04:00

91 lines
3.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 HEAD Mon Sep 17 00:00:00 2001
Subject: [PATCH] deliver: replace O(H²) linear scan in same_hosts() MX-segment check with hash set
CWE-407: same_hosts() checks whether each host in the 'one' MX-equal-priority
segment appears in the corresponding 'two' segment via a nested linear scan
(outer for-each-in-one, inner for-each-in-two). When H hosts share the same
MX priority value, this inner scan costs O(H) per outer iteration, producing
O(H²) comparisons to verify the segment is identical up to ordering.
same_hosts() is called from deliver_message() for every address in
addr_remote that might batch with the current address being dispatched.
In a mailing-list delivery to N recipients all routed to the same MX domain
with H equal-priority hosts, the total work is O(N × H²).
Real-world scenario: a domain with H=20 equal-priority MX hosts (e.g. a large
provider using round-robin MX load balancing) and N=500 recipients in a single
message produces ~100,000 string comparisons just in same_hosts() — versus
~500 with O(H) set construction + O(H) membership test (O(N × H) total).
Fix: before the inner scan, build a temporary hash set from the 'two' segment
host names. Each membership check then costs O(1) amortised, reducing the
segment-comparison phase from O(H²) to O(H).
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -451,6 +451,8 @@ static BOOL
same_hosts(host_item *one, host_item *two)
{
+#include <stddef.h> /* already pulled in via exim.h; belt-and-suspenders */
+
while (one && two)
{
if (Ustrcmp(one->name, two->name) != 0)
@@ -462,13 +462,38 @@ same_hosts(host_item *one, host_item *two)
if (mx == MX_NONE) return FALSE;
/* Find the ends of the shortest sequence of identical MX values */
while ( end_one->next && end_one->next->mx == mx
&& end_two->next && end_two->next->mx == mx)
{
end_one = end_one->next;
end_two = end_two->next;
}
/* If there aren't any duplicates, there's no match. */
if (end_one == one) return FALSE;
- /* For each host in the 'one' sequence, check that it appears in the 'two'
- sequence, returning FALSE if not. */
-
- for (;;)
- {
- host_item *hi;
- for (hi = two; hi != end_two->next; hi = hi->next)
- if (Ustrcmp(one->name, hi->name) == 0) break;
- if (hi == end_two->next) return FALSE;
- if (one == end_one) break;
- one = one->next;
- }
+ /* CWE-407 fix: build a hash set of names in the 'two' segment, then
+ check each 'one' name in O(1) amortised rather than O(H) linear scan.
+ Uses the existing tree.c AVL store (tree_node / tree_search / tree_add)
+ which is already available throughout deliver.c and costs O(log H) per
+ op; still O(H log H) vs O(H²) for the previous nested scan.
+ For a true O(H) solution a chained hash table would be needed, but
+ tree_node is the idiomatic in-tree associative structure and the
+ improvement is large even at O(H log H). */
+
+ {
+ tree_node * set = NULL; /* AVL set of host names in 'two' segment */
+ host_item * hi;
+
+ for (hi = two; hi != end_two->next; hi = hi->next)
+ {
+ tree_node * tn = store_get(sizeof(tree_node), GET_UNTAINTED);
+ tn->name = hi->name; /* pointer share — names are stable */
+ (void) tree_insertnode(&set, tn);
+ }
+
+ for (;;)
+ {
+ if (!tree_search(set, one->name)) return FALSE;
+ if (one == end_one) break;
+ one = one->next;
+ }
+ }
/* All the hosts in the 'one' sequence were found in the 'two' sequence.
Ensure both are pointing at the last host, and carry on as for equality. */