Skip to content

fix(xmp): audit - bound decode recursion and growth, escape encoder output - #5405

Open
lgritz wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
lgritz:lg-xmpaudit
Open

fix(xmp): audit - bound decode recursion and growth, escape encoder output#5405
lgritz wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
lgritz:lg-xmpaudit

Conversation

@lgritz

@lgritz lgritz commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

The shared XMP decoder is fed file-controlled bytes from jpeg, png, tiff, psd, and heif. Doing a Claude-assisted audit of this code found and fixed three issues:

  • Recursion depth was tracked but never enforced, so a deeply nested XMP packet (easy to make tiny via a compressed png chunk) could crash the decoder with a stack overflow. Depth is now capped at 64.

  • Building up a spec's attribute list was quadratic: each new list item re-read and re-wrote the entire accumulated value. A crafted file with a large attribute list could burn seconds of CPU and gigabytes of memory. Added a budget capping attribute count and total attribute payload size per spec.

  • The XMP encoder wrote attribute values straight into XML with no escaping, so a value read from an untrusted file could break out of its attribute and forge others, or produce malformed XML. Values are now escaped.

Added tests covering all three cases (and Python code to generate certain malformed files).

Note: currently, decode_xmp() always returns true, so these caps truncate silently instead of surfacing an error.

Assisted-by: Claude Code / claude-opus-5

The shared XMP decoder is fed file-controlled bytes from jpeg, png,
tiff, psd, and heif. Three issues fixed:

- Recursion depth was tracked but never enforced, so a deeply nested
  XMP packet (easy to make tiny via a compressed png chunk) could
  crash the decoder with a stack overflow. Depth is now capped at 64
  levels.

- Building up a spec's attribute list was quadratic: each new list
  item re-read and re-wrote the entire accumulated value. A crafted
  file with a large attribute list could burn seconds of CPU and
  gigabytes of memory. Added a budget capping attribute count and
  total attribute payload size per spec.

- The XMP encoder wrote attribute values straight into XML with no
  escaping, so a value read from an untrusted file could break out of
  its attribute and forge others, or produce malformed XML. Values
  are now escaped.

Added fixtures covering all three cases.

Known gap left as follow-up: decode_xmp() always returns true, so
these caps truncate silently instead of surfacing an error.

Assisted-by: Claude Code / claude-opus-5
Signed-off-by: Larry Gritz <lg@larrygritz.com>
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