Skip to content

fix(encoding): return an error instead of panicking on corrupt miniblock value counts#7997

Open
hindriix wants to merge 1 commit into
lance-format:mainfrom
hindriix:fix/miniblock-corrupt-value-counts
Open

fix(encoding): return an error instead of panicking on corrupt miniblock value counts#7997
hindriix wants to merge 1 commit into
lance-format:mainfrom
hindriix:fix/miniblock-corrupt-value-counts

Conversation

@hindriix

Copy link
Copy Markdown

What

FileReader panicked while decoding a Lance v2 file with corrupted miniblock
metadata instead of returning an error. With a debug build the panic is
attempt to subtract with overflow; in release the subtraction wraps and the
bogus count trips a later panic during decode.

Why

analyze_value_counts derives the final chunk's value count as
items_in_page - counted, where counted accumulates 1 << log for every
non-last chunk and each log comes straight from the on-disk metadata words.
Corrupted words can make counted exceed items_in_page, so the subtraction
underflows. Nothing validated the accumulated count against the page size.

This is the miniblock row-count path reported in #7936; the surrounding code
was refactored in #7564 (analyze_value_counts / build_chunk_index), but the
unchecked subtraction moved with it and still panics.

How

  • Compute the last chunk's count with checked_sub and return
    Error::InvalidInput (with the offending counted / items_in_page values)
    when it underflows, matching the crate's existing decode-time validation
    style.
  • Thread the Result out of analyze_value_counts and build_chunk_index;
    the sole production caller in MiniBlockScheduler::initialize already runs in
    a Result context and simply propagates with ?.
  • flat_value_counts_iter recomputes the same counted only after
    analyze_value_counts has succeeded, so its last-chunk subtraction is now
    guaranteed non-negative and is left unchanged.

Verification

  • Added test_corrupt_chunk_counts_error (rstest, two cases) asserting both the
    Error::InvalidInput variant and the message. It reproduces the panic on
    main before the fix and passes after.
  • cargo test -p lance-encoding --lib: 513 passed, 0 failed, 5 ignored.
  • cargo fmt --all -- --check and
    cargo clippy -p lance-encoding --tests -- -D warnings: clean.

Thanks to @mmatczuk for the fuzzer report and precise root-cause pointer.

Fixes #7936

…ock value counts

analyze_value_counts subtracted the accumulated non-last chunk value counts
from the page item count without validating them. Corrupted miniblock
metadata can make the non-last chunks claim more items than the page holds,
underflowing the subtraction and panicking ("attempt to subtract with
overflow" in debug; a wrapped value that panics later in release).

Validate with checked_sub and surface Error::InvalidInput, propagating the
Result through build_chunk_index so FileReader returns a clean error on a
malformed file instead of aborting.
@github-actions github-actions Bot added the A-encoding Encoding, IO, file reader/writer label Jul 25, 2026
@github-actions github-actions Bot added the bug Something isn't working label Jul 25, 2026
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The miniblock value-count analysis now validates metadata using checked subtraction and returns InvalidInput for corruption. Chunk-index construction and scheduler initialization propagate these errors, while tests cover the new Result API and corruption case.

Miniblock metadata validation

Layer / File(s) Summary
Validate and propagate miniblock errors
rust/lance-encoding/src/encodings/logical/primitive.rs
analyze_value_counts detects invalid counts, build_chunk_index returns and propagates errors, and miniblock scheduler initialization forwards them.
Cover corrupted metadata errors
rust/lance-encoding/src/encodings/logical/primitive.rs
Existing tests unwrap successful chunk-index construction, and a new test verifies corrupted metadata returns Error::InvalidInput with the expected message.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: xuanwo, ali2arslan

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: returning an error instead of panicking on corrupt miniblock counts.
Description check ✅ Passed The description is directly related to the code changes and accurately explains the bug, fix, and verification.
Linked Issues check ✅ Passed The PR addresses #7936 by validating corrupt miniblock counts and returning an error instead of panicking.
Out of Scope Changes check ✅ Passed The changes stay focused on miniblock count validation, API propagation, and regression tests with no unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
rust/lance-encoding/src/encodings/logical/primitive.rs (1)

7476-7504: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover nested and scheduler error propagation.

This regression test passes None, 0, so it exercises only the flat call at Line 2518. Add coverage for the nested call at Line 2500 and an initialization-level assertion for propagation at Line 2585.

As per coding guidelines, every bugfix and feature must have corresponding tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-encoding/src/encodings/logical/primitive.rs` around lines 7476 -
7504, Extend the corruption tests around test_corrupt_chunk_counts_error to
cover nested chunk-index construction by supplying repetition metadata and a
nonzero nesting level, exercising the nested build_chunk_index path. Add an
initialization-level test near the existing scheduler/error-propagation coverage
to assert the InvalidInput error from corrupt chunk metadata is propagated
unchanged through initialization.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/lance-encoding/src/encodings/logical/primitive.rs`:
- Around line 2427-2435: Update the miniblock metadata validation around the
chunk-counting logic to reject every malformed input with Error::invalid_input
instead of assertions. In the surrounding loop, use checked_add when
accumulating counted, reject non-last chunks with log == 0, and replace the
last-chunk consistency assertion with an error; each error must include the
chunk index, log value, and relevant derived counts while preserving the
existing checked_sub handling for page underflow.

---

Nitpick comments:
In `@rust/lance-encoding/src/encodings/logical/primitive.rs`:
- Around line 7476-7504: Extend the corruption tests around
test_corrupt_chunk_counts_error to cover nested chunk-index construction by
supplying repetition metadata and a nonzero nesting level, exercising the nested
build_chunk_index path. Add an initialization-level test near the existing
scheduler/error-propagation coverage to assert the InvalidInput error from
corrupt chunk metadata is propagated unchanged through initialization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 71f953b6-ba8e-416a-9062-d2d90cb43217

📥 Commits

Reviewing files that changed from the base of the PR and between e1dbd14 and 4426942.

📒 Files selected for processing (1)
  • rust/lance-encoding/src/encodings/logical/primitive.rs

Comment on lines +2427 to +2435
// The last chunk holds whatever items the non-last chunks did not. Corrupted
// metadata can make the non-last chunks claim more items than the page holds,
// so validate rather than underflow on the subtraction.
let last_chunk_values = items_in_page.checked_sub(counted).ok_or_else(|| {
Error::invalid_input(format!(
"miniblock metadata is corrupt: chunk value counts sum to at least {counted}, \
which exceeds the page item count {items_in_page}"
))
})?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Reject all malformed miniblock metadata instead of asserting.

checked_sub fixes only the overflow case. A non-last log == 0 still triggers the debug assertion at Line 2424, while an inconsistent nonzero last log still triggers Line 2437 in debug builds and is silently accepted in release builds. Replace these assertions with Error::invalid_input returns, use checked_add for counted, and include the chunk index, log value, and derived counts in the error.

Suggested direction
-        debug_assert!(log > 0);
-        counted += 1u64 << log;
+        if log == 0 {
+            return Err(Error::invalid_input(/* chunk index and log */));
+        }
+        counted = counted
+            .checked_add(1u64 << log)
+            .ok_or_else(|| Error::invalid_input(/* accumulated count context */))?;

-        debug_assert!(last_log == 0 || (1u64 << last_log) == last_chunk_values);
+        if last_log != 0 && (1u64 << last_log) != last_chunk_values {
+            return Err(Error::invalid_input(/* last-log/count context */));
+        }

As per coding guidelines, invalid API-boundary input must be rejected with descriptive errors rather than assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/lance-encoding/src/encodings/logical/primitive.rs` around lines 2427 -
2435, Update the miniblock metadata validation around the chunk-counting logic
to reject every malformed input with Error::invalid_input instead of assertions.
In the surrounding loop, use checked_add when accumulating counted, reject
non-last chunks with log == 0, and replace the last-chunk consistency assertion
with an error; each error must include the chunk index, log value, and relevant
derived counts while preserving the existing checked_sub handling for page
underflow.

Source: Coding guidelines

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

Labels

A-encoding Encoding, IO, file reader/writer bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FileReader panics on corrupted miniblock row counts

1 participant