Clamp on-chain numUsedAccounts before the warmup account-index bound - #2496
Clamp on-chain numUsedAccounts before the warmup account-index bound#24960x-SquidSol wants to merge 1 commit into
Conversation
…bound warmup/[slab]/[accountIdx] gated `accountIdx >= engine.numUsedAccounts` with the raw on-chain field, and accountIdx has no upper bound (only >= 0). A sentinel/ garbage numUsedAccounts (u64::MAX / uninitialized slab) is huge, so the guard passes and parseAccount reads an out-of-range slot. Clamp the count with sanitizeAccountCount (garbage -> 0) before the check. v12-only (v17 returns 501). - warmup: sanitizeAccountCount(Number(engine.numUsedAccounts)) as the bound - add a regression test (sentinel/over-cap/negative -> 0; legit preserved; guard rejects a garbage-count index that previously passed) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@0x-SquidSol is attempting to deploy a commit to the Khubair Nasir's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
Next review available in: 33 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
dcccrypto
left a comment
There was a problem hiding this comment.
Correct, and it does close the reported hole — Number(u64::MAX) is ~1.8e19,
above the 4096 default cap, so sanitizeAccountCount returns 0 and every index
is rejected. Suite: 2922 passed / 0 failed.
But it's weaker than it needs to be, and the stronger form is two lines away
sanitizeAccountCount takes an optional per-slab cap:
export function sanitizeAccountCount(count: number, maxAccounts?: number): number {
if (count < 0) return 0;
const cap = maxAccounts != null && maxAccounts > 0 ? maxAccounts : MAX_SLAB_ACCOUNTS; // 4096
if (count > cap) return 0;
return count;
}This PR omits the second argument, so it falls back to the hardcoded 4096. The
route already parses the real value two lines above the guard:
const engine = parseEngine(data);
const riskParams = parseParams(data); // <-- has maxAccounts, currently unused hereand the codebase already uses the stronger form elsewhere —
MarketStatsCard.tsx:244:
sanitizeAccountCount(engine.numUsedAccounts, params ? Number(params.maxAccounts) : undefined)The gap this leaves: for a slab whose real maxAccounts is below 4096 — say 512
— a garbage numUsedAccounts anywhere in 512…4096 still passes the clamp, and
parseAccount still reads a slot beyond the slab's actual account array. That is
the same out-of-range read the PR is closing, just at a less extreme sentinel
value. u64::MAX is the easy case; a corrupted-but-plausible count is the one
worth bounding.
Suggested:
const numUsed = sanitizeAccountCount(
Number(engine.numUsedAccounts),
Number(riskParams.maxAccounts),
);That also makes riskParams load-bearing rather than parsed-and-ignored on this
path.
Test doesn't bind the fix
I replaced the clamp with a bare Number(engine.numUsedAccounts) — the bug fully
restored — and warmup-account-index-clamp.test.ts stayed 3/3 green.
Binding it is cheap here: mock @percolatorct/sdk's parseEngine to return
numUsedAccounts: 2n ** 64n - 1n and parseParams to return a small
maxAccounts, drive GET, and assert 404 — plus a control with a sane count
where the same index resolves. That would also cover the maxAccounts point
above, since the sane-cap case is exactly what the current default misses.
What
Clamp
engine.numUsedAccountswithsanitizeAccountCountbefore using it as theaccountIdxbound inwarmup/[slab]/[accountIdx].Why
The guard
accountIdx >= engine.numUsedAccountstrusted the raw on-chain field, andaccountIdxhas no upper bound (only>= 0). A sentinel/garbagenumUsedAccounts(u64::MAX / uninitialized slab) is huge, so the guard passed and
parseAccountreadan out-of-range slot.
sanitizeAccountCountclamps garbage (> 4096/ negative /sentinel) to
0, so the bound correctly rejects.Changes
const numUsed = sanitizeAccountCount(Number(engine.numUsedAccounts))as the bound.garbage-count index that previously passed.
Testing
npx tsc --noEmit— clean.Notes