Skip to content

Check for overflow in StorageView::reserve before allocation - #2094

Merged
jordimas merged 1 commit into
OpenNMT:masterfrom
infinityscroll:fix/storage-view-byte-overflow-20260830
Aug 30, 2026
Merged

jordimas merged 1 commit into
OpenNMT:masterfrom
infinityscroll:fix/storage-view-byte-overflow-20260830

Conversation

@infinityscroll

@infinityscroll infinityscroll commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Reject negative reserve requests before the existing capacity fast path.
  • Check the element-count-to-byte-count multiplication before allocation, using division to avoid signed overflow.
  • Validate before releasing the current buffer, so a rejected request preserves its contents and shape.
  • Exercise FLOAT16/FLOAT32 byte-count boundaries, negative sizes, preservation after rejection, and zero/smaller/equal-capacity no-ops.

Validation

Built and tested on macOS arm64, AppleClang 21, Debug, Ruy backend, two build jobs:

cmake -S . -B build -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
  -DCMAKE_BUILD_TYPE=Debug -DWITH_MKL=OFF -DWITH_ACCELERATE=OFF \
  -DWITH_RUY=ON -DOPENMP_RUNTIME=NONE -DBUILD_TESTS=ON \
  -DBUILD_CLI=OFF -DBUILD_SHARED_LIBS=ON
cmake --build build --target ctranslate2_test --parallel 2
build/tests/ctranslate2_test tests/data \
  '--gtest_filter=StorageViewTest.*:CPU/StorageViewDeviceTest.*'
build/tests/ctranslate2_test tests/data --gtest_brief=1
  • Targeted storage tests: 11/11 passed.
  • Before the follow-up correction, the same tests against the original Check for overflow in StorageView::reserve byte size #2092 implementation reproduced the negative-size and buffer-preservation failures (9 passed, 2 failed).
  • Full local suite: 192 passed, 1 skipped, 3 failed (196 total). The failures are CPU/OpDeviceFPTest.Gemm/float32, GemmBias/float32, and GemmResidual/float32. The same three numerical failures were present in the Check for overflow in StorageView::reserve byte size #2092 baseline before the follow-up correction.
  • git diff --check passes. Linux/MKL, CUDA, and Windows have not been tested locally; upstream CI is still needed.

AI assistance disclosure

OpenAI Codex assisted with preparing the follow-up correction and regression tests.

Reject negative sizes before the capacity fast path, check byte-count multiplication before releasing storage or allocating, and add safe overflow, preservation, and no-op regression tests.
@infinityscroll
infinityscroll force-pushed the fix/storage-view-byte-overflow-20260830 branch from 3227a01 to b817e51 Compare August 30, 2026 16:14
@infinityscroll

infinityscroll commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor Author

@jordimas Could you review it when you have a chance?

@jordimas

Copy link
Copy Markdown
Collaborator

@jordimas Could you review it when you have a chance?

Looks good. Thanks for your contribution

@jordimas
jordimas merged commit af0453f into OpenNMT:master Aug 30, 2026
22 checks passed
jordimas added a commit that referenced this pull request Oct 18, 2026
* Guard dim_t overflow in compute_size, reshape and grow

The model loader validates shapes with a per-multiplication overflow
check (since #2091), but the StorageView API paths still multiply dims
unchecked: compute_size() and StorageView::reshape() can wrap int64_t
(e.g. a shape of {2^62+1, 8} wraps to 8), yielding a view whose shape
claims far more elements than _size, and grow() can wrap its addition.

Apply the same per-multiplication guard used by the loader to
compute_size() and reshape(), add an overflow check to grow(), and
reject the overflowing cases with std::invalid_argument in two new unit
tests, mirroring the #2094 test style.

* Accept zero dimensions in the overflow guards

A zero dimension yields an empty tensor, which cannot overflow, but the
guards divided by it to compare with the running product: x86_64 raises
SIGFPE, while AArch64 division by zero returns 0 and made every later
check reject valid zero-dim shapes. Skip the division when the dimension
is zero.

* Check empty dimensions before multiplying the shape size

A zero dimension makes the size 0 whatever its position, so the other
dimensions are never multiplied and an overflowing prefix next to a zero
dimension is not an error. Skipping the division is not enough: the
prefix can overflow before the zero is reached, which made the result
depend on where the zero appeared in the shape.

The same ordering in reshape() also left the known size at 0 when the new
shape combined a zero dimension with an unknown one, and that 0 is the
divisor of the compatibility check below (crash on master today). The
unknown dimension is now rejected in that case.

* Cover the grow() guard with unit tests

Split the negative increment out of the overflow condition so it gets its own
message, and pin the three cases: a negative size (which used to silently shrink
the dimension), an overflowing addition (tested on {INT64_MAX, 0}, where the zero
dimension keeps the allocation empty), and normal growth.

---------

Co-authored-by: Jordi Mas <jmas@softcatala.org>
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.

2 participants