Skip to content

Negative-sign check runs before the zero-start-pointer guard: 0x2D at memory address 0 bypasses ZeroStringStartPointer #42

Closed

Description

@thedavidmeister

Unit

src/lib/parse/LibParseDecimal.sol:115-118 vs :30-32.

Intent oracle

ZeroStringStartPointer exists to reject start == 0 outright (the unchecked cursor >= start loop bound degenerates when start is 0).

Violated property

unsafeDecimalStringToSignedInt(0, end) calls LibParseChar.isMask(0, end, CMASK_NEGATIVE_SIGN) BEFORE any zero check. If the byte at memory address 0 (scratch space) happens to be 0x2D ('-'), isNeg is 1, the inner parse is called with start = 1, and the revert designed for zero start pointers never fires — the parse proceeds reading from address 1.

Verified repro

Trace: signed(0, end>0) → isMask reads byte(0, mload(0)) → scratch-dependent. The existing test testExternalTestZeroStringStartPointer (signed variant) passes only because scratch at address 0 is typically not 0x2D in that call context; nothing pins the guard for the 0x2D case.

Triage framing

Reachability requires a caller both passing start=0 and having 0x2D in scratch — out-of-contract usage twice over. Surfaced because the guard's purpose is exactly to make out-of-contract usage loud, and it is bypassable by memory state the caller does not control. Cheap fix: hoist a start == 0 check (or the sign check) so the guard is unconditional.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

adversarialFound by adversarial testingauditAudit finding

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions