mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
A precompile's input is the two-index slice Memory.GetPtr returns, and nothing on the path to Run copies it. len is what the caller declared and paid for; cap is the rest of EVM memory, which that same caller filled with MSTORE. So a parser reaching past its input does not panic and does not halt a validator -- it reads bytes the attacker chose and answers over them. Measured, not assumed: TestCalldataCarriesCallerChosenSpareCapacity drives the VM and reads back len=8 cap=256 with 0xA5 past the end. Cursor bounds every read against len, never against cap and never by letting a slice expression refuse, and caps what it returns so a field cannot be re-sliced forward. Verdict carries the bit (bool, error) was missing -- did a verifier run -- so "nothing checked this" stops borrowing the spelling of "this failed", and its zero value denies. Poisoned is the one definition of the adversarial fixture: append-built input has spare capacity too, but zeroed, so an over-read reads as a harmless run of zeros. p3q parses through the cursor. Its six hand-written bounds become zero; the class now rests on the single check in Cursor.Bytes, and deleting that check fails both suites -- including with fixtures degraded back to plain append, which is what the class used to depend on.
33 lines
1.4 KiB
Go
33 lines
1.4 KiB
Go
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package contract
|
|
|
|
// Poisoned returns b with spare capacity behind it, filled with a
|
|
// recognisable pattern. It is the one definition of what a precompile's input
|
|
// actually looks like, and every refusal test in this repository submits one.
|
|
//
|
|
// The EVM hands a precompile the two-index slice Memory.GetPtr returns
|
|
// (instructions.go opCall and its three siblings), and nothing on the path to
|
|
// Run copies it. So len is the size the caller declared and paid gas for,
|
|
// while cap is the rest of EVM memory — memory that same caller filled with
|
|
// MSTORE before making the call.
|
|
//
|
|
// A fixture built with append also carries spare capacity, but zeroed or
|
|
// stale. An over-read into it looks like a harmless run of zeros, and a test
|
|
// over such a fixture passes whether or not the bound it means to exercise is
|
|
// present. Chosen bytes are what make an over-read visible as an over-read,
|
|
// and what make the test's subject the parser rather than the allocator.
|
|
//
|
|
// It lives in non-test source because nine packages' tests need the same
|
|
// definition, and two definitions of the adversary is one too many. Nothing
|
|
// in a Run path calls it.
|
|
func Poisoned(b []byte, spare int) []byte {
|
|
full := make([]byte, len(b)+spare)
|
|
for i := range full {
|
|
full[i] = 0xA5
|
|
}
|
|
copy(full, b)
|
|
return full[:len(b)]
|
|
}
|