From 19e092da548a4db17a8487c8d50ad0b7cd457b41 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Sat, 25 Apr 2026 11:20:29 -0400 Subject: [PATCH] Prevent Rows row index overflow (#9817) - None. Rows used unchecked usize arithmetic when validating a requested row index. In optimized builds, very large indexes could wrap the bounds check before reaching the unchecked row access path. This adds checked arithmetic for row index validation and reuses it for both Rows::row and Rows::row_len. Yes. This adds regression coverage for overflowing row indexes. Invalid row indexes that overflow during bounds validation now panic consistently. There are no API changes. --- arrow-row/src/lib.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/arrow-row/src/lib.rs b/arrow-row/src/lib.rs index cdb52a8ee7fd..7e6cbc3bcc86 100644 --- a/arrow-row/src/lib.rs +++ b/arrow-row/src/lib.rs @@ -1020,14 +1020,20 @@ impl Rows { /// Returns the row at index `row` pub fn row(&self, row: usize) -> Row<'_> { - assert!(row + 1 < self.offsets.len()); + self.checked_row_end(row); unsafe { self.row_unchecked(row) } } + fn checked_row_end(&self, row: usize) -> usize { + row.checked_add(1) + .filter(|end| *end < self.offsets.len()) + .expect("row index out of bounds") + } + /// Returns the row at `index` without bounds checking /// /// # Safety - /// Caller must ensure that `index` is less than the number of offsets (#rows + 1) + /// Caller must ensure that `index + 1` is less than the number of offsets (#rows + 1) pub unsafe fn row_unchecked(&self, index: usize) -> Row<'_> { let end = unsafe { self.offsets.get_unchecked(index + 1) }; let start = unsafe { self.offsets.get_unchecked(index) }; @@ -3588,4 +3594,13 @@ mod tests { assert_eq!(unchecked_values_len, 13); assert!(checked_values_len > unchecked_values_len); } + + #[test] + #[should_panic(expected = "row index out of bounds")] + fn row_should_panic_on_overflowing_index() { + let rows = RowConverter::new(vec![SortField::new(DataType::Int32)]) + .unwrap() + .empty_rows(0, 0); + rows.row(usize::MAX); + } }