Skip to content

fix(build): verify Math constants in runtime snapshot - #726

Open
da1suk8 wants to merge 1 commit into
supabase:mainfrom
da1suk8:fix/snapshot-math-verification
Open

fix(build): verify Math constants in runtime snapshot#726
da1suk8 wants to merge 1 commit into
supabase:mainfrom
da1suk8:fix/snapshot-math-verification

Conversation

@da1suk8

@da1suk8 da1suk8 commented Aug 29, 2026

Copy link
Copy Markdown

Summary

Follow-up to #723 — this prevents recurrence; it does not by itself fix the already-published amd64 images. V8 computes Math.E, Math.LN10, Math.LN2, Math.LOG10E, and Math.LOG2E at snapshot-creation time, and crates/base/build.rs freezes those values into the startup snapshot that every worker boots from — so a build environment that corrupts the computation ships wrong constants to every user, and nothing in the pipeline could catch it. That is exactly what happened with the linux/amd64 v1.74.2/v1.74.3 images.

This PR passes a warmup script to create_snapshot (its second parameter — deno_core's built-in path that boots a snapshotting-mode runtime from the freshly created snapshot, runs the script, and re-serializes). The script compares all eight Math constants bit-for-bit against their IEEE-754 representations and throws on any mismatch, which fails create_snapshot — and the build — before the snapshot is ever written to disk. A corrupted snapshot can no longer reach a cache or a release silently; the failure mode becomes a loud build error:

Uncaught Error: the freshly created startup snapshot carries corrupted Math constants:
Math.PI: expected 0xhogehogehogehoge, got 0x400921fb54442d18 (3.141592653589793);
refusing to continue the build (see supabase/edge-runtime#723)

Notes on the approach:

  • Using the warmup path (rather than booting a regular JsRuntime after create_snapshot) keeps the whole process in V8's snapshotting mode, respecting deno_core's one-initialization-mode-per-process contract, and lets deno_core derive the ops-only extension set itself (Extension::for_warmup) — no duplicated extension list.
  • The shipped snapshot becomes a "warmed" one (re-serialized after the script runs). Measured locally (debug, macOS arm64): 9,936,452 → 6,780,949 bytes (~32% smaller), and the build script gains ~130 ms.
  • The check runs wherever the build runs — the release Docker lanes, CI, and local builds. Merging this also modifies build.rs, which forces one fresh snapshot build on the release lane, flushing any currently cached artifact.

Limitations

  • This is a canary on the eight Math constants, not a checksum of the whole blob — it detects the observed corruption class, not arbitrary snapshot damage.
  • Verification runs one re-serialization before the final bytes; only a nondeterministic corruption striking solely the second serialization could escape. A follow-up integration test probing the built binary would close that gap completely.
  • A snapshot corrupted at rest in a build cache after a verified build is not re-checked (cargo won't re-run the build script); that residual is covered by the cache-busting suggestion already discussed in the issue.
  • Already-published images are unaffected; they need a rebuild.

Validation

  • cargo check -p base / full build: the verification runs on every build and passes.
  • Negative test: temporarily corrupting one expected value fails the build (exit 101) with the message shown above; reverting restores a green build. (Note for anyone exercising the failure path: deno_core prints a harmless WARNING: v8::OwnedIsolate for snapshot was leaked on the error path.)
  • Booted the resulting binary against the warmed snapshot: a main-worker service returns all eight constants bit-exact over HTTP, and a user worker created via EdgeRuntime.userWorkers serves requests normally, with no errors logged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant