get_row_and_optional_column (src/xlsx/mod.rs:2825-2860) accumulates both components with unchecked u32 arithmetic:
c @ b'A'..=b'Z' => col = col * 26 + (c - b'A') as u32 + 1, // :2834
...
c @ b'0'..=b'9' => row = row * 10 + (c - b'0') as u32, // :2850
Neither is bounded, and the MAX_ROWS / MAX_COLUMNS checks in get_dimension (src/xlsx/mod.rs:2791-2796) are warn! only, so an out-of-range value is logged and then returned to the caller anyway.
Three separate outcomes, all from ~2 KB packages with a single real cell. calamine 0.36.0.
1. In range for u32, far out of range for a spreadsheet
<dimension ref="A1:ZZZZZZ1"/>:
start=(0, 0) end=(0, 321272405) (MAX_COLUMNS = 16384)
Same in debug and release. The warn! fires, then the value is handed back from dimensions(). That is roughly 19,600 times the grid width, and dimensions() is the natural pre-allocation hint for the incremental readers, so a caller that sizes a row buffer from it commits about 10 GB.
2. Column overflow
<dimension ref="A1:ZZZZZZZ1"/> (seven letters, 26^7 > u32::MAX):
debug: thread 'main' panicked at src/xlsx/mod.rs:2834:38:
attempt to multiply with overflow
release: start=(0, 0) end=(0, 4058115285)
3. Row overflow, and it is reachable from a cell reference
<dimension ref="A1:A99999999999"/> panics the same way at :2850. More importantly the same parser handles <c r="...">, so it is reachable while iterating cells rather than only when reading the declaration:
<dimension ref="A1:A5"/>
<sheetData>
<row r="1"><c r="A1"><v>1</v></c></row>
<row r="99999999999"><c r="A99999999999"><v>2</v></c></row>
</sheetData>
let mut r = wb.worksheet_cells_reader(&name)?;
while let Some(c) = r.next_cell()? { println!("{:?}", c.get_position()); }
debug: cell at (0, 0)
thread 'main' panicked at src/xlsx/mod.rs:2850:38:
attempt to multiply with overflow
release: cell at (0, 0)
cell at (1215752190, 0)
In release the reader reports a cell at row 1,215,752,190, which is not a position that appears anywhere in the file.
Why it compounds
A fabricated position like that is exactly what Range::from_sparse turns into an unbounded densification, so this feeds directly into #693: the two together mean a small file can produce a cell position no writer wrote, and then an allocation sized from it. Bounding the parse would close one of the two ways to reach that.
For what it is worth, Reference::validate (src/xlsx/mod.rs:3122-3145) already does exactly the right check with XlsxError::ColumnNumberOverflow and RowNumberOverflow, but it is only applied to formula references, not to <dimension> or <c r>.
Suggested fix
Use checked arithmetic in the accumulation loops and return XlsxError::ColumnNumberOverflow / RowNumberOverflow on overflow or on exceeding the existing constants, reusing the variants validate already defines. That turns all three cases above into a normal Result the caller can handle, instead of a debug panic, a wrapped value, or a silently out-of-range one.
Found while building the gRPC service in #688. Related: #693 (unbounded densification, which this can feed) and #692 (unchecked subtraction on a reversed ref). Happy to send a PR.
get_row_and_optional_column(src/xlsx/mod.rs:2825-2860) accumulates both components with uncheckedu32arithmetic:Neither is bounded, and the
MAX_ROWS/MAX_COLUMNSchecks inget_dimension(src/xlsx/mod.rs:2791-2796) arewarn!only, so an out-of-range value is logged and then returned to the caller anyway.Three separate outcomes, all from ~2 KB packages with a single real cell. calamine 0.36.0.
1. In range for
u32, far out of range for a spreadsheet<dimension ref="A1:ZZZZZZ1"/>:Same in debug and release. The
warn!fires, then the value is handed back fromdimensions(). That is roughly 19,600 times the grid width, anddimensions()is the natural pre-allocation hint for the incremental readers, so a caller that sizes a row buffer from it commits about 10 GB.2. Column overflow
<dimension ref="A1:ZZZZZZZ1"/>(seven letters, 26^7 >u32::MAX):3. Row overflow, and it is reachable from a cell reference
<dimension ref="A1:A99999999999"/>panics the same way at:2850. More importantly the same parser handles<c r="...">, so it is reachable while iterating cells rather than only when reading the declaration:In release the reader reports a cell at row 1,215,752,190, which is not a position that appears anywhere in the file.
Why it compounds
A fabricated position like that is exactly what
Range::from_sparseturns into an unbounded densification, so this feeds directly into #693: the two together mean a small file can produce a cell position no writer wrote, and then an allocation sized from it. Bounding the parse would close one of the two ways to reach that.For what it is worth,
Reference::validate(src/xlsx/mod.rs:3122-3145) already does exactly the right check withXlsxError::ColumnNumberOverflowandRowNumberOverflow, but it is only applied to formula references, not to<dimension>or<c r>.Suggested fix
Use checked arithmetic in the accumulation loops and return
XlsxError::ColumnNumberOverflow/RowNumberOverflowon overflow or on exceeding the existing constants, reusing the variantsvalidatealready defines. That turns all three cases above into a normalResultthe caller can handle, instead of a debug panic, a wrapped value, or a silently out-of-range one.Found while building the gRPC service in #688. Related: #693 (unbounded densification, which this can feed) and #692 (unchecked subtraction on a reversed
ref). Happy to send a PR.