This is an automated email from the ASF dual-hosted git repository.
rok pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new a3d630fc19a GH-50077: [C++][IPC] Avoid int64 overflow in
ReadSparseCSXIndex (#50038)
a3d630fc19a is described below
commit a3d630fc19af967761b84b87bb3aa3f0c26f3235
Author: jmestwa-coder <[email protected]>
AuthorDate: Tue Jun 30 17:33:05 2026 +0530
GH-50077: [C++][IPC] Avoid int64 overflow in ReadSparseCSXIndex (#50038)
### Rationale for this change
`ReadSparseCSXIndex` validates the SparseTensor `indices`/`indptr` buffer
sizes against the claimed shape using `int64` products (`non_zero_length *
byte_width` and `(shape[axis] + 1) * byte_width`). Both inputs come unchecked
from the flatbuffer via `GetSparseTensorMetadata`, so a value near `INT64_MAX`
overflows the signed product (UBSan-confirmed), wraps to a small value, and the
size guard passes. The index `Tensor` is then built over a buffer smaller than
its shape, enabling an o [...]
### What changes are included in this PR?
Compute the `indices`/`indptr` byte counts (and the `shape[axis] + 1` term)
with `MultiplyWithOverflow`/`AddWithOverflow`, the same checked helpers already
used for this size math in `tensor.cc`, and return `Status::Invalid` when the
computation overflows. Also reject a negative `non_zero_length` up front, since
the bare `Tensor` constructor used here skips the positive-shape check in
`CheckTensorValidity`.
### Are these changes tested?
Covered by the existing sparse tensor IPC round-trip tests; the change only
adds overflow/negative-input guards on the existing validation path.
### Are there any user-facing changes?
No.
* GitHub Issue: #50077
Authored-by: jmestwa-coder <[email protected]>
Signed-off-by: Rok Mihevc <[email protected]>
---
cpp/src/arrow/ipc/reader.cc | 34 ++++++++++++++++++++++++++--------
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/cpp/src/arrow/ipc/reader.cc b/cpp/src/arrow/ipc/reader.cc
index f8bbf6c13d3..b964c998dd9 100644
--- a/cpp/src/arrow/ipc/reader.cc
+++ b/cpp/src/arrow/ipc/reader.cc
@@ -79,6 +79,7 @@ namespace flatbuf = org::apache::arrow::flatbuf;
using internal::AddWithOverflow;
using internal::checked_cast;
using internal::checked_pointer_cast;
+using internal::MultiplyWithOverflow;
namespace ipc {
@@ -2331,6 +2332,9 @@ Result<std::shared_ptr<SparseIndex>> ReadSparseCSXIndex(
if (shape.size() != 2) {
return Status::Invalid("Invalid shape length for a sparse matrix");
}
+ if (non_zero_length < 0) {
+ return Status::Invalid("Invalid non-zero length for a sparse matrix");
+ }
auto* sparse_index = sparse_tensor->sparseIndex_as_SparseMatrixIndexCSX();
@@ -2350,16 +2354,24 @@ Result<std::shared_ptr<SparseIndex>> ReadSparseCSXIndex(
/*allow_short_read=*/false));
std::vector<int64_t> indices_shape({non_zero_length});
- const auto indices_minimum_bytes = indices_shape[0] *
indices_type->byte_width();
- if (indices_minimum_bytes > indices_buffer->length()) {
+ const auto indices_minimum_bytes =
+ MultiplyWithOverflow<int64_t>({indices_shape[0],
indices_type->byte_width()});
+ if (!indices_minimum_bytes.has_value() ||
+ indices_minimum_bytes.value() > indices_buffer->length()) {
return Status::Invalid("shape is inconsistent to the size of indices
buffer");
}
switch (sparse_index->compressedAxis()) {
case flatbuf::SparseMatrixCompressedAxis::SparseMatrixCompressedAxis_Row: {
- std::vector<int64_t> indptr_shape({shape[0] + 1});
- const int64_t indptr_minimum_bytes = indptr_shape[0] * indptr_byte_width;
- if (indptr_minimum_bytes > indptr_buffer->length()) {
+ const auto indptr_length = AddWithOverflow<int64_t>({shape[0], 1});
+ if (!indptr_length.has_value()) {
+ return Status::Invalid("shape is inconsistent to the size of indptr
buffer");
+ }
+ std::vector<int64_t> indptr_shape({indptr_length.value()});
+ const auto indptr_minimum_bytes =
+ MultiplyWithOverflow<int64_t>({indptr_shape[0], indptr_byte_width});
+ if (!indptr_minimum_bytes.has_value() ||
+ indptr_minimum_bytes.value() > indptr_buffer->length()) {
return Status::Invalid("shape is inconsistent to the size of indptr
buffer");
}
return std::make_shared<SparseCSRIndex>(
@@ -2367,9 +2379,15 @@ Result<std::shared_ptr<SparseIndex>> ReadSparseCSXIndex(
std::make_shared<Tensor>(indices_type, indices_data, indices_shape));
}
case
flatbuf::SparseMatrixCompressedAxis::SparseMatrixCompressedAxis_Column: {
- std::vector<int64_t> indptr_shape({shape[1] + 1});
- const int64_t indptr_minimum_bytes = indptr_shape[0] * indptr_byte_width;
- if (indptr_minimum_bytes > indptr_buffer->length()) {
+ const auto indptr_length = AddWithOverflow<int64_t>({shape[1], 1});
+ if (!indptr_length.has_value()) {
+ return Status::Invalid("shape is inconsistent to the size of indptr
buffer");
+ }
+ std::vector<int64_t> indptr_shape({indptr_length.value()});
+ const auto indptr_minimum_bytes =
+ MultiplyWithOverflow<int64_t>({indptr_shape[0], indptr_byte_width});
+ if (!indptr_minimum_bytes.has_value() ||
+ indptr_minimum_bytes.value() > indptr_buffer->length()) {
return Status::Invalid("shape is inconsistent to the size of indptr
buffer");
}
return std::make_shared<SparseCSCIndex>(