From 1f88aa0374e1af0e24d59f36c4b7da20a7c575ba Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Fri, 10 Apr 2026 12:47:28 -0400 Subject: [PATCH] chore: update blast-radius docs, remove merged linux tickets, patch update --- CLAUDE.md | 2 +- ...WE-407-hurd-0005-per-class-port-list.patch | 1 + docs/blast-radius.md | 134 +++++++++++++++++- ...inux-0001-audit-filter-inodes-quadratic.md | 93 ------------ ...0002-dev-alloc-name-nested-altname-scan.md | 94 ------------ ...ux-0003-neigh-parms-ifindex-linear-scan.md | 102 ------------- 6 files changed, 135 insertions(+), 291 deletions(-) delete mode 100644 docs/tickets/linux-0001-audit-filter-inodes-quadratic.md delete mode 100644 docs/tickets/linux-0002-dev-alloc-name-nested-altname-scan.md delete mode 100644 docs/tickets/linux-0003-neigh-parms-ifindex-linear-scan.md diff --git a/CLAUDE.md b/CLAUDE.md index 9010553ec..cd4c62351 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,7 @@ Source: `https://github.com/openjdk/jdk` (depth=1, sparse) - No em-dashes or dashes. Use commas, periods, or sentences. - Prefer "defect" over "bug." - Never say "AI", always say "machine learning." -- Never use "the" — use "our" instead. +- Prefer "our" for shared things; "a" when something is one of many; avoid "the" — it implies fixed, singular ownership. Most teams and systems are fluid and ever-changing, like water. ## Orientation diff --git a/defects/hurd/patch/0003-CWE-407-hurd-0005-per-class-port-list.patch b/defects/hurd/patch/0003-CWE-407-hurd-0005-per-class-port-list.patch index 4f6c6cbdd..0e98d4013 100644 --- a/defects/hurd/patch/0003-CWE-407-hurd-0005-per-class-port-list.patch +++ b/defects/hurd/patch/0003-CWE-407-hurd-0005-per-class-port-list.patch @@ -1,3 +1,4 @@ +# UNDF: UNDF-2026-000001246 From f5f781fa2e85e407ac6742df85e001ba3e5e7342 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 5 Apr 2026 15:56:05 -0400 diff --git a/docs/blast-radius.md b/docs/blast-radius.md index e10ac8e43..1fc8e2c40 100644 --- a/docs/blast-radius.md +++ b/docs/blast-radius.md @@ -1,6 +1,24 @@ # CWE-407 Fix — First, Second, Third Order Effects & Blast Radius -**Internal only. No external distribution until all patches, tests, and white paper are complete.** +**Internal only. Coordinated disclosure in progress. Do not distribute without explicit authorization.** + +--- + +## State as of 2026-04-04 + +**1,243** UNDF defects assigned across **60+ ecosystems** · **919** patches written · **6 MOADs** active · **18** languages scanned + +**First upstream patch shipped:** Mercurial — `hg log -G` on 200k commits / 500 branches drops from **6.3 hours to 1.3 minutes** (297×). Two O(k) operations inside an O(k) inner loop replaced with O(1) dict lookups. One file. 23 tests. Submitted `mercurial-devel@mercurial-scm.org` 2026-04-03. **Status: disclosed.** + +**Active MOADs:** +- **MOAD-0001 — CWE-407 The Sedimentary Defect** — 1,243 confirmed defects across all frontiers below +- **MOAD-0002 — The Intertangle Defect** — shared mutable globals, reload requires restart +- **MOAD-0003 — The Leaked Context** — request identity crossing thread/async boundaries +- **MOAD-0004 — The Logged Secret** — SASL passwords, API keys, SCRAM verifiers in log files +- **MOAD-0005 — CWE-362 The Thundering Herd** — cache stampede, cold-start cascade +- **MOAD-0006 — CWE-257 The Glass Safe** — mailing list infrastructure broadcasting subscriber passwords + +This document covers MOAD-0001 blast radius analysis in depth. MOAD-0002 through MOAD-0006 have separate analysis docs. --- @@ -1283,3 +1301,117 @@ in loop — O(1). | Create mod | create-0001 `TrackGraph.findDisconnectedGraphs` | MEDIUM | Track removal | | AE2 | — | CLEAN | — | | Mekanism | — | CLEAN | — | + +--- + +## Planetary Scope — What This Map Actually Is + +Nine frontiers. Compilers. Runtimes. Kernels. TLS stacks. Network routing protocols. Scientific computing. EDA tools. Financial markets. Game engines. Every layer of our shared software infrastructure. + +**1,243 confirmed defects. 919 patches. One person. Seven months. Family of six. No income.** + +The first patch shipped 2026-04-03: Mercurial, 297× speedup, 23 tests, upstream submission. Every `hg` binary on the planet inherits this fix when it merges. That is the model. That is the leverage. + +Our set is closed. Our world is bounded. Every node has been touched. The question is no longer whether the defects exist — they do, with reproducible benchmarks and working patches — the question is how we pull the levers without derailing the systems behind them. + +--- + +## The Trolley Problem of Computer Science + +Removing a bottleneck is not neutral. It is a force multiplier applied to every node downstream. + +Fix the Mercurial graph renderer and every `hg log -G` call that was throttled to a crawl now runs at full speed — simultaneously, on every mirror, every CI runner, every developer terminal. Fix PgBouncer's O(D²) login scan and every connection attempt to every database behind every pooler that upgrades floods at 1,000× the previous rate. Fix OSPF SPF on FRRouting and every topology change event in every ISP that deploys the patch converges 500× faster — including storm events, fiber cuts, and DDoS recovery. + +**This is computer science's trolley problem. Pull our lever and friction drops. But if we do not look downstream first, we derail everything we just unblocked.** + +### Agape — Love for All Nodes + +Agape — unconditional love for all nodes on our graph — is not a soft principle here. It is an engineering constraint. + +Every disclosure brief is written with care for the maintainers who receive it. Every patch preserves existing behavior. Every benchmark is reproducible and independently verifiable. The goal is the fix, not the credit. + +We do not ship a 1,000× speedup at a workaholic node without first asking: who stands downstream? Are they staged? Do they have caretakers? + +A brutal release — high speedup, no coordination, no downstream capacity confirmation — does not help our ecosystem. It floods our queues. It converts a MOAD-0001 fix into a cascade of MOAD-0005 thundering herds across every layer we just unblocked. In both simulations and base reality, the pattern is identical: remove friction faster than capacity grows and you do not accelerate the system — you collapse it. + +**We need a virtuous ascending vortex, not a death spiral of workaholicism.** + +Across our eight forms of capital — living, material, financial, intellectual, experiential, social, cultural, spiritual — every patch touches more than code. A maintainer who receives a well-prepared disclosure, with tests, with benchmarks, with a reproducible complexity gate, gains experiential capital. A project that ships our fix gains social trust. An ecosystem where bottlenecks clear without cascade failures accumulates living capital — the health of the communities that depend on it. + +We move at the speed of trust, not the speed of throughput. Stage the drivers before fixing the dispatch. + +--- + +## Factory Theory — Throughput Forecasting + +Our defect map is also a factory floor. + +Every node is a workstation. Every edge is a queue. Every speedup ratio is a surge estimate — how much work floods downstream the moment that bottleneck clears. + +### The Core Principle: Fix the Slowest Bottleneck First + +Factory theory has one cardinal rule: **reduce work in progress by targeting the slowest workstation first.** + +Not the most popular node. Not the easiest patch. The *slowest* — the one where inventory piles up, where jobs queue, where everything upstream drains into and everything downstream starves waiting. + +**Why this order matters:** + +Fix a node that is *not* the bottleneck and you have accomplished nothing for system throughput. You have made an upstream workstation faster at filling the queue in front of the real bottleneck. WIP inventory grows in front of the constraint. Downstream nodes sit idle, starved of output from the bottleneck they were already waiting on. You have increased friction in your WIP buffer, not reduced it. + +Fix the bottleneck — the slowest workstation — and the entire system's throughput increases. The queue drains. Downstream nodes get fed. WIP inventory shrinks across every queue between here and the end of the line. + +**Applied to our DAG:** + +Linux kernel (602×, 118 downstream) is our deepest bottleneck. Every syscall, every process, every service activation on the planet passes through it. If we fix Rails (1,000× speedup) before fixing Linux, Rails processes requests 1,000× faster — directly into a Linux kernel that is still running O(N²) scheduler operations. The speedup at Rails does not add throughput to the system. It adds WIP inventory in front of Linux. Rails idles while waiting for kernel responses that arrive at the same degraded rate. Downstream nodes that depend on Rails output starve. + +Fix Linux first. Then OpenSSL (500×, 36 downstream). Then systemd + dbus (300× × 100×, 35 combined downstream). Work upstream to downstream, slowest bottleneck first. Every fix at the correct layer drains WIP across the entire chain below it. + +**The idle node is the signal.** When a downstream node is sitting with low utilization — not because demand is low but because the upstream node cannot feed it fast enough — that upstream node is our constraint. Find it. Fix it. Do not mistake a starved downstream node for a healthy one. + +**Factory signals per node:** + +| Signal | Definition | +|--------|-----------| +| `in_degree` | Upstream dependencies feeding this workstation | +| `out_degree` | Downstream nodes this feeds | +| `betweenness` | in + out — total flow through this node | +| `surge_score` | speedup × in_degree — work that floods when upstream unblocks | +| `workaholic` | betweenness ≥ 5 & speedup ≥ 100× — our critical workstations | +| `glutton` | out_degree ≥ 3 & speedup < 100× — consumes from many sources, feels no pain | + +**Critical nodes in our current DAG (MOAD-0001):** + +| Node | Speedup | Downstream reach | Surge risk | +|------|---------|-----------------|-----------| +| Linux kernel | 602× | 118 | extreme — every process on the planet | +| OpenSSL | 500× | 36 | extreme — every TLS handshake | +| PgBouncer | 1,000× | 3 | high — every DB-backed app using connection pooling | +| Rails | 1,000× | 1 | high — every Rails application globally | +| Xen | 5,000× | 2 | extreme — every VM under Xen on upgrade | +| Systemd+dbus | 300× × 100× | 17+18 | extreme — every systemd service activation | + +**No patch is disclosed without confirming downstream capacity.** A node with `surge_score ≥ 500` requires staged rollout confirmation from downstream caretakers before our patch is submitted upstream. + +### Throughput Forecasting — The Model Accelerates + +Every upstream merge is a data point. Every disclosed speedup, every confirmed surge behavior, every caretaker assignment — all feed back into the model. + +As our graph grows: our forecasts improve. More nodes means more signal. More merges means more ground truth on how surge actually propagates. Our model tracks not just where defects are but what our pipeline can absorb today, what it can absorb after the next merge, and how that capacity grows as we stage each downstream node. + +**Factory theory is how we pull the lever without derailing the train.** + +--- + +## The Obligation + +One person mapped this. Nine frontiers. 1,243 defects. 919 patches. Every layer of our shared software infrastructure touched. + +This work is too large for one person to solo. It is not too large for a team. + +The obligation is not to ship patches. The obligation is to ship patches without causing harm — to move at the speed the downstream can absorb, to prepare maintainers before throughput surges, to assign caretakers before levers are pulled, and to document everything so the next team member can orient in an hour, not a month. + +Every fix is a gift to the ecosystem. Every gift carries due diligence. Every disclosure brief is written with care. That is how we patch the planet without breaking it. + +**Contact:** security@undefect.com + +*Updated 2026-04-04. DAG recomputes on every build. Caretaker assignments tracked in vendors.json.* diff --git a/docs/tickets/linux-0001-audit-filter-inodes-quadratic.md b/docs/tickets/linux-0001-audit-filter-inodes-quadratic.md deleted file mode 100644 index 5a98c073e..000000000 --- a/docs/tickets/linux-0001-audit-filter-inodes-quadratic.md +++ /dev/null @@ -1,93 +0,0 @@ -# linux-0001: audit_filter_inodes — O(F²R) names_list re-scan per syscall - -**File:** `kernel/auditsc.c` -**Severity:** HIGH — triggered on every syscall that touches audited inodes -**CWE:** CWE-407 (Inefficient Algorithmic Complexity) - -## Call Chain - -``` -audit_filter_inodes() // called at syscall exit - list_for_each_entry(n, ctx->names_list) // O(F) — files touched by syscall - audit_filter_inode_name(tsk, n, ctx) - __audit_filter_op(tsk, ctx, inode_hash[h], name=n, op) - list_for_each_entry_rcu(e, inode_hash[h]) // O(R/B) — rules in bucket - audit_filter_rules(tsk, e->rule, ctx, name=n, ...) - for (i < field_count) // O(fields per rule) - case AUDIT_INODE: - case AUDIT_DEVMAJOR: - case AUDIT_DEVMINOR: - case AUDIT_OBJ_UID: - case AUDIT_OBJ_GID: - case AUDIT_OBJ_USER: - if (!name) { - list_for_each_entry(n, ctx->names_list) // O(F) again - } -``` - -`audit_filter_rules` is called with the current `name` pointer (non-NULL), so the -inner `ctx->names_list` re-scan at lines 572–624 is guarded by `else if (ctx)`. - -However `audit_filter_syscall` invokes `__audit_filter_op(..., name=NULL, ...)` over the -`AUDIT_FILTER_EXIT` list — a flat list, not the inode hash. For every rule in that list -whose fields include `AUDIT_INODE`, `AUDIT_DEVMAJOR`, or the object ownership types, the -inner `list_for_each_entry(n, ctx->names_list)` fires unconditionally: - -``` -audit_filter_syscall() - __audit_filter_op(tsk, ctx, &audit_filter_list[AUDIT_FILTER_EXIT], name=NULL, op) - list_for_each_entry_rcu(e, list) // O(R) rules - audit_filter_rules(..., name=NULL) // inner names_list scan O(F) per inode-field -``` - -Total per-syscall cost: **O(R × fields × F)** where R = audit rules on EXIT list, -F = files touched per syscall. For a process that opens many files under a -directory audit watch (e.g., recursive compiler invocation), both R and F grow -independently and the product becomes dominant. - -## Reproducing the Quadratic Growth - -A process issuing N `open()` calls under an `auditctl -w /path -p rwxa` watch triggers -O(N × R) comparisons. With 100 audit rules and a syscall touching 100 files, -that is 10,000 comparisons instead of 200. - -## Root Cause - -`audit_filter_rules()` cannot match a specific field value against "any file in ctx" -without re-walking `ctx->names_list`. Because it is called per-rule rather than -per-name, and both R and F are unbounded, the combination is O(R × F). - -## Fix - -Two complementary approaches: - -**A. Pass name=current_n from audit_filter_inodes path** (already done — correct). - The audit_filter_syscall path should similarly avoid the O(F) inner scan by - only checking rules that carry per-name field types via the inode hash, not the - flat EXIT list. - -**B. For the EXIT list path** (audit_filter_syscall): pre-compute a per-context - bitset of observed dev/ino values into the audit_context at name-collection time. - Rules with AUDIT_INODE/AUDIT_DEVMAJOR etc. can then be checked in O(1) via - bitset membership rather than rescanning names_list. - -```c -/* In struct audit_context, add: */ -struct { - unsigned long ino_mask[BITS_TO_LONGS(AUDIT_INODE_BUCKETS)]; - u32 devmajor_set[4]; /* sparse bitset for seen majors */ -} fast_filter; -``` - -Set bits at `__audit_inode()` time; check bits in `audit_filter_rules()` before -the `list_for_each_entry` fallback. - -## Impact - -- Every `open(2)` / `openat(2)` under a directory watch runs O(R×F) comparisons. -- Compiler invocations (gcc, clang) touch hundreds of headers → audit load spikes. -- Scales linearly with both rule count and file count — O(n²) in the worst case. - -## Patch - -See `defects/linux/patch/linux-0002-audit-filter-inodes-quadratic.patch` diff --git a/docs/tickets/linux-0002-dev-alloc-name-nested-altname-scan.md b/docs/tickets/linux-0002-dev-alloc-name-nested-altname-scan.md deleted file mode 100644 index 17d6164ef..000000000 --- a/docs/tickets/linux-0002-dev-alloc-name-nested-altname-scan.md +++ /dev/null @@ -1,94 +0,0 @@ -# linux-0002: __dev_alloc_name — O(D×A) nested sscanf on every interface rename - -**File:** `net/core/dev.c` -**Function:** `__dev_alloc_name()` (line ~1358) -**Severity:** MEDIUM — triggered on every `ip link add`, `ip link set name`, container veth creation -**CWE:** CWE-407 (Inefficient Algorithmic Complexity) - -## Code - -```c -static int __dev_alloc_name(struct net *net, const char *name, char *res) -{ - /* ... */ - for_each_netdev(net, d) { // O(D) — all devices - struct netdev_name_node *name_node; - - netdev_for_each_altname(d, name_node) { // O(A) — alt names per device - if (!sscanf(name_node->name, name, &i)) // string parse each time - continue; - if (i < 0 || i >= max_netdevices) - continue; - snprintf(buf, IFNAMSIZ, name, i); - if (!strncmp(buf, name_node->name, IFNAMSIZ)) - __set_bit(i, inuse); - } - /* same sscanf/snprintf/strncmp on d->name */ - } - i = find_first_zero_bit(inuse, max_netdevices); - /* ... */ -} -``` - -## Complexity - -| Variable | Meaning | -|----------|---------| -| D | Number of net devices in the namespace | -| A | Number of alternative names per device | - -Total work per call: **O(D × A × sscanf_cost)**. - -`sscanf` with a format containing `%d` is not O(1); it involves format string parsing. -Container-heavy hosts (Kubernetes nodes) routinely carry D=500+ veth/bridge/vlan -devices, each with 1-3 alt names from `ip link property add`. - -## When Triggered - -- `ip link add vethN type veth` in a pod namespace — creates two interfaces, - both call `dev_alloc_name()` with format `"veth%d"`. -- A node creating 100 pods triggers 200 calls; if D=400 devices already exist - with A=2 alt names, each call walks 800 entries. -- Batch pod launches show O(D²) total work as D grows. - -## Root Cause - -The bitmap approach is correct for the final `find_first_zero_bit`, but the bitmap -is populated by rescanning all devices + alt names on every call. The primary -device name already uses the hash (`dev_name_hash`), but alt names bypass the hash -and fall into the linear `netdev_for_each_altname` walk. - -## Fix - -Maintain a sorted or hash-keyed index of all allocated numeric suffixes per name -prefix. On device/altname registration, insert the suffix into the prefix's free -map; on deregistration, remove it. `__dev_alloc_name` then does one map lookup -to find the first free slot in O(log D) or O(1) amortized. - -Simpler interim fix: skip the `netdev_for_each_altname` inner loop when counting -in-use slots for the primary name format, since alt names use different formats -(verified by `netdev_name_node_alt_create` — alt names are explicit strings, not -`%d` patterns). Add a guard: - -```c -netdev_for_each_altname(d, name_node) { - /* Alt names created via 'ip link property add' are never %d patterns; - * skip sscanf unless the alt name could plausibly match. */ - if (!memchr(name_node->name, '0' + (i % 10), IFNAMSIZ)) - continue; /* fast reject — not numeric */ - /* ... existing sscanf logic ... */ -} -``` - -Full fix: maintain per-prefix bitmaps in a global xarray keyed by name prefix hash. - -## Impact - -- O(D×A) work per interface creation; D and A both grow with container density. -- On a Kubernetes node with 500 pods: ~1000 devices × 2 alt names = 2000 sscanf - calls per new pod creation, versus O(1) with an index. -- Multiplied by pod churn rate (100/min), this is 200,000 sscanf calls/min. - -## Patch - -See `defects/linux/patch/linux-0003-dev-alloc-name-nested-altname.patch` diff --git a/docs/tickets/linux-0003-neigh-parms-ifindex-linear-scan.md b/docs/tickets/linux-0003-neigh-parms-ifindex-linear-scan.md deleted file mode 100644 index 983730e0b..000000000 --- a/docs/tickets/linux-0003-neigh-parms-ifindex-linear-scan.md +++ /dev/null @@ -1,102 +0,0 @@ -# linux-0003: lookup_neigh_parms — O(P) linear scan by ifindex on every netlink neigh table op - -**File:** `net/core/neighbour.c` -**Function:** `lookup_neigh_parms()` (line ~1752) -**Severity:** MEDIUM — triggered on every `ip neigh` operation and ARP/NDP table config -**CWE:** CWE-407 (Inefficient Algorithmic Complexity) - -## Code - -```c -static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl, - struct net *net, - int ifindex) -{ - struct neigh_parms *p; - - list_for_each_entry(p, &tbl->parms_list, list) { // O(P) linear scan - if ((p->dev && p->dev->ifindex == ifindex && - net_eq(neigh_parms_net(p), net)) || - (!p->dev && !ifindex && net_eq(net, &init_net))) - return p; - } - return NULL; -} -``` - -`tbl->parms_list` holds one `neigh_parms` per network device that has joined the -neighbour table. In environments with many network devices (bridges, VLANs, VxLAN -tunnels, bond members), this list grows to O(D) entries. - -## Complexity - -| Variable | Meaning | -|----------|---------| -| P | Length of tbl->parms_list — one entry per netdev registered with this neigh_table | - -`lookup_neigh_parms` is called from `neigh_table_set_key()` (netlink path) whenever -`ip neigh change`, `ip neigh add`, or `ip ntable change` is issued. On a host -with D=300 network interfaces (common in VxLAN fabrics), each such command walks -300 parms entries. - -## When Triggered - -``` -ip ntable change name arp dev eth0 # calls lookup_neigh_parms O(P) -ip neigh change 192.168.1.1 dev eth0 ... # calls neigh_lookup + parms lookup -``` - -Automation scripts that reconfigure neighbour parameters across many interfaces -(e.g., setting `base_reachable_time` for all VTEP devices) issue O(D) netlink -commands, each doing an O(D) scan → O(D²) total. - -## Root Cause - -`parms_list` is a flat linked list ordered by insertion. Lookup by `ifindex` is -O(P) because there is no secondary index. - -The natural key for `neigh_parms` is `(net, ifindex)`. An xarray keyed by ifindex -within each `net` provides O(1) lookup with no extra memory per entry. - -## Fix - -Replace the `parms_list` linear search with an xarray stored in `struct neigh_table`: - -```c -/* In struct neigh_table (include/net/neighbour.h): */ -struct xarray parms_xa; /* keyed by ifindex, value = neigh_parms * */ -struct list_head parms_list; /* keep for iteration (GC, sysctl dumps) */ -``` - -```c -/* neigh_parms_alloc: */ -xa_store(&tbl->parms_xa, p->dev ? p->dev->ifindex : 0, p, GFP_KERNEL); - -/* lookup_neigh_parms replacement: */ -static inline struct neigh_parms *lookup_neigh_parms(struct neigh_table *tbl, - struct net *net, - int ifindex) -{ - struct neigh_parms *p = xa_load(&tbl->parms_xa, ifindex); - if (p && net_eq(neigh_parms_net(p), net)) - return p; - return NULL; -} - -/* neigh_parms_release: */ -xa_erase(&tbl->parms_xa, p->dev ? p->dev->ifindex : 0); -``` - -The `parms_list` is retained for the GC timer path (`neigh_periodic_work`) which -iterates all parms to call `neigh_set_reach_time`. - -## Impact - -- O(D) per netlink command; O(D²) for configuration scripts covering all devices. -- On a VxLAN gateway with 500 VTEPs: 500 parms entries × 500 commands = 250,000 - list-node comparisons per configuration pass. -- With xarray: 500 commands × O(1) = 500 xa_load calls. - -## Patch - -See `defects/linux/patch/linux-0004-neigh-parms-xarray-lookup.patch`