Skip to content

fix(snapshot): bump FetchSnapshotConfig read cap from 1 MiB to 64 MiB#3

Merged
tonicmuroq merged 1 commit intomainfrom
fix/snapshot-config-limit
May 6, 2026
Merged

fix(snapshot): bump FetchSnapshotConfig read cap from 1 MiB to 64 MiB#3
tonicmuroq merged 1 commit intomainfrom
fix/snapshot-config-limit

Conversation

@tonicmuroq
Copy link
Copy Markdown
Contributor

Fixes #2.

Summary

pull.go:159 reads the snapshot config blob via io.ReadAll(io.LimitReader(body, 1<<20)) with the comment "config blob is tiny", but the SnapshotConfig embeds per-file SparseMap fields. A 4 GiB Windows guest running an Electron app with a live Firebase WebSocket signed in produces sparse maps in the high-hundred-KB range per fragmented file, and the config blob easily exceeds 1 MiB once memory-ranges and overlay.qcow2 are both accounted for.

Observed live:

field bytes
files["memory-ranges"].sparseMap 580,107
files["overlay.qcow2"].sparseMap 611,926
base config fields ~few KB
total config blob 1,367,436

Symptom on the wake side:

ERR wake default/<vm> error="pull hibernation snapshot <name>:
    stream snapshot:
    fetch snapshot config:
    parse snapshot config: unexpected end of JSON input"

io.LimitReader returns EOF after 1,048,576 bytes; io.ReadAll swallows the EOF; the caller hands the truncated buffer to json.Unmarshal, which fails on the unterminated structure.

Fix

  • Raise the cap to 64 MiB (still bounded — defends against a malicious manifest pointing at an arbitrarily large config blob).
  • Pre-check desc.Size so we reject oversize blobs before any byte is read.
  • Add a streaming overflow guard for the case where the descriptor lies about Size.

The reader path is the only thing that changes; pushers and on-disk format are untouched.

Relationship to cocoonstack/cocoon#23

Independent of #23. #23 addressed the writer-side tar PAX cap (a single file's sparse map > 1 MiB couldn't be encoded). This is the reader-side config cap (aggregate sparse maps + config metadata > 1 MiB couldn't be parsed). Both bugs share the same triggering condition (live Firebase agent → fragmented memory) but live in different layers.

Test plan

  • TestFetchSnapshotConfigOverOneMiB — builds a real ~1.4 MB SnapshotConfig with two fragmented sparse maps and asserts parse succeeds. Would have failed under the old 1 MiB cap with "unexpected end of JSON input".
  • TestFetchSnapshotConfigRejectsOversizeDescriptor — descriptor advertising > 64 MiB is rejected before any read.
  • go test ./snapshot/ — all existing tests still pass alongside the new ones.

🤖 Generated with Claude Code

The previous cap was set with the comment "config blob is tiny", but
the SnapshotConfig embeds a per-file SparseMap for every file in the
snapshot. A 4 GiB Windows VM whose guest has many small live
allocations (e.g. an Electron app with a Firebase WebSocket signed in)
produces sparse maps in the high-hundred-KB range per fragmented file,
and the config blob easily exceeds 1 MiB once memory-ranges and
overlay.qcow2 are both accounted for. Observed live: 580 KB +
612 KB + base fields = 1.37 MB total config blob.

Wake-side symptom was

    pull hibernation snapshot ...:
        stream snapshot:
        fetch snapshot config:
        parse snapshot config: unexpected end of JSON input

— `io.ReadAll(io.LimitReader(body, 1<<20))` truncated the JSON exactly
at 1 MiB, leaving an unterminated structure.

Bump the cap to 64 MiB (still defends against pathological / malicious
manifests) and add a descriptor.Size pre-check so we reject oversize
blobs before reading. Add a streaming overflow guard for the case
where the descriptor lies about Size.

Tests:
  - TestFetchSnapshotConfigOverOneMiB: builds a real ~1.4 MB
    SnapshotConfig with two fragmented sparse maps and asserts parse
    succeeds (would have failed under the old 1 MiB cap).
  - TestFetchSnapshotConfigRejectsOversizeDescriptor: rejects a
    descriptor advertising > 64 MiB up front.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tonicmuroq tonicmuroq force-pushed the fix/snapshot-config-limit branch from dc0745d to 0315e8a Compare May 6, 2026 02:17
@tonicmuroq tonicmuroq merged commit d94f215 into main May 6, 2026
2 checks passed
@tonicmuroq tonicmuroq deleted the fix/snapshot-config-limit branch May 6, 2026 02:19
tonicmuroq added a commit to cocoonstack/vk-cocoon that referenced this pull request May 6, 2026
Picks up cocoonstack/epoch#3, which raises the snapshot-config blob
read cap from 1 MiB to 64 MiB. Wake-side parse failed with
"unexpected end of JSON input" when the SnapshotConfig — including
per-file SparseMap fields for memory-ranges and overlay.qcow2 — grew
past 1 MiB, which a 4 GiB Windows guest with a Firebase-active agent
hits routinely.

No vk-cocoon source changes; just the go.mod / go.sum bump and a
rebuild. Existing tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tonicmuroq added a commit to cocoonstack/vk-cocoon that referenced this pull request May 6, 2026
Picks up cocoonstack/epoch#3, which raises the snapshot-config blob
read cap from 1 MiB to 64 MiB. Wake-side parse failed with
"unexpected end of JSON input" when the SnapshotConfig — including
per-file SparseMap fields for memory-ranges and overlay.qcow2 — grew
past 1 MiB, which a 4 GiB Windows guest with a Firebase-active agent
hits routinely.

No vk-cocoon source changes; just the go.mod / go.sum bump and a
rebuild. Existing tests pass.
tonicmuroq added a commit to cocoonstack/vk-cocoon that referenced this pull request May 6, 2026
Picks up cocoonstack/epoch#3, which raises the snapshot-config blob
read cap from 1 MiB to 64 MiB. Wake-side parse failed with
"unexpected end of JSON input" when the SnapshotConfig — including
per-file SparseMap fields for memory-ranges and overlay.qcow2 — grew
past 1 MiB, which a 4 GiB Windows guest with a Firebase-active agent
hits routinely.

No vk-cocoon source changes; just the go.mod / go.sum bump and a
rebuild. Existing tests pass.
CMGS pushed a commit to cocoonstack/vk-cocoon that referenced this pull request May 6, 2026
Picks up cocoonstack/epoch#3, which raises the snapshot-config blob
read cap from 1 MiB to 64 MiB. Wake-side parse failed with
"unexpected end of JSON input" when the SnapshotConfig — including
per-file SparseMap fields for memory-ranges and overlay.qcow2 — grew
past 1 MiB, which a 4 GiB Windows guest with a Firebase-active agent
hits routinely.

No vk-cocoon source changes; just the go.mod / go.sum bump and a
rebuild. Existing tests pass.
CMGS pushed a commit to cocoonstack/vk-cocoon that referenced this pull request May 6, 2026
Picks up cocoonstack/epoch#3, which raises the snapshot-config blob
read cap from 1 MiB to 64 MiB. Wake-side parse failed with
"unexpected end of JSON input" when the SnapshotConfig — including
per-file SparseMap fields for memory-ranges and overlay.qcow2 — grew
past 1 MiB, which a 4 GiB Windows guest with a Firebase-active agent
hits routinely.

No vk-cocoon source changes; just the go.mod / go.sum bump and a
rebuild. Existing tests pass.
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.

FetchSnapshotConfig truncates config blob > 1 MiB → wake fails on highly fragmented snapshots

1 participant