anticsdecoded|Portfolio
SEC|2026
/audits / NEO.cs
cat /audits/NEO.cs
NODE: PRIVATE

L1 (Neo N3 / NeoVM)

// engagement
protocolNeo
categoryL1 (Neo N3 / NeoVM)
kindprivate · Private
date2026-10
duration2 weeks
languageC#
chainsNeo N3
nSLOC2,110
// findingsNODE: 1 FND
critical0
high1
medium0
low0
info0
gas0
// summary

Coordinated disclosure (GitHub Security Advisory draft) for neo-project/neo, the Neo N3 C#/.NET blockchain node, covering the flat-priced `System.Iterator.Value` interop and its interaction with `FindOptions.DeserializeValues` storage iterators. Validated three ways on a Release build: a unit reproducer against the real iterator, on-chain execution through the real ApplicationEngine with gas metering, and a running local chain regtest.

// methodology
  • Manual review
  • PoC development
  • Local chain regtesting
// scope
pathnsloc
src/Neo/SmartContract/ApplicationEngine.Iterator.cs120
src/Neo/SmartContract/Iterators/StorageIterator.cs90
src/Neo/SmartContract/BinarySerializer.cs480
src/Neo/SmartContract/ApplicationEngine.Storage.cs320
src/Neo/SmartContract/ApplicationEngine.cs1,100
total2,110
// findings
H-01highacknowledged

Under-priced `System.Iterator.Value` re-deserializes attacker payloads on every call, exhausting node CPU and stalling block production

`System.Iterator.Value` is registered at a flat 16 datoshi while a `StorageIterator` created with `FindOptions.DeserializeValues` re-runs a full `BinarySerializer.Deserialize` of the attacker-controlled value (up to 65,535 bytes, deserializing to ~2,048 stack items) on every call — `Value()` does not advance the iterator and caches nothing, and under `PickField0`/`PickField1` the transient array is discarded so the VM reference counter never accounts for it. Measured through the real ApplicationEngine: 3,000 calls charged 0.0365 GAS while consuming 0.56s of node CPU, about 15 node-CPU-seconds per GAS paid, with a ~167× CPU spread between payloads for the identical flat fee; on a running local chain a single ~0.024 GAS transaction made a block persist in 778ms against 3.5ms for an empty block, so roughly 0.46 GAS per block sustains a full 15-second block time of CPU on every validating node, and no `MaxBlockSystemFee` cap bounds per-block system fees. Permissionless — a one-time ~10 GAS contract deployment plus ~8 GAS of stored payload, reusable across every later block; scored High per Neo's bounty taxonomy ("all network fail"), CVSS v3.1 7.7 (AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H).