Skip to content

v0.6.0 — bound what a package can ask for

Choose a tag to compare

@tannevaled tannevaled released this 09 Oct 13:32
· 2 commits to main since this release
780b61c

A security release. Behaviour changes: packages this reader previously
accepted are now refused, which is why it is a minor rather than a patch.

An ODF package is a ZIP, and a ZIP says how big it will be once opened.
What it says is a number in a file.

input before after
an .ods of 67 206 bytes whose content.xml inflates to 64 MiB 222.9 MiB held, accepted 0.0 MiB, refused by its declared size
a 65 851 byte package of 200 000 nested tables 115.5 MiB, 296 ms, accepted 18.0 MiB, 7 ms, refused

Four ceilings, bounding four different things

any one entry 64 MiB refused from its declared size, before a byte is inflated
the package 192 MiB one entry under the ceiling says nothing about a thousand
entries 8 192 the count decides an allocation made before anything is read
nesting 256 no size ceiling can see it: the bytes are small, the shape costs

For scale, OpenSSL's own lifecycles.ods — six sheets, every row padded to
1024 columns — has a 96 453 byte content.xml. These leave it six hundred
times its size.

⛔ Where a guard goes is not where the code recurses

The nesting bound was first written where this package recurses. It caught
nothing: a table inside a table cell never reaches parseBlock, because
parseCell hands it to the decoder's Skip. The memory was never in our
document tree — it was in encoding/xml's stack of open elements.

⛔ Two guards were removed rather than kept

A check on the bytes actually read is unreachable beside the declared-size
check: archive/zip makes the declaration binding. An unreachable guard is not
defence in depth; it is a claim nobody has tested. The dependency that replaces
it is pinned by a test.

Measured and found NOT to be risks

encoding/xml expands no custom entities and fetches no external ones, so
billion laughs and <!ENTITY x SYSTEM "file:///etc/passwd"> are refused by
the decoder itself — measured in both directions rather than assumed.

100 % statement coverage, -race green, ten build targets.