Skip to content

Commit dc69fd7

Browse files
docs: add Round 3 security audit report (2026-03-05)
11 findings (2 HIGH, 4 MEDIUM, 3 LOW, 2 INFO) including TOCTOU re-exploitation in async_finality and CommitGuard alias safety gap. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent f7dc8fd commit dc69fd7

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Security Audit Report — grevm (Round 3)
2+
3+
**Date:** 2026-03-05
4+
**Scope:** All Rust source in `src/` (~3000 LOC)
5+
**Language:** Rust | **Framework:** revm v29.0.1-gravity fork, Block-STM
6+
**Repository:** `Galxe/grevm` (branch: `security-audit-fixes`)
7+
**Prior Audits:** [Round 1 — 2026-02-26](./2026-02-26-security-audit-report.md) (19 findings), [Round 2 — 2026-03-02](./2026-03-02-security-audit-round2-report.md) (10 findings)
8+
9+
---
10+
11+
## Summary
12+
13+
| Severity | Count |
14+
|----------|-------|
15+
| HIGH | 2 |
16+
| MEDIUM | 4 |
17+
| LOW | 3 |
18+
| INFO | 2 |
19+
| **Total** | **11** |
20+
21+
> [!NOTE]
22+
> This Round 3 audit was performed against the codebase **after** Round 1 and Round 2 fixes were applied.
23+
> It includes verification of prior fix completeness and a cross-module review with gravity-reth, gravity-sdk, and gravity-aptos.
24+
25+
---
26+
27+
## HIGH Severity (2)
28+
29+
### GREVM-R3-001: TOCTOU in `async_finality` — Lock-Drop-Reacquire Still Exploitable
30+
31+
**File:** `grevm/src/scheduler.rs:355-374`
32+
33+
The `async_finality` loop acquires the lock at line 358 to check `status == Unconfirmed`, then **drops the lock** (temporary `MutexGuard`). It performs timestamp checks (lines 361-371) without holding any lock, then **re-acquires** at line 372 and unconditionally sets `Finality`.
34+
35+
The Round 1 reviewer (GREVM-004) stated logical timestamps prevent exploitation. However, the `lower_ts` mechanism has a gap: `reset_validation_idx(txid+1)` sets `lower_ts[txid+1]`, NOT `lower_ts[txid]`. So for `finality_idx = txid`, the timestamp guard does not fire for conflicts detected AT that txid.
36+
37+
**Race sequence:**
38+
1. Finality thread: reads `Unconfirmed` at line 358, drops lock
39+
2. Worker: validates tx, finds conflict, sets `Conflict`, calls `reset_validation_idx(txid+1)` — updates `lower_ts[txid+1]`
40+
3. Finality thread: checks `lower_ts[txid]` (unchanged!), passes, re-acquires lock, overwrites to `Finality`
41+
42+
**Impact:** Transaction committed as final with stale execution results — consensus-breaking state corruption.
43+
44+
**Fix:** Re-check status after re-acquiring the lock:
45+
```rust
46+
let mut tx_state = self.tx_states[finality_idx].lock();
47+
if tx_state.status != TransactionStatus::Unconfirmed {
48+
break;
49+
}
50+
tx_state.status = TransactionStatus::Finality;
51+
```
52+
53+
**Prior:** GREVM-004 (Round 1). **Still exploitable.**
54+
55+
---
56+
57+
### GREVM-R3-002: CommitGuard Does Not Prevent Simultaneous Mutable Aliases
58+
59+
**File:** `grevm/src/async_commit.rs:13-27`, `scheduler.rs:456,580`
60+
61+
`CommitGuard::new()` takes `&UnsafeCell<ParallelState<DB>>` (shared ref). Two `CommitGuard` instances can coexist — one at line 456 (`parallel_execute`) and one at line 580 (`fallback_sequential`). While the current code ensures temporal separation (scope joins before fallback), the type system provides no enforcement.
62+
63+
Additionally, `state_ref()` (line 68-71) creates `&ParallelState` while `state_mut()` creates `&mut ParallelState` from the same `UnsafeCell`, within the same `commit()` method — technically UB under Rust's aliasing rules even though the references don't overlap temporally within the method.
64+
65+
**Impact:** The CommitGuard pattern fails to provide the compile-time safety GREVM-R2-001 was designed to deliver. Future refactors could silently introduce UB.
66+
67+
**Prior:** GREVM-R2-001 (Round 2). **Incomplete fix.**
68+
69+
---
70+
71+
## MEDIUM Severity (4)
72+
73+
### GREVM-R3-003: `nonce + 1` Overflow in Nonce Assertion
74+
75+
**File:** `grevm/src/async_commit.rs:105`
76+
77+
`assert_eq!(change.info.nonce, expect + 1)` — if `expect` is `u64::MAX`, `expect + 1` wraps to 0 in release mode (overflow-checks disabled). The assertion would compare against 0 instead of the intended value.
78+
79+
### GREVM-R3-004: Write Set Not Cleaned on EVM Error Re-execution
80+
81+
**File:** `grevm/src/scheduler.rs:719-739`
82+
83+
When a tx encounters an EVM error, the old write_set from a previous successful incarnation is preserved and stored back. The stale `mv_memory` entries remain. During re-execution, `write_new_locations` flag may be incorrectly computed, potentially skipping necessary revalidation of dependent transactions.
84+
85+
### GREVM-R3-005: `key_tx` Dependency Race with Concurrent `commit`
86+
87+
**File:** `grevm/src/tx_dependency.rs:112-123`
88+
89+
Race between `key_tx(txid)` and `commit(txid-1)` can cause unnecessary double-scheduling. Benign in current code but creates latent performance degradation under pathological workloads.
90+
91+
### GREVM-R3-006: `update_mv_memory` Coinbase Skip Hides User ETH Transfers
92+
93+
**File:** `grevm/src/storage.rs:208-210`
94+
95+
All coinbase changes are skipped in `update_mv_memory`, including **user-initiated ETH transfers** to the coinbase address. If tx A sends ETH to coinbase and tx B reads the coinbase balance, the change is invisible in MVMemory. The `accurate_origin` flag partially mitigates this but has a gap: when `commit_idx == current_tx.txid` and a preceding executed-but-uncommitted tx sent ETH to coinbase.
96+
97+
**Impact:** Potential stale coinbase balance reads causing incorrect execution that validation may not catch.
98+
99+
---
100+
101+
## LOW Severity (3)
102+
103+
### GREVM-R3-007: ERC20 Transfer Uses Slot 0, TransferFrom Uses Slot 1 for Balances
104+
105+
**File:** `grevm/src/hint.rs:236-253 vs 255-280`
106+
107+
Inconsistent storage slot assumptions between Transfer (slot 0) and TransferFrom (slot 1) for balance mappings. Performance-only impact since validation catches all conflicts.
108+
109+
### GREVM-R3-008: `results.push(result)` Before Error Check
110+
111+
**File:** `grevm/src/async_commit.rs:136-139`
112+
113+
The execution result is pushed to `self.results` even when the nonce check fails. The results vector contains an entry for a transaction whose state was never committed.
114+
115+
**Prior:** GREVM-R2-002. **Partially fixed.**
116+
117+
### GREVM-R3-009: `lazy_reward` Applied After Error Not Guarded at Method Entry
118+
119+
**File:** `grevm/src/async_commit.rs:141-151`
120+
121+
The `commit()` method does not check `self.commit_result` at entry. If a caller fails to check between calls, state corruption could compound. Mitigated by caller's check in `async_commit()`.
122+
123+
---
124+
125+
## INFO (2)
126+
127+
### GREVM-R3-010: Block-STM Validation Invariant Confirmed ✓
128+
129+
The Block-STM validation loop correctly detects all read-write conflicts and triggers re-execution. The OCC-based approach ensures eventual consistency even when hints are incorrect.
130+
131+
### GREVM-R3-011: DAG-based Scheduling Correctly Parallelizes Independent Transactions ✓
132+
133+
The hint-based DAG construction and partition assignment produce correct dependency ordering. Mis-hints only affect performance, not correctness, due to the validation fallback.
134+
135+
---
136+
137+
## Fix Verification from Prior Rounds
138+
139+
| Prior Finding | Current Status |
140+
|---------------|----------------|
141+
| GREVM-004 (TOCTOU in async_finality) | **Still exploitable** — see GREVM-R3-001 |
142+
| GREVM-006 (CAS loop in next_validation_idx) | **Not implemented** — fix checklist incorrectly marks as done |
143+
| GREVM-R2-001 (CommitGuard) | **Incomplete** — see GREVM-R3-002 |
144+
| GREVM-R2-002 (Early return on nonce) | **Partially done** — results.push before error check |
145+
| GREVM-R2-003 (empty partition guard) | **Partially done** — parallel_cnt not capped |
146+
| All other fixes | Correctly applied |
147+
148+
---
149+
150+
## Cumulative Statistics (Rounds 1-3)
151+
152+
| Severity | R1 | R2 | R3 | Total |
153+
|----------|----|----|-----|-------|
154+
| CRITICAL | 1 | 0 | 0 | 1 |
155+
| HIGH | 5 | 2 | 2 | 9 |
156+
| MEDIUM | 5 | 3 | 4 | 12 |
157+
| LOW | 5 | 3 | 3 | 11 |
158+
| INFO | 3 | 2 | 2 | 7 |
159+
| **Total** | **19** | **10** | **11** | **40** |

0 commit comments

Comments
 (0)