igneum/tools/exec-attacks
igneum-josh 179317c122 Kill by exact command line or pid file, never by a name: tools/ci/kill-by-name-check.sh in the gate; the 36 pgrep/pkill literals in the tree fixed
The fleet's 22:09 UK incident (a Mac-side pkill -f <log file name> matched nothing, the roll-everything script lived on and wiped a held box) and the day's two pgrep self-matches are one class. The check flags pgrep -f / pkill -f with a plain literal (every one on a line), any pgrep/pkill on a file-name shape, and ps | grep with a literal; it allows the bracket form, -x, -F pidfile, kill $(cat pidfile), a variable and a full path; 11 banned and 16 allowed shapes in its self-test; 0.15 s over the tree. The 25 pkill -f sp1-gpu-server inside bash -c bodies (which matched the calling bash) are pkill -x; the other 11 literals take the bracket form; prover-socket-check accepts both. Row R in the record; the CLAUDE.md rule names the check and covers pkill and file names.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 23:10:31 +01:00
..
contracts exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
lib Kill by exact command line or pid file, never by a name: tools/ci/kill-by-name-check.sh in the gate; the 36 pgrep/pkill literals in the tree fixed 2026-10-06 23:10:31 +01:00
.gitignore exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
compile.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
net.sh Kill by exact command line or pid file, never by a name: tools/ci/kill-by-name-check.sh in the gate; the 36 pgrep/pkill literals in the tree fixed 2026-10-06 23:10:31 +01:00
README.md exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
run_all.sh exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
run_scenario6.sh exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario1_malformed.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario2_nonce.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario3_pgas.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario4_registry.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario5_rpcfuzz.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario6_reorg.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00

Execution-layer attacks (robustness and conformance)

Adversarial tests of the Igneum execution layer (docs/design/execution-layer.md, docs/spec/07-execution.md) against a throwaway 3-node igneumd simnet. Each scenario is a runnable command with a pass criterion taken from the design and a measured result. This is testing of our own private software.

Everything runs on ports 27600 and above under /tmp/igneum-exec-attacks. The live devnet (26610, 26611, 26640, 26641, 28640) and other agents' ports (up to 27599) are never touched.

Build

The node, the honest miner and the hostile injector are built in the worktree vendor/igneum-node-exec-attacks (branch exec-attacks):

cd vendor/igneum-node-exec-attacks
export PATH="$HOME/.rustup/toolchains/stable-aarch64-apple-darwin/bin:$PATH"
CARGO_TARGET_DIR=target nice -n 19 cargo build --release -j 4 -p kaspad -p igneum-miner --features igneum-pow

This produces igneumd, igneum-miner and igneum-inject under target/release.

igneum-inject is the hostile miner: it fetches a block template over gRPC, replaces the EVM body with an arbitrary set of raw EIP-2718 bytes (which the mempool would never hand out), recomputes hash_merkle_root and resubmits, so transactions the mempool rejects reach consensus body validation and the executor directly. Several blocks built off one template share a selected parent and land in parallel on the DAG.

Contracts

node compile.mjs compiles contracts/PgasBomb.sol (modexp/keccak loops, cheap in gas and heavy in pgas) and contracts/RegistryAbuse.sol (a Worker and a Factory for the developer-registry tests) with solc 0.8.37.

Network

./net.sh start [1|3]     # hub topology: 3 nodes, 1 or 3 honest stub miners
./net.sh start-split     # partition P1={node1}, P2={node2,node3}, no link until heal (igneum-inject addpeer)
./net.sh stop

Nodes run --simnet --enable-unsynced-mining --unsaferpc (PoW skipped). eth JSON-RPC on 27690/27691/27692, gRPC on 27610/27620/27630, p2p on 27611/27621/27631. Node 1's miner pays the test miner account; nodes 2 and 3 pay the test accounts B and C, so rewards are spendable by the harness whichever chain wins.

Scenarios (run in priority order 1, 2, 5, 3, 6, 4)

# Command What it does Criterion
1 node scenario1_malformed.mjs malformed and boundary txs over eth_sendRawTransaction and inside a hostile block (bad RLP, wrong chain id, oversized calldata, gas at/over the block limit, bad signature, nonce far ahead, nonce reuse, zero/max fee) state-free faults invalidate the block; state-dependent faults skip the tx with no receipt; no panic; RSS bounded
2 node scenario2_nonce.mjs one sender's nonces spread across parallel blocks in different orders, duplicates in several blocks, a conflicting same-nonce pair exactly one execution per nonce; deterministic; state roots identical on all nodes
5 node scenario5_rpcfuzz.mjs every eth_*/igneum_* with junk params, huge arrays, deep nesting; 50x eth_call flood from one client errors not crashes; honest latency under 200 ms
3 node scenario3_pgas.mjs modexp loops cheap in gas, heavy in pgas, with growing loop counts the per-block pgas budget B_p caps inclusion; no executed block exceeds B_p; execution time per block measured
6 ./run_scenario6.sh partition/heal reorgs of several depths with hostile miners while txs flow (uses start-split and igneum-inject addpeer) state root recomputed deterministically; displaced-tx receipts consistent on all nodes and canonical; no stuck mempool
4 node scenario4_registry.mjs register a payee for someone else's code; factory inheritance (CREATE, CREATE2, same-tx override, unregistered, EOA override); self-dealing (sender = payee = miner) design 4.5: base fees burned, no positive-expectation loop; records the max share a self-dealer recovers

run_all.sh runs every scenario in priority order (starting and stopping the right network for each) and prints a one-line pass/fail per scenario. Per-scenario detail lands in results/*.json.

Notes on DAG semantics observed here: a selected-chain reorg does not orphan merged blocks (design 1.2/1.3), so a "displaced" transaction re-executes exactly once in the segment that merges its block rather than losing its receipt; scenario 6 checks for a consistent, canonical outcome across nodes rather than Ethereum-style eviction.