ck: checked fork sum in Rust branch-table validation [host PASS] - #232
Merged
Merged
Conversation
The branch-table fork sum used plain `.sum()`. Kernel images build with
--release and no Cargo profile enables overflow checks, so the sum wrapped:
{root forks = 2^64-1, child forks = 2} summed to 1 == children and was
accepted, while the C codec refuses it (continuity_codec.c:482-491 on
hive/CK-5-CODEC-continuity-codec). The Rust oracle (ADR 0024 Q2) now uses
try_fold + checked_add and refuses with the same class and text:
Corrupt("fork indexes are not contiguous").
Tests: hostile wraparound table refused via validate() and decode();
a full table (256 rows, fork sum 255) still validates and round-trips.
Mutation: reverting to `.sum()` makes the hostile-table test fail under
--release (wrap -> accepted).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AgentState::validate scanned 0..parent.forks with no bound; forks is
input-controlled, so a hostile table (forks = 2^64-1, child id not a derived
index) made the Rust oracle spin. The scan is now capped at MAX_BRANCHES,
as C does (continuity_codec.c:468 on hive/CK-5-CODEC-continuity-codec), and
such a child is refused Corrupt("branch lineage is inconsistent"), C's class
and text (continuity_codec.c:474-479). Valid tables have every forks <= 255,
so no valid outcome changes.
Test: huge_fork_count_with_underivable_child_is_refused_without_spinning.
Mutation: removing the cap makes that test hang (~2^64 hashes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aien-dev
marked this pull request as ready for review
October 1, 2026 19:04
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.
What
Two oracle fixes in
AgentState::validate(crates/aienos-kernel/src/continuity.rs) so the Rust oracle (ADR 0024 Q2) refuses what the C codec (#227) refuses.Commit 1 (09a0bab): checked fork sum
The fork sum used plain
.sum()(was :476). Kernel images build with--releaseand no Cargo profile enables overflow checks (source: ~/handoffs/2026-10-01-CK-5-CODEC-inspector.md, "Builder finding 2"), so the sum wraps. Table {root forks = 2^64-1, child index 0 forks = 2} summed to 1 == children and was accepted by Rust release, while C refuses it.Fix:
try_fold(0u64, |acc, b| acc.checked_add(b.forks)); refusalContinuityError::Corrupt("fork indexes are not contiguous"), the same class and text as Cfail(why, CC_E_CORRUPT, "fork indexes are not contiguous")at native/kernel/svc/continuity_codec.c:482-491 (branch hive/CK-5-CODEC-continuity-codec).Commit 2 (214527f): cap the lineage index scan
validatescanned0..parent.forkswith no bound (was :468, inspector finding 1).forksis input-controlled, so a table with forks = 2^64-1 and a child id that is no derived index made Rust spin for ~2^64 hashes. The scan is now capped atMAX_BRANCHES(256) exactly as C does (continuity_codec.c:468); such a child is refusedCorrupt("branch lineage is inconsistent"), C's class and text (continuity_codec.c:474-479). A valid table has every forks <= 255, so no valid outcome changes. Difference from old Rust: a child at index >= 256 under a huge parent used to be refused later as "fork indexes are not contiguous"; it now gets the lineage text, matching C. Implemented as a cap (C's approach) rather than a refusal before the loop, because an early refusal would give a different reason text than C for a huge-forks parent whose child sits at a low index.Tests (continuity_tests.rs)
wrapping_fork_sum_is_refused_like_the_c_codec: hostile wrap table refused viavalidate()and viadecode()of patched honest bytes (layout asserted before patching); honest bytes still decode.branch_table_at_the_maximum_legal_fork_sum_is_accepted: 256 rows (root + 255 children, fork sum 255) validates and round-trips; a 257th fork isLimit("too many branches").huge_fork_count_with_underivable_child_is_refused_without_spinning: forks = 2^64-1 with a rogue child id (asserted not to be any index < 256) refused with the lineage text.Mutations:
.sum()makes the wrap test fail under--release(sum wraps to 1, table accepted). In debug the revert panics on overflow instead, so the release run is the one that proves the wrap case.Other arithmetic on decoded counts/lengths (continuity.rs, recovery_core.rs)
bytes.len() - HEADER_BYTES: safe, 16 header bytes already taken.fork()index + 1(:382 before this PR): unchanged. C does the same unguarded add (continuity_codec.c:516); after commit 1 any validated/decoded table has every forks <= 255, so unreachable from decoded input.i as u64 + 1: i < manifests.len(), safe.provision()store.generation() + 1(:738 before this PR): left unchanged on purpose. At u64::MAX it wraps in release, buttransactthen refuses cleanly withGenerationExhaustedand zero writes (store/engine.rs:418, test :1201-1211); debug would panic.entries.len() - roots - manifests: disjoint filter counts, safe. :2511 - active: slot_id checked <= 1 at decode (store/v1.rs:550).Evidence
All four runs are host unit runs on the light lane, and all four ran on PR head 214527f. Run 1 was queued before commit 2 but executed after it was pushed: its logs list
huge_fork_count_with_underivable_child_is_refused_without_spinning. Each log reports13 passed; 0 failedand lists all three new tests (forge receipt: ~/workspace/test-queue-results.md:196-199).-p aienos-kernel --lib continuity(debug)-p aienos-kernel --release --lib continuityRUSTSUM_RUN=2 ... -p aienos-kernel --lib continuity(debug)RUSTSUM_RUN=2 ... -p aienos-kernel --release --lib continuity(proves the wrap case)Logs live in ~/workspace/test-queue-logs/ on the Spark.
Inspector: VERDICT: merge (~/handoffs/2026-10-01-CK-5-RUSTSUM-INSPECT-inspector.md, head 214527f). Both divergences are fixed with C's refusal class and text, each fix has a hostile test that fails on the old code, and the maximum legal table still passes.
QEMU: not applicable. Hardware: NOT_RUN.
No C code, GATES.md or contract doc touched.
🤖 Generated with Claude Code