Skip to content

fix(bytesbuf): guard total capacity against overflow at growth sites#593

Open
sandersaares wants to merge 5 commits into
mainfrom
u/sasaares/bytesbuf-capacity-overflow
Open

fix(bytesbuf): guard total capacity against overflow at growth sites#593
sandersaares wants to merge 5 commits into
mainfrom
u/sasaares/bytesbuf-capacity-overflow

Conversation

@sandersaares

Copy link
Copy Markdown
Member

[Copilot speaking]

What

BytesBuf's type invariant that total capacity() == len + available never exceeds usize::MAX was asserted (relied upon by capacity()'s wrapping_add) but not actually enforced. The two capacity-growing sites each guarded only the single component they grow:

  • extend_capacity_by_at_least (the reserve path) checked only that available did not overflow.
  • append (the put_bytes path) checked only that len did not overflow.

Appending an existing BytesView reuses shared, immutable memory that can be aliased by multiple views, so the logical len can grow independently of physical address-space consumption. As a result len + available could pass usize::MAX while neither component overflowed on its own, and capacity() would silently return a wrapped value, violating internal accounting assumptions. This is reachable on 32-bit targets, where the address space is small enough that shared-span aliasing plus reserved capacity can push the sum past usize::MAX.

Fix

Both growth sites now validate the total len + available (not just the grown component) through a shared private assert_capacity_within_bounds helper, and do so before mutating any state, so a rejected grow leaves the buffer untouched. The # Panics docs on reserve/put_bytes already promised the total buffer capacity would be guarded, so this makes the code match the contract and makes capacity()'s invariant comment true.

Testing

capacity_bounds_check_* tests exercise assert_capacity_within_bounds directly at usize boundaries (usize::MAX, usize::MAX - 4 plus 8, usize::MAX/2 + 1 twice). The overflow regime is unreachable end-to-end on 64-bit (you cannot hold 2^64 bytes, even aliased), and forcing it by crafting len/available field values would construct an invalid BytesBuf, so the pure-function test is the honest way to lock in the overflow logic. The check-site wiring remains covered by the existing normal-path append/reserve tests plus review.

Verified locally: cargo test -p bytesbuf, cargo clippy -p bytesbuf --all-targets, just format, and just spellcheck all clean.

BytesBuf's `capacity() == len + available` type invariant (`capacity <= usize::MAX`) was asserted but not enforced. The two capacity-growing sites each guarded only the component they grow: `extend_capacity_by_at_least` (reserve) checked `available` alone and `append` (put_bytes) checked `len` alone. Because appended shared memory lets the logical `len` grow independently of physical address-space consumption, `len + available` could exceed `usize::MAX` while neither component overflowed on its own, causing `capacity()`'s `wrapping_add` to silently return a wrapped value. This is reachable on 32-bit targets.

Both growth sites now check the total `len + available` via a shared `assert_capacity_within_bounds` helper, before mutating any state, so the invariant the `# Panics` docs already promise is actually upheld.

Regression coverage tests the helper directly with `usize`-boundary values; the overflow regime is unreachable end-to-end on 64-bit and cannot be forced by crafting field values without constructing an invalid buffer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15947dc4-48a0-44e0-b4af-00c1ae525e5f
Copilot AI review requested due to automatic review settings July 22, 2026 12:31

Copilot AI 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.

Pull request overview

This PR hardens bytesbuf::BytesBuf’s capacity accounting by enforcing the invariant that total capacity (len + available) never exceeds usize::MAX, preventing capacity()’s wrapping_add from silently producing wrapped values (notably reachable on 32-bit targets via shared-span aliasing + reserved capacity).

Changes:

  • Introduces a shared assert_capacity_within_bounds(len, available) guard for the total-capacity invariant.
  • Wires the guard into both growth sites: extend_capacity_by_at_least (reserve path) and append (put_bytes path), before mutating the relevant state.
  • Adds focused unit tests that exercise boundary and overflow cases for the helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/bytesbuf/src/buf.rs
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

⚠️ Potential breaking changes detected

cargo semver-checks flagged the following on this PR. This is informational -- breaking changes between commits are expected; the major-version bump happens at release time, not on every PR.

bytesbuf

    Building bytesbuf v0.6.0 (current)
       Built [   3.284s] (current)
     Parsing bytesbuf v0.6.0 (current)
      Parsed [   0.005s] (current)
    Building bytesbuf v0.6.0 (baseline)
       Built [   3.247s] (baseline)
     Parsing bytesbuf v0.6.0 (baseline)
      Parsed [   0.004s] (baseline)
    Checking bytesbuf v0.6.0 -> v0.6.0 (no change; assume minor)
     Checked [   0.016s] 196 checks: 195 pass, 1 fail, 0 warn, 49 skip

--- failure inherent_method_missing: pub method removed or renamed ---

Description:
A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/inherent_method_missing.ron

Failed in:
  BytesView::get_num_le, previously in file /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytesbuf-0.6.0/src/view_get.rs:232
  BytesView::get_num_be, previously in file /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytesbuf-0.6.0/src/view_get.rs:319
  BytesView::get_num_ne, previously in file /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytesbuf-0.6.0/src/view_get.rs:409
  BytesBuf::put_num_le, previously in file /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytesbuf-0.6.0/src/buf_put.rs:211
  BytesBuf::put_num_be, previously in file /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytesbuf-0.6.0/src/buf_put.rs:238
  BytesBuf::put_num_ne, previously in file /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytesbuf-0.6.0/src/buf_put.rs:266

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [   7.475s] bytesbuf

