Skip to content

feat(hwp): resource limits and fuzzing for the document readers - #686

Merged
KnifeLemon merged 1 commit into
mainfrom
engine/resource-limits
Oct 27, 2026
Merged

KnifeLemon merged 1 commit into
mainfrom
engine/resource-limits

Conversation

@KnifeLemon

Copy link
Copy Markdown
Owner

What does this change?

Project 4 item "Layout engine: resource limits and fuzzing for readers and layout" (M0). Untrusted documents can no longer hang, exhaust the memory or crash Filee: the HWPX, DOCX and OFD readers now share one set of limits (Documents/DocumentLimits.cs) that the HWP 5.0 reader and the layout entry point will use too, and a mutation fuzzer checks the readers on every test run and for a few minutes every night.

Limits (a document that passes one stops with the new localized error.document_too_complex, the detail says which):

  • Package (Documents/DocumentPackage.cs, built from what OfdPackage already did; OFD now uses it too): at most 100,000 parts; a part may unpack to 64 MB, all parts to 1 GB, counted while unpacking (a header that understates a size doesn't help), and a part over 4 MB to at most 200× its packed size. Parts are only found by name inside the package; extracted pictures get names Filee makes (an entry image.png:evil no longer becomes an alternate data stream).
  • XML (Infrastructure/SafeXml.cs, the tree builder GeoXml had, now shared with KML/GPX): no DTD, no resolver, at most 512 levels and 5,000,000 elements per package. Before, HWPX and DOCX used XDocument.Load with DTD parsing on and no depth limit.
  • Document (Documents/DocumentBudget.cs): tables in tables, text boxes, notes, groups and fields nest at most 32 levels; 1,000,000 paragraphs; 200,000 tables / pictures / drawing objects; table columns cut at 16,384 and row spans at the table's end; Word style chains followed 20 deep.
  • Pictures (Documents/PictureSize.cs): a PNG / JPEG / GIF / BMP / WebP whose header claims more than 16,384 × 16,384 pixels is left out before anything decodes it.
  • Layout (Documents/LayoutGuard.cs, for the layout engine; wiring comes with the layout items): 20,000 pages, 64 empty pages in a row, a 10-minute budget, cancellation and time checked per paragraph, and an object larger than the page is clipped and placed.
  • Embedded fonts: no reader reads them (DOCX word/fonts, HWPX / OFD font files keep only their names); documented, with a test.

Fuzzing: DocumentFuzzer mutates packages written in code (the 한글-style package from HwpxReaderTests, a rich DOCX built here, and both written by Filee's own writers): attributes set to extreme or foreign values, elements removed / repeated / renamed / nested, text and bytes damaged, DTDs inserted, pictures given huge headers. A reader may only read a file or throw InvalidDataException / ConversionRefusedException, within 30 s, on a 1 MB stack. DocumentFuzzTests runs 50 inputs per seed with a fixed seed in the normal test run (about a second per reader, so PR CI timing stays the same) and 3 minutes per reader with a new seed in the new nightly .github/workflows/fuzz.yml (ubuntu, 25-minute timeout; failing inputs are uploaded; FILEE_FUZZ_SEED repeats a run).

Bugs found and fixed (each with a regression test in DocumentHostileInputTests):

  • Fuzzer, on main: HWPX colCnt="2147483647" → OutOfMemoryException; HWPX cell address + span overflowing → ArgumentOutOfRangeException in ColumnWidths; DOCX duplicate abstractNumId → ArgumentException; DOCX page size -1e308 → OverflowException (Math.Abs(int.MinValue)); DOCX damaged XML → raw XmlException.
  • Found while adding the limits: a DOCX footnote that refers to itself recursed until the stack overflowed (process crash); a 100,000-style basedOn chain did the same; unclosed fields made every run / paragraph walk all of them (and HWPX nested links as deep as the fields); HWPX column widths overflowed their checked sum; a huge rowSpan made writers loop billions of times; VML line ends at the end of the range overflowed Math.Abs.

Left for later items: a cyclic-CFB test comes with the HWP 5.0 reader (Filee has no CFB reader yet; the limits note what it must check), wiring LayoutGuard into the layout loop, and fuzzing the HWP reader and the layout entry point (the harness takes any Action<string, string> reader). The PPTX reader is not covered here.

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format --verify-no-changes
  • dotnet test (Filee.Engines.Tests, Filee.Core.Tests, LocalizationTests)
  • Tried it in the app (what you converted or clicked, and on which OS):

Fuzzing: 150 s per reader with the fixed code (~8,000 inputs each, no failures); the same harness on main found the five fuzzer bugs above within two minutes.

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none)
  • The PR does one thing (unrelated changes go in a separate PR)

🤖 Generated with Claude Code

Untrusted HWPX, DOCX and OFD files can no longer hang, exhaust the memory or
crash Filee. The readers share Documents/DocumentLimits, which the HWP 5.0
reader and the layout engine will use too: packages are read by
DocumentPackage (64 MB per part, 1 GB in all, counted while unpacking, an
inflate ratio, 100,000 parts), XML by SafeXml (no DTD or resolver, 512 levels,
5 million elements; the tree builder GeoXml had, now shared), nesting of
tables, text boxes, notes, groups and fields stops at 32 levels, paragraphs
and objects are counted, and pictures whose header claims more than
16,384 x 16,384 pixels are left out. LayoutGuard holds the page, empty-page,
time and cancellation checks and the clip-and-place rule for the layout loop.
Passing a limit stops with the new error.document_too_complex.

DocumentFuzzer mutates packages written in code; DocumentFuzzTests runs it
briefly with every test run and for three minutes per reader every night
(.github/workflows/fuzz.yml). Fixed with regression tests: HWPX column counts
and spans at the end of the int range (OutOfMemoryException,
ArgumentOutOfRangeException, overflowing width sums, endless row spans), DOCX
duplicate numbering ids, page sizes that overflowed Math.Abs, raw
XmlException from damaged DOCX parts, a footnote that refers to itself and
long basedOn chains (stack overflows), and unclosed fields that slowed every
run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feature New features label Oct 27, 2026
@KnifeLemon
KnifeLemon merged commit a34e747 into main Oct 27, 2026
11 checks passed
KnifeLemon added a commit that referenced this pull request Oct 28, 2026
Revert #686 and #678: Project 4 work moves to the builtin-engines branch
KnifeLemon added a commit that referenced this pull request Oct 28, 2026
Reverts e081cec, the main-side revert from #688.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
andin1st pushed a commit to andin1st/Filee that referenced this pull request Oct 28, 2026
…urce-limits"

This reverts commit a34e747, reversing
changes made to 8db117a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant