Skip to content

Fuzz/xmp merge coverage - #479

Open
DichenZhang1 wants to merge 11 commits into
mainfrom
fuzz/xmp-merge-coverage
Open

DichenZhang1 wants to merge 11 commits into
mainfrom
fuzz/xmp-merge-coverage

Conversation

@DichenZhang1

Copy link
Copy Markdown
Collaborator

fuzzer: add ultrahdr_xmp_fuzzer for the primary image XMP merge

The primary image XMP packet is untrusted input. When a gain map is
appended to an already compressed base image, jpegr.cpp inherits that
image's APP1 packet verbatim:

if (pXmp == nullptr && decoder.getXMPSize() > 0) {
  xmp_from_jpg.data = decoder.getXMPPtr();
  pXmp = &xmp_from_jpg;
}

and hands it to generateXmpForPrimaryImage(), which now parses the
packet, resolves namespace prefixes, locates and rewrites the encoder
owned gain map description by byte offset, and reserializes the result.
That is a few hundred lines of hand rolled XML span arithmetic reachable
from attacker controlled bytes, and none of the existing fuzz targets
reach it: ultrahdr_enc_fuzzer only ever feeds the encoder base images it
generated itself, whose XMP is well formed by construction.

Add a dedicated target that drives:

  • generateXmpForPrimaryImage() with an arbitrary user packet (and, for
    contrast, with no user packet at all),
  • getMetadataFromXMP() on the merged result, so the packet we emit has
    to survive our own reader,
  • getMetadataFromXMP() on the raw untrusted packet.

Wired into CMake, ossfuzz.sh and Android.bp alongside the existing
enc/dec/legacy targets. No new build options are required:
generateXmpForPrimaryImage() is compiled unconditionally, so the target
builds in the default OSS-Fuzz configuration.

Smoke tested locally against a stub FuzzedDataProvider (this host has no
clang) over well formed, truncated, fragmented and NUL bearing packets:
no crashes, and the target compiles clean under -Wall -Wextra -Werror.

DichenZhang1 and others added 11 commits September 12, 2026 03:56
- Expose uhdr_enc_set_xmp_data and uhdr_dec_get_xmp in public C API
- Support passing custom XMP metadata into encodeJPEGR and appendGainMap
- In ISO metadata mode, pass through user/base XMP packet directly
- In XMP metadata mode, merge Ultra HDR container directory into user/base XMP RDF
- Automatically extract and preserve base JPEG XMP in API-2/3/4 encoding workflows
- Wire -X <xmp_file> and probe XMP display into ultrahdr_app CLI
- Add unit tests verifying XMP encoding, decoding, and preservation across API-0 and API-2
- Support encoding and decoding Extended XMP metadata (> 64 KB) in JPEG
- Compute 128-bit MD5 GUID and inject xmpNote:HasExtendedXMP into Standard XMP
- Chunk serialized Extended XMP payload across multiple http://ns.adobe.com/xmp/extension/\0 APP1 segments
- Reassemble Extended XMP chunks during JPEG header parsing and expose reconstructed buffer
- Calculate dynamic encoder output buffer allowance based on total XMP size and chunk count
- Add unit tests validating multi-segment Extended XMP encoding, decoding, and bit-for-bit roundtrip integrity
The primary image XMP packet is untrusted input. When a gain map is
appended to an already compressed base image, jpegr.cpp inherits that
image's APP1 packet verbatim:

    if (pXmp == nullptr && decoder.getXMPSize() > 0) {
      xmp_from_jpg.data = decoder.getXMPPtr();
      pXmp = &xmp_from_jpg;
    }

and hands it to generateXmpForPrimaryImage(), which now parses the
packet, resolves namespace prefixes, locates and rewrites the encoder
owned gain map description by byte offset, and reserializes the result.
That is a few hundred lines of hand rolled XML span arithmetic reachable
from attacker controlled bytes, and none of the existing fuzz targets
reach it: ultrahdr_enc_fuzzer only ever feeds the encoder base images it
generated itself, whose XMP is well formed by construction.

Add a dedicated target that drives:

  * generateXmpForPrimaryImage() with an arbitrary user packet (and, for
    contrast, with no user packet at all),
  * getMetadataFromXMP() on the merged result, so the packet we emit has
    to survive our own reader,
  * getMetadataFromXMP() on the raw untrusted packet.

Wired into CMake, ossfuzz.sh and Android.bp alongside the existing
enc/dec/legacy targets. No new build options are required:
generateXmpForPrimaryImage() is compiled unconditionally, so the target
builds in the default OSS-Fuzz configuration.

Smoke tested locally against a stub FuzzedDataProvider (this host has no
clang) over well formed, truncated, fragmented and NUL bearing packets:
no crashes, and the target compiles clean under -Wall -Wextra -Werror.
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.

2 participants