Skip to content

fix(xls): decode PtgStr as a ByteString before BIFF8 - #703

Open
TaoGuerreiro wants to merge 1 commit into
tafia:masterfrom
ChalkyOrg:fix/xls-biff5-ptgstr
Open

fix(xls): decode PtgStr as a ByteString before BIFF8#703
TaoGuerreiro wants to merge 1 commit into
tafia:masterfrom
ChalkyOrg:fix/xls-biff5-ptgstr

Conversation

@TaoGuerreiro

Copy link
Copy Markdown

Problem

Opening some BIFF5 (Excel 5.0/95) workbooks panics instead of returning an error:

thread panicked at src/xls.rs:1247:28:
range end index 11 out of range for slice of length 11
   calamine::xls::read_unicode_string_no_cch
   calamine::xls::parse_formula
   calamine::xls::Xls<RS>::parse_workbook
   calamine::open_workbook

Because parse_workbook parses formulas while loading each sheet, the panic escapes open_workbook and the whole file becomes unreadable — even though the call site already tolerates formula errors through unwrap_or_else.

Cause

A string literal in a formula (PtgStr, [MS-XLS] 2.5.198.89) is a ShortXLUnicodeString in BIFF8 — cch, a 1-byte grbit, then the characters — but a ShortByteString before BIFF8: cch immediately followed by cch bytes, with no grbit.

parse_formula applies the BIFF8 layout unconditionally, although it already computes is_pre_biff8 a few lines above. On a BIFF5 stream it therefore takes the first character as the grbit, reads one byte past the literal, and advances the cursor by 2 + cch instead of 1 + cch. When the literal ends the rgce, the slice overruns and panics.

Actual bytes from an affected file, an 11-character literal ending the token stream:

17 0b 32 30 31 39 30 30 32 34 38 37 39     (13 bytes)
│  │  └─ the 11 characters, no grbit
│  └──── cch = 11
└─────── PtgStr

The reader asks for 12 bytes after cch where only 11 exist — hence range end index 11 out of range for slice of length 11.

This shows up on BIFF5 exports whose cells hold string formulas rather than plain values (the file that surfaced this has ~524k Formula records for 9536 rows — every cell is ="..."). A BIFF5 file of plain values never reaches this code.

Fix

Decode the pre-BIFF8 layout as a byte string with high_byte = None, mirroring what parse_string already does for Biff2..Biff5, and bound-check both branches so a truncated literal returns XlsError::Len rather than panicking.

Tests

Three unit tests over parse_formula: a BIFF5 literal ending the rgce (panicked before this change), the BIFF8 layout with its grbit (unchanged behaviour), and a truncated literal that must be an error and not a panic. The existing suite passes unchanged; cargo fmt --check and cargo clippy --all-targets are clean.

The file that surfaced this contains personal data so it is not attached, but I can produce a reduced fixture if you would like one in tests/.

A string literal inside a formula is a ShortXLUnicodeString in BIFF8
(cch, a 1-byte grbit, then the characters) but a ShortByteString in
BIFF2-5 (cch immediately followed by cch bytes, no grbit).

`parse_formula` applied the BIFF8 layout unconditionally, so on a BIFF5
workbook it treated the first character as the grbit, then read one byte
past the literal and advanced the cursor by `2 + cch` instead of
`1 + cch`. When the literal ends the rgce this panics:

    range end index 11 out of range for slice of length 11
    at xls.rs read_unicode_string_no_cch <- parse_formula

Since `parse_workbook` calls `parse_formula` while loading each sheet,
the panic escapes `open_workbook`, making such files unreadable — even
though the caller already tolerates formula errors via `unwrap_or_else`.

Real-world trigger: BIFF5 exports whose cells are string formulas rather
than plain values (e.g. federation exports where every cell is ="...").

Decode the pre-BIFF8 layout as a byte string, matching what
`parse_string` already does for BIFF2-5, and bound-check both branches
so a truncated literal is an error rather than a panic.
@jmcnamara

Copy link
Copy Markdown
Collaborator

I think this is fixing the same issue as #521

Could you evaluate and confirm that.

@jmcnamara

Copy link
Copy Markdown
Collaborator

@TaoGuerreiro Ping on the open question above.

@jmcnamara

Copy link
Copy Markdown
Collaborator

@TaoGuerreiro Ping once more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants