Skip to content

Commit 7f2c4e4

Browse files
docs: drop circular severity-driver & unfounded silent-reservoir claims
Two corrections requested by review: 1. "Reachability drives severity" is circular — reachability is part of the bounty severity *definition* (single packet/tx), and severity_estimated is itself derived from root_cause/attack_path, so any root-cause->severity "lift" is doubly circular. Removed fig7 (lift + reservoir panels) and all lift numbers; §7 now states plainly that reachability is definitional, not a finding, and that we draw no root-cause->severity conclusions from estimated tiers. 2. The "silent reservoir" (N silently-patched fixes would rate High) is unfounded: we cannot tell whether those fixes were never reported or were reported via the bounty and then quietly patched. Removed entirely. Replaced the priority map's estimated-severity axis with an honest one: fixes per subsystem (observed volume) coloured by impact class from the bounty *definition* (consensus/value = critical-if-exploitable; availability = DoS). security_report §2/§4 reworked around observed volume + definitional impact mapping; softened "silently" to "no CVE/advisory" throughout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b0c1c1b commit 7f2c4e4

6 files changed

Lines changed: 132 additions & 187 deletions

File tree

docs/analysis.md

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ security fixes across the eleven production Ethereum clients.*
66

77
> **Key takeaways.** For anyone auditing Ethereum clients (or other blockchain /
88
> consensus systems), the data says: (1) the **historical fix record — not the
9-
> CVE list — is the real map**, since ~94% of fixes ship silently; (2) the threat
10-
> profile is **availability- and consensus-centric** (untrusted network input
11-
> crashing or diverging a node), not the memory-corruption profile of generic
12-
> C/C++ datasets; (3) severe bugs concentrate in a few regions — **crypto, the
13-
> EVM, and the consensus state-transition** for chain-split/value bugs, **p2p /
14-
> sync / RPC** for DoS — and severity tracks **reachability × blast radius** (led
15-
> by integer-overflow, consensus-divergence, resource-exhaustion); (4) fixes are
16-
> **localized** (43% single-file); and (5) because **one spec is implemented
17-
> eleven ways in six languages**, a fix in one client is a variant lead for the
18-
> other ten. The actionable version is the audit field guide
9+
> CVE list — is the more complete map**, since ~94% of fixes carry no CVE or
10+
> advisory; (2) the threat profile is **availability- and consensus-centric**
11+
> (untrusted network input crashing or diverging a node), not the memory-
12+
> corruption profile of generic C/C++ datasets; (3) by the bounty's impact
13+
> definition, **consensus, EVM and crypto code is where an exploitable bug is
14+
> *critical*** (chain split / invalid value) while **p2p / sync / RPC is where
15+
> *DoS* lives**, so scope an audit accordingly; (4) fixes are **localized** (43%
16+
> single-file); and (5) because **one spec is implemented eleven ways in six
17+
> languages**, a fix in one client is a variant lead for the other ten. The
18+
> actionable version is the audit field guide
1919
> ([`security_report.md`](./security_report.md)); below, each finding is
2020
> cross-checked against the vulnerability-dataset literature (CVEfixes, BigVul,
2121
> Devign, CrossVul, DiverseVul, PrimeVul, Croft et al.).
@@ -119,51 +119,28 @@ across implementations. It is the diversity dimension DiverseVul and CrossVul
119119
argue reduces overfitting, obtained here **within a single well-specified
120120
domain**.
121121

