anticsdecoded|Portfolio
SEC|2026
/audits / GNO.go
cat /audits/GNO.go
NODE: PRIVATE

Smart-contract platform (Gnolang VM)

// engagement
protocolGno
categorySmart-contract platform (Gnolang VM)
kindprivate · Private
date2026-10
duration2 weeks
languageGo
chainsGno
nSLOC1,520
// findingsNODE: 1 FND
critical1
high0
medium0
low0
info0
gas0
// summary

Private disclosure for gnolang/gno (gno.land), the Gnolang smart-contract platform, covering the gas metering of uverse native builtins — natives that fall outside the VM's calibration table and are charged a flat 2205-gas `OpCPUCallNativeBody`. Validated on a locally built single-validator chain with default consensus parameters: measured multi-minute freezes of block production and RPC from single cheap transactions, plus a zero-cost unauthenticated `vm/qeval` variant.

// methodology
  • Manual review
  • PoC development
  • Gas-metering analysis
// scope
pathnsloc
gnovm/pkg/gnolang/uverse.go560
gnovm/pkg/gnolang/native_gas.go300
tm2/pkg/bech32/bech32.go80
gno.land/pkg/sdk/vm/keeper.go420
gno.land/pkg/sdk/vm/params.go160
total1,520
// findings
C-01criticalacknowledged

Unmetered bech32 decode in the `address.IsValid` native lets one cheap transaction freeze block production for days

`address.IsValid` runs a full `bech32.DecodeNoLimit` over an arbitrarily long input — multiple linear passes at ~16 ns/byte — but uverse natives miss the calibration table and are charged the flat 2205-gas fallback (flagged in-tree as arbitrary and not properly benchmarked), and the call returns false instead of panicking so realm code can simply loop it: measured gas per call is 5474 regardless of string length, a ~56,000× underpricing of identical bech32 work against the calibrated `chain.packageAddress` native. On a locally built chain, `Bomb(8MB, 300)` at ~4.9M gas froze block production and the RPC endpoint for 252 seconds and then committed as a valid transaction; at the default 3B block-gas limit one transaction sustains about 5 days of freeze (64MB string: over a month), with no wall-clock preemption of DeliverTx. On permissionless defaults the halt needs no deployment at all (a MsgRun ephemeral package froze the chain for 102s), and the free, unauthenticated `vm/qeval` endpoint with its 3B query budget turns the same defect into zero-cost, no-identity RPC exhaustion; on mainnet gnoland-1 and onyx the consensus path is policy-gated, leaving the RPC vector (High there, Critical on default configurations).