Repository navigation
fix(relay): share one overflow-free created_at skew check - #9231
Draft
ThePumpingLemma wants to merge 1 commit into
Draft
ThePumpingLemma wants to merge 1 commit into
ThePumpingLemma wants to merge 1 commit into
Conversation
Problem: five relay freshness gates (general ingest drift, NIP-43 leave, moderation commands, relay admin commands, NIP-IA archive requests) each re-implemented `(created_at as i64 - now).abs() > max`. For `created_at = now + 2^63` the difference is exactly `i64::MIN`, whose `abs()` overflows: debug builds panic, release builds wrap to a negative number that is never `> max`, so an extreme future timestamp passes the gate. Neighbouring values overflow the subtraction itself (debug panic). The observer-frame gate used `unsigned_abs` but still did the i64 subtraction. Change: add `handlers::freshness` with `check_created_at_skew`, which compares the nostr `Timestamp` seconds against `now` with `u64::abs_diff`, one `unix_now_secs` now source, and one `MAX_COMMAND_SKEW_SECS` (120 s) for the command gates. Every relay copy is migrated; the per-site windows (900 s, 300 s, 120 s) and error texts are unchanged, with the reported delta widened to i128. The general ingest gate keeps its existing chrono `now`/`event_ts` locals because later code uses them. The pre-existing local Postgres test URL in identity_archive.rs gets the repo's usual sadscan fixture marker. Why safe: for every created_at that did not overflow, abs_diff gives the same distance as before, so in-window events are accepted and out-of-window events rejected exactly as today; only the overflowing inputs change from accept/panic to reject. Tests: table test of the helper (window edges, 0, u64::MAX, 2^63 and now + 2^63 +/- 1) and a seam test through identity_archive's `enforce_freshness`. Before the fix the seam test panicked with "attempt to negate with overflow" in debug and failed with the extreme event accepted in release. Signed-off-by: David Grochowski <dgrochowski@squareup.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Five relay
created_atfreshness gates (general ingest drift ±900 s, NIP-43 leave ±120 s, moderation commands ±120 s, relay admin commands ±120 s, NIP-IA archive requests ±120 s) each re-implemented(created_at as i64 - now).abs() > max. Withcreated_at = now + 2^63the difference is exactlyi64::MIN. Itsabs()overflows: debug builds panic, and release builds wrap to a negative value that is never> max, so the gate accepts an extreme future timestamp. Nearby values overflow the subtraction itself, which panics in debug. The observer-frame gate (±300 s) usedunsigned_absbut still did the i64 subtraction.Change
handlers::freshnessmodule:check_created_at_skew(Timestamp, now, max)compares inu64withabs_diff, so no input can overflow it.unix_now_secs()is the single source for the current time.MAX_COMMAND_SKEW_SECS(120 s) is now one constant for all four command gates.now/event_tslocals stay because later code uses them.abs_diffand is not touched.identity_archive.rsgets the repo's usualsadscan:disablefixture marker. The commit hook flags the file once it is modified.Accept/reject results only change for inputs that overflowed before. For every other
created_at,abs_diffgives the same distance as the old code.Testing
Failing before the fix (seam test through
identity_archive::enforce_freshness):After the fix:
cargo test -p buzz-relay --lib -- freshness moderation_commands relay_admin identity_archive ingest: 233 passed, 33 ignored (Postgres)cargo test --release -p buzz-relay --lib -- freshness: 7 passedcargo clippy -p buzz-relay --all-targets -- -D warnings: cleancargo fmt --all: cleanThe new tests are a helper table test (window edges, 0,
u64::MAX,2^63,now + 2^63 ± 1), a delta-widening test, an epoch-clock test, and the seam test.Not covered
created_at as i64. The general gate now rejects extreme values before that check runs.Review
No blockers were found. Every relay skew gate uses the shared helper, and each keeps its window and error text for in-range input. The canvas future check runs only after the ±900 s gate, so it cannot overflow. The sadscan marker matches the repo's existing fixture convention.
Optional:
unix_now_secs()and scopeevent_tsto the canvas branch.unix_now_secsdoc says "fails every check closed", butcreated_at≤ window still passes when now = 0.Exercised on a local relay
Commit tested:
ca729388a. Dev (debug) profile, so the old code would panic on overflow. Isolated harness relay on :3030 withBUZZ_REQUIRE_RELAY_MEMBERSHIP=trueand the sender set as relay owner. Events were signed with exactcreated_atvalues, including ones above 2^53, and sent over WSEVENT.invalid: event timestamp too far from server timeinvalid: event timestamp out of range: … delta=150s (max ±120s)invalid: event timestamp out of range: … delta=150s (max ±120s)member not foundatnow)invalid: leave request timestamp out of range (delta=150s, max ±120s)relay owner cannot leave)invalid: event timestamp out of range: … delta=150s (max ±120s)invalid: event timestamp too far from server timeThe relay never panicked, and the socket stayed open.
Observation: over WS ingest, the extreme values never reach the per-site command gates. The general ±900 s gate runs first and rejects them with its own text. So the overflow fix in the command gates is shown only by the unit and seam tests. On this path, the per-site text appears only for small out-of-window deltas.
Postgres-gated tests: I ran
cargo test -p buzz-relay --lib -- --ignored --test-threads=1 freshness moderation_commands relay_admin identity_archive ingest, pointingDATABASE_URL,BUZZ_TEST_DATABASE_URLandBUZZ_TEST_REDIS_URLat the harness: 32 passed, 1 failed. The failure isingest_write_fence_follows_community_deletion_lifecycle. It runs sqlx migrations, which fail against the harness schema that pgschema already applied (type "channel_type" already exists). That is a test-environment problem and doesn't involve the freshness code. The 4 freshness/moderation/relay-admin/identity-archive tests are among the passes; the filter withoutingestgave 9 passed.Not exercised:
/eventstransport.