122-
## 7. What raises severity
123-
124-
Severity here is the **Ethereum Foundation bug-bounty** grade (network-scale
125-
impact × single-packet/tx reachability), not CVSS. Only **6.4%** of rows were
126-
graded by the bounty, so we estimate the rest with an LLM decomposition
127-
(`impact_type` / `reachability` / `blast_radius`) calibrated against the graded
128-
rows (~60% exact / ~80% within ±1 tier on real severe bugs; method:
129-
[`severity_labeling.md`](./severity_labeling.md)). Combining graded + estimated
130-
tiers, **675 rows (30%) now carry a bounty tier** (176 High/Critical), enough for
131-
a robust reading.
132-
133-
![Figure 7](figures/fig7_severity_drivers.png)
134-
135-
**(a) Reachability × blast-radius is the severity driver** (Fig, left; n=176).
136-
Three root causes are *over-represented* in High/Critical: **integer_overflow
137-
(lift ×1.71)**, **consensus_divergence (×1.60)**, and **resource_exhaustion / DoS
138-
(×1.26)** — precisely the classes that map to the bounty's impact categories
139-
(invalid-ETH / chain split / network takedown). Conversely **`race_condition` has
140-
lift ≈ 0.29** and `unhandled_error/nil` ≈ 0.67: common bugs, but locally-triggered
141-
and low-blast-radius, so out of the "single-packet/tx, network-scale" severity
142-
model by definition. Severity tracks *what the bounty pays for*, not code-bug
143-
class alone.
144-
145-
**(b) The silent reservoir — most severe fixes were never graded** (Fig, right).
146-
Of the **1,552 silently-patched client fixes, ~34% (532) carry a bounty-relevant
147-
tier when assessed****110 High, 242 Medium, 180 Low** — and the estimated-High
148-
ones are dominated by `liveness_dos` (89) and `chain_split` (21). Only 60 fixes
149-
in the whole corpus were actually bounty-graded, so the public severity record
150-
**understates the severe population by roughly an order of magnitude**; the
151-
`severity_estimated` / `severity_source` columns expose the would-be-rated slice
152-
explicitly (never overwriting the 60 ground-truth grades).
153-
154-
**Reporting bias, not a severity map.** Among the *graded* rows, Geth dominates —
155-
not because Geth is buggier, but because it publishes GitHub Security Advisories
156-
while most clients patch silently. The graded slice measures **disclosure policy,
157-
not security posture**. And fix size does **not** separate severity (median ~51
158-
LOC high vs 45 overall) — you cannot spot a critical bug by diff size.
159-
160-
*Takeaways for a researcher.* (i) Prioritize by **attacker-reachability ×
161-
subsystem** (p2p, rpc, crypto, consensus state-transition) rather than by whether
162-
a CVE exists. (ii) The **unrated `consensus_divergence` / `resource_exhaustion`
163-
rows are a hunting ground** for under-triaged severe bugs — the corpus surfaces
164-
exactly the silent, high-impact fixes that CVE-anchored datasets miss. (iii)
165-
Because one spec is implemented eleven ways, a severe fix in one client is a lead
166-
to look for its **silent analogue in the others** (§6).
122+
## 7. Severity — what the bounty counts, and what we can honestly say
123+
124+
Severity here is the **Ethereum Foundation bug-bounty** grade: an impact reachable
125+
by a single packet / on-chain tx that splits the chain, takes the network down,
126+
corrupts value, or slashes validators (not CVSS). Two honest caveats bound what
127+
this dataset supports:
128+
129+
- **Remote reachability is part of the *definition*, not a finding.** Because a
130+
bug is only graded when it is remotely triggerable, "reachable bugs are the
131+
severe ones" is true *by construction* — not an empirical result. What the data
132+
adds is *where the impactful code is*: consensus / EVM / crypto for
133+
chain-split/value bugs, p2p / sync / RPC for DoS (the audit priority map,
134+
[`security_report.md`](./security_report.md) §2).
135+
- **Only 6.4% of rows were bounty-graded.** The rest are LLM-*estimated*
136+
([`severity_labeling.md`](./severity_labeling.md)), and that estimate is derived
137+
partly from `root_cause` / `attack_path` — so we deliberately draw **no**
138+
"root-cause X raises severity" conclusion from it, which would be circular.
139+
Treat `severity_estimated` as a triage prior and filter to
140+
`severity_source == bounty-graded` for ground truth. Note also that a graded row
141+
reflects **disclosure policy** (which clients publish advisories), not relative
142+
security posture, and fix size does **not** separate severity (median ~51 LOC
143+
high vs 45 overall) — you cannot spot a critical bug by diff size.
167144

