Skip to content

fix(spreadsheet): close XLSX DTD pre-scan bypass via declared encodings - #39

Merged
matbrgz merged 2 commits into
masterfrom
fix/xlsx-dtd-encoding-bypass
Oct 12, 2026
Merged

matbrgz merged 2 commits into
masterfrom
fix/xlsx-dtd-encoding-bypass

Conversation

@matbrgz

@matbrgz matbrgz commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

O que mudou

Correção de segurança em namescommons.spreadsheet (leitor de XLSX): a pré-checagem de DTD decodificava o prólogo só como UTF-8/UTF-16, mas o expat respeita a codificação declarada na declaração XML e, via codecs do Python, aceita outras (cp037/EBCDIC, utf-32, latin-1 e demais de byte único). Um <!DOCTYPE/<!ENTITY escrito assim passava pela pré-checagem e chegava ao expat (diferencial de parser).

Manutenção, sem spec nova. Atualizados ADR 0005 (consequências) e spec 003b R1.4.

  • (a) A declaração encoding do XML só pode ser ausente, UTF-8 ou UTF-16 (sem diferenciar maiúsculas; hífen opcional); outra coisa é FormatError.
  • (b) Prefixo que não é UTF-8 nem UTF-16 válido (por BOM/NUL) é FormatError: UTF-32, EBCDIC, bytes nulos fora de UTF-16, UTF-8 inválido.
  • (c) Independente da pré-checagem, o próprio expat recusa DTD: StartDoctypeDeclHandler e EntityDeclHandler levantam FormatError com arquivo e membro.

Como foi verificado

  • 702 testes passam (uv run pytest -q); os dois passos offline da CI rodam e geram os CSVs esperados.
  • Novos testes, para workbook, rels, sharedStrings e aba: DOCTYPE em membro declarado cp037, utf-32 (e LE/BE), latin-1, iso-8859-1, windows-1252, us-ascii; prefixos EBCDIC/UTF-32/UTF-8 inválido sem declaração; DTD em UTF-8/UTF-16 com declaração válida; XLSX normal em UTF-8/UTF-16 ainda lido; handlers do expat recusam DTD e billion laughs com a pré-checagem desligada.
  • Os casos existentes (BOM, billion laughs, 300 mil linhas com memória plana < 5 MiB, fixtures do INE) seguem passando.

Decisão tomada sozinho

O ET.XMLParser em C (padrão no CPython 3.12) não expõe .parser (o expat), então não dá para instalar os handlers nele. O leitor passou a usar xml.parsers.expat direto com ET.TreeBuilder, gerando eventos start/end em blocos de 64 KiB (substitui ET.parse e ET.iterparse; mesmo tipo de elemento, mesma leitura em fluxo). Custo se estiver errada: ~60 linhas próprias no lugar de duas chamadas da biblioteca padrão; reverter é local ao módulo. Também: declaração utf8 (sem hífen) passa na pré-checagem, mas o expat não lê conteúdo multibyte com esse nome e o resultado é FormatError de XML ilegível, sem risco.

O mesmo código foi portado para BenchCommons/tools-dataset; a correção lá é feita à parte.

🤖 Generated with Claude Code

matbrgz and others added 2 commits October 12, 2026 15:30
The DTD pre-scan decoded the prolog as UTF-8/UTF-16 only, while expat
honours any declared encoding (cp037, utf-32, latin-1...), so a DOCTYPE
in such a member reached the parser. Reject prefixes that are not valid
UTF-8/UTF-16, reject XML declarations with other encodings, and make
expat itself raise FormatError on DOCTYPE/ENTITY declarations.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@matbrgz
matbrgz merged commit 5144518 into master Oct 12, 2026
1 check passed
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.

1 participant