fetch

    Building fetch v0.13.0 (current)
       Built [  29.704s] (current)
     Parsing fetch v0.13.0 (current)
      Parsed [   0.007s] (current)
    Building fetch v0.13.0 (baseline)
       Built [  28.757s] (baseline)
     Parsing fetch v0.13.0 (baseline)
      Parsed [   0.006s] (baseline)
    Checking fetch v0.13.0 -> v0.13.0 (no change; assume minor)
     Checked [   0.014s] 196 checks: 194 pass, 2 fail, 0 warn, 49 skip

--- failure method_parameter_count_changed: pub method parameter count changed ---

Description:
A publicly-visible method now takes a different number of parameters, not counting the receiver (self) parameter.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#fn-change-arity
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/method_parameter_count_changed.ron

Failed in:
  fetch::handlers::Metrics::layer now takes 0 parameters instead of 1, in /home/runner/work/oxidizer/oxidizer/crates/fetch/src/handlers/metrics.rs:235

  fetch::handlers::Logging::layer now takes 0 parameters instead of 2, in /home/runner/work/oxidizer/oxidizer/crates/fetch/src/handlers/logging.rs:43
     Summary semver requires new major version: 2 major and 0 minor checks failed

--- failure method_requires_different_generic_type_params: method now requires a different number of generic type parameters ---

Description:
A method now requires a different number of generic type parameters than it used to. Uses of this method that supplied the previous number of generic types will be broken.
        ref: https://doc.rust-lang.org/reference/items/generics.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/method_requires_different_generic_type_params.ron

Failed in:
  fetch::HttpClientBuilder::meter_provider takes 1 generic types instead of 0, in /home/runner/work/oxidizer/oxidizer/crates/fetch/src/client_builder.rs:254
    Finished [  60.625s] fetch

Add `#[track_caller]` to `assert_capacity_within_bounds` so an overflow panic reports the actual growth site (`reserve`/`append`) rather than the helper, and include the `len`/`available` values in the panic message for easier diagnosis.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15947dc4-48a0-44e0-b4af-00c1ae525e5f
Copilot AI review requested due to automatic review settings July 22, 2026 13:11

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread crates/bytesbuf/src/buf.rs Outdated
Reword `assert_capacity_within_bounds`'s doc: `capacity()` returns `usize`, so "`capacity() <= usize::MAX`" was tautological. State the real invariant instead - the true (non-wrapping) sum `len + available` must fit in `usize` so `capacity()` does not wrap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15947dc4-48a0-44e0-b4af-00c1ae525e5f
Copilot AI review requested due to automatic review settings July 22, 2026 13:33

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

crates/bytesbuf/src/buf.rs:137

  • The new helper/doc explains the real invariant (non-wrapping len + available), but there are still internal comments elsewhere in this file that say "capacity <= usize::MAX is a type invariant" (e.g., capacity() and calculate_len()), which is tautological and no longer matches what’s actually being enforced. Please update those comments to refer to the checked len + remaining_capacity invariant (guarded by assert_capacity_within_bounds at the two growth sites) so future readers don’t get misled.
/// Panics if the true (non-wrapping) sum `len + available` would overflow `usize`.
///
/// `capacity()` returns `len + available`, so that sum must fit in `usize` for `capacity()` not to
/// wrap. The two capacity-growing sites (`reserve` and `append`) each grow only one of `len` or
/// `available`. A bounds check on the grown component alone is insufficient: `len` can grow via
/// appended shared memory while `available` holds separately reserved capacity, so only their sum
/// is bounded.

…overflow guard

Reword the internal comments that described a tautological "`capacity <= usize::MAX` is a type invariant" (in `capacity()`, `calculate_len()`, the consume span-count loop, `ConsumeManifest::required_spans_capacity`, and the remaining-capacity iterator) to state the real, enforced invariant: the true (non-wrapping) sum `len + available` never overflows `usize`, guarded at the growth sites by `assert_capacity_within_bounds`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15947dc4-48a0-44e0-b4af-00c1ae525e5f
Copilot AI review requested due to automatic review settings July 22, 2026 14:09

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread crates/bytesbuf/src/buf.rs Outdated
Move the capacity-overflow check in `append` ahead of `freeze_from_first` so a rejected append leaves the buffer untouched, as the docs promise. Freezing changes neither `len` nor `available`, so the check is equivalent before or after it, but running it first ensures the panic path performs no mutation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 15947dc4-48a0-44e0-b4af-00c1ae525e5f
Copilot AI review requested due to automatic review settings July 22, 2026 14:34

Copilot AI 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.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (f4ef129) to head (3e72d91).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #593   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         359      359           
  Lines       27800    27808    +8     
=======================================
+ Hits        27800    27808    +8     
Flag Coverage Δ
linux 85.7% <100.0%> (-14.3%) ⬇️
linux-arm 86.7% <100.0%> (-13.3%) ⬇️
scheduled ?
windows 87.1% <100.0%> (-12.9%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

/// `available`. A bounds check on the grown component alone is insufficient: `len` can grow via
/// appended shared memory while `available` holds separately reserved capacity, so only their sum
/// is bounded.
#[track_caller]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

#[inline(always)]?

@ralfbiedert ralfbiedert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved. The overflow checks cover the actual total-capacity growth sites, execute before mutation, and do not affect the byte-write hot path. Nearby unsafe copying invariants are unchanged.

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.

6 participants