FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: low
Likelihood: low

`uint32` truncation of the good-faith CORRUPTED claim deadline wraps near the 2106 ceiling, locking the named whitehat out and diverting the entire bounty to recovery

Author Revealed upon completion

Description

  • When the moderator flags a good-faith CORRUPTED outcome and names a whitehat, flagOutcome records corruptedClaimDeadline = _firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW (180 days). Per DESIGN.md §12 (lines 302-307) the full pool (principal + bonus) is reserved for the named whitehat via claimAttackerBounty for that entire 180-day window, after which anyone may sweepUnclaimedCorrupted the remainder to recoveryAddress.

  • The deadline is narrowed to uint32 by a truncating cast. CORRUPTED_CLAIM_WINDOW = 180 days = 15_552_000 and type(uint32).max = 4_294_967_296. When the first good-faith CORRUPTED flag occurs at block.timestamp >= 4_279_415_296 (~2105-08-11) — within 180 days of the uint32 timestamp ceiling — the sum exceeds type(uint32).max and the cast silently wraps modulo 2^32 to a small value near the Unix epoch. That past deadline simultaneously inverts both CORRUPTED-path gates: the whitehat's window is closed the instant it opens, and the permissionless sweep-to-recovery path is open from that same instant.

// src/ConfidencePool.sol:372
if (willBeGoodFaithCorrupted) {
if (_firstGoodFaithCorruptedAt == 0) {
// forge-lint: disable-next-line(unsafe-typecast)
_firstGoodFaithCorruptedAt = uint32(block.timestamp);
}
// Reuses the original window on re-entry — which may already be in the past, leaving
// nothing to claim. Intended: the deadline must never be extendable.
// Sum stays in uint32 unless flagged within 180 days of the 2106 ceiling; out of scope.
// forge-lint: disable-next-line(unsafe-typecast)
@> corruptedClaimDeadline = uint32(_firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW);
} else {
corruptedClaimDeadline = 0;
}

Recommended Mitigation

// Reuses the original window on re-entry — which may already be in the past, leaving
// nothing to claim. Intended: the deadline must never be extendable.
- // Sum stays in uint32 unless flagged within 180 days of the 2106 ceiling; out of scope.
- // forge-lint: disable-next-line(unsafe-typecast)
- corruptedClaimDeadline = uint32(_firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW);
+ // Compute in uint256 and saturate at type(uint32).max so the boundary case keeps the
+ // whitehat's window OPEN rather than silently wrapping it into the past.
+ uint256 deadline = uint256(_firstGoodFaithCorruptedAt) + CORRUPTED_CLAIM_WINDOW;
+ corruptedClaimDeadline = deadline > type(uint32).max ? type(uint32).max : uint32(deadline);

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!