168145
## 8. Data quality and coverage
169146

-86.9 KB
Binary file not shown.
491 Bytes
Loading

docs/figures/fig9_priority_map.png

-16.6 KB
Loading

docs/security_report.md

Lines changed: 73 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,82 +14,85 @@ languages, both layers). Numbers and examples are drawn from
1414
1515
## 1. What matters — the impact model that sets priorities
1616

17-
Prioritize by **blast radius × remote reachability**, per the
17+
Rank findings by the **impact they could realize**, per the
1818
[Ethereum Foundation bug bounty](https://ethereum.org/en/bug-bounty/): a finding
19-
is severe if a **single network packet or on-chain transaction** can **split the
20-
chain**, **take the network down**, **create/steal ETH**, or **slash validators**.
21-
Everything below is ranked against that model — not CVSS, and not code-bug class
22-
in isolation. (Note: clients patch ~94% of issues *silently*, so **the historical
23-
fix record — not the CVE list — is the real map**; this guide is that map.)
24-
25-
## 2. Where to look — the audit priority map
26-
27-
![Figure 1 — audit priority map](figures/fig9_priority_map.png)
28-
29-
Plotting every subsystem by **audit volume** (how many bugs it has produced) and
30-
**severity density** (share rated High/Critical) yields three actionable groups:
31-
32-
**① Deep-audit first — low volume, high severity density.** Few fixes, but a bug
33-
here is disproportionately *critical* because it sits on consensus-critical or
34-
cryptographic paths:
35-
36-
| Subsystem | fixes | %High/Crit | why it's severe |
37-
|---|---:|---:|---|
38-
| `crypto` | 40 | **15%** | signature/curve/hash errors → forgery, DoS, or divergence |
39-
| `evm` / `opcodes` / `precompiles` | ~80 | **12%** | any semantic disagreement = **chain split** |
40-
41-
**② Highest priority — high volume *and* high severity.** The consensus and
42-
network core; audit breadth *and* depth here:
43-
44-
| Subsystem | fixes | %High/Crit | dominant cause → entry |
45-
|---|---:|---:|---|
46-
| `p2p` | 82 | **17%** | resource_exhaustion ← malicious p2p message |
47-
| `fork-choice` | 93 | **13%** | missing validation ← crafted state |
48-
| `sync` | 140 | **11%** | resource_exhaustion ← malformed input |
49-
| `beacon-chain:block-processing` | 84 | **10%** | **consensus_divergence** ← malformed input |
50-
| `beacon-chain:attestation` | 106 | **9%** | missing validation ← malicious attestation |
51-
52-
**③ Highest volume, mostly availability.** Most churn and most bugs, but they
53-
skew toward liveness/DoS rather than consensus — audit for resource limits and
54-
input validation:
55-
56-
| Subsystem | fixes | %High/Crit | dominant cause |
57-
|---|---:|---:|---|
58-
| `state-trie` | 210 | 7% | improper_state_update |
59-
| `p2p-interface` | 181 | 7% | missing_input_validation |
60-
| `rpc` | 147 | 5% | resource_exhaustion |
61-
62-
**One-line takeaway:** *audit `crypto`, the `EVM`, and the consensus state-
63-
transition for **critical** (chain-split/value) bugs; audit `p2p`, `sync`, and
64-
`RPC` for **availability** (DoS) bugs.*
19+
is Critical/High if a **single network packet or on-chain transaction** can
20+
**split the chain**, **take the network down**, **create/steal ETH**, or **slash
21+
validators**. This is the lens for everything below — not CVSS, and not code-bug
22+
class in isolation. (Most fixes carry **no CVE or public advisory**, so the
23+
historical **fix record — not the CVE list — is the more complete map** of where
24+
clients break; this guide is that map.)
25+
26+
## 2. Where to look — by volume and impact class
27+
28+
![Figure 1 — where fixes concentrate, by impact class](figures/fig9_priority_map.png)
29+
30+
Two *observed* facts scope an audit: **where fixes concentrate** (volume — where
31+
bugs historically occur) and **which impact class each subsystem maps to** by the
32+
bounty definition (a consensus- or value-affecting bug is Critical/High *if
33+
exploitable*; an availability bug is a DoS).
34+
35+
**Highest volume — most bugs land here.** `state-trie` (210), `p2p-interface`
36+
(181), `rpc` (147), `sync` (140), `beacon-chain:attestation` (106), `transactions`
37+
(99), `fork-choice` (93). Audit breadth first where the churn is.
38+
39+
**Consensus- and value-critical — where an exploitable bug is *critical* by
40+
definition** (semantic disagreement → chain split; bad arithmetic → invalid
41+
value): the **EVM / opcodes / precompiles**, **gas & balance arithmetic**
42+
(`transactions`), **fork-choice**, the **beacon-chain state-transition**
43+
(`block-processing`, `attestation`), **state-trie**, and **crypto / KZG**. Note
44+
`crypto` and `evm` are **low-volume but consensus-critical** — few historical
45+
fixes, but a bug there is critical by definition, so deep-audit them.
46+
47+
**Availability — audit for DoS:** `p2p`, `p2p-interface`, `sync`, `rpc`, `txpool`,
48+
`database` — dominated by resource-exhaustion and missing input validation; audit
49+
for resource bounds on peer-controlled work.
50+
51+
Per-subsystem **dominant cause → entry point** (observed labels):
52+
53+
| Subsystem | fixes | dominant cause → entry |
54+
|---|---:|---|
55+
| `state-trie` | 210 | improper_state_update ← malformed input |
56+
| `p2p-interface` | 181 | missing_input_validation ← malformed input |
57+
| `rpc` | 147 | resource_exhaustion ← malformed input |
58+
| `sync` | 140 | resource_exhaustion ← malformed input |
59+
| `beacon-chain:attestation` | 106 | missing_input_validation ← malicious attestation |
60+
| `fork-choice` | 93 | missing_input_validation ← crafted state |
61+
| `beacon-chain:block-processing` | 84 | consensus_divergence ← malformed input |
62+
| `p2p` | 82 | resource_exhaustion ← malicious p2p message |
63+
| `evm` | 40 | integer_overflow_underflow ← crafted state |
64+
| `crypto` | 40 | missing_input_validation ← malformed input |
6565

6666
## 3. The attack surface — where untrusted input enters
6767

6868
![Figure 2 — attack surface](figures/fig8_attack_surface.png)
6969

70-
Severity requires a remotely-reachable trigger, so your taint sources are the
71-
places a node ingests adversary-controlled data. In order of exposure: **p2p /
72-
gossip** (messages, attestations, blocks, peers), the **untrusted-parsing** layer
73-
(RLP / SSZ / JSON decoders that run *before* validation), **on-chain transactions
74-
/ the EVM**, and **crafted chain state** (snap-sync, historical data). Start taint
75-
analysis here; the `internal_only` slice is not bounty-reachable.
70+
A remotely-triggerable bug originates where a node ingests adversary-controlled
71+
data — your taint sources. In order of exposure: **p2p / gossip** (messages,
72+
attestations, blocks, peers), the **untrusted-parsing** layer (RLP / SSZ / JSON
73+
decoders that run *before* validation), **on-chain transactions / the EVM**, and
74+
**crafted chain state** (snap-sync, historical data). Start taint analysis here.
75+
76+
## 4. Rank a finding by the impact it could realize
7677

77-
## 4. What raises severity — rank findings by class
78+
The bounty's impact categories map to concrete bug classes and code regions — use
79+
this to weight a finding by *what it could actually cause*, not by whether it
80+
carries a CVE:
7881

79-
![Figure 3 — what raises severity](figures/fig7_severity_drivers.png)
82+
| Impact (bounty) | Realized by | Where it lives |
83+
|---|---|---|
84+
| **Chain split** | consensus_divergence | EVM opcodes/precompiles, gas accounting, fork-choice, beacon-chain state-transition |
85+
| **Invalid value / ETH** | integer_overflow, improper_state_update | gas & balance arithmetic, precompiles, state-trie |
86+
| **Network takedown (DoS)** | resource_exhaustion | p2p, rpc, sync, crypto |
87+
| **Validator slashing** | attestation / slashing validation | beacon-chain state-transition |
8088

81-
Three root causes are over-represented among High/Critical fixes and should raise
82-
your priority on a finding: **integer overflow/underflow (×1.71)** — gas/balance/
83-
slot arithmetic that can mint invalid value or diverge; **consensus divergence
84-
(×1.60)** — the chain-split class; and **resource exhaustion / DoS (×1.26)**.
85-
Conversely, `race_condition` (×0.29) and `unhandled_error/nil` (×0.67) are common
86-
but usually *locally* triggered and low-blast-radius — real bugs, lower priority
87-
under this model. (Fig 3b: most such severe bugs were **never publicly graded**
88-
another reason to trust the fix record over the CVE list.)
89+
*(Remote reachability is part of the severity **definition**, not a separate
90+
finding — a bug is only in scope if a single packet/tx can trigger it, which is
91+
exactly why §3's entry points matter.)*
8992

9093
## 5. The recurring vulnerability patterns — what to look for
9194

92-
![Figure 4 — root cause & trigger](figures/fig2_rootcause_attack.png)
95+
![Figure 3 — root cause & trigger](figures/fig2_rootcause_attack.png)
9396

9497
Six archetypes cover most of the corpus. Each is a hunting hypothesis: a
9598
mechanism, a trigger, and a **code smell** to grep for.
@@ -125,9 +128,9 @@ key. Subsystems fixed in *many* clients are the best variant-hunting grounds:
125128

126129
**The method (N-day / variant analysis):** take a fix in client *A*, find the
127130
analogous code in *B…K* by `label`, and check whether the same guard exists.
128-
Because fixes are usually silent, a fix that landed in one client is frequently
129-
**not yet** mirrored in the others — a repeatable path to fresh findings unique to
130-
a multi-implementation ecosystem. Its sharpest form is **spec-divergence testing**
131+
Because fixes usually ship without an advisory, a fix that landed in one client is
132+
frequently **not yet** mirrored in the others — a repeatable path to fresh findings
133+
unique to a multi-implementation ecosystem. Its sharpest form is **spec-divergence testing**
131134
(P5): where clients implement the same pyspec/EELS function (EVM opcodes,
132135
precompiles, SSZ, epoch processing), fuzz edge cases for behavioural disagreement
133136
— the direct route to chain-split severity.
@@ -165,8 +168,9 @@ precompiles, SSZ, epoch processing), fuzz edge cases for behavioural disagreemen
165168
bug shared by clients with >33% combined share is a network-level event; a bug
166169
in one client is a lead to check the others. Diversity both mitigates and
167170
*reveals* bugs (variant hunting).
168-
5. **Prioritize by reachability × blast radius, not by CVE existence.** The
169-
highest-impact bugs here were patched silently; a CVE-only view misses them.
171+
5. **Prioritize by the impact a bug could realize** (chain split / DoS / value /
172+
slashing), not by whether it carries a CVE. Most fixes here have no CVE, so a
173+
CVE-only view misses them.
170174

171175
## 9. Audit playbook (checklist)
172176

0 commit comments

Comments
 (0)