CVE-2026-45525 AI-generated analysis
platform/packages/providers/MediaProvider, media metadata redaction (util/IsoInterface and util/XmpInterface on 14; util/XmpDataParser on 15+)
Makes MediaProvider's ISO/MP4 parser keep parsing when an XMP box is too large to load. It also makes the XMP redaction logic redact the whole XMP byte range, instead of nothing, when the XMP payload could not be loaded, so location metadata is no longer left unredacted for oversized XMP boxes.
Root cause
In IsoInterface's box parser, an XMP-carrying box (type BOX_XMP, or a BOX_UUID box with the XMP UUID) whose payload could not be loaded made the parser stop. On Android 14 the BOX_UUID / BOX_XMP branches returned null when len > Integer.MAX_VALUE or when the allocation threw OutOfMemoryError. On 15+ they returned false when allocateBuffer returned null (per the createXmpData code comment, for payloads above 1 MB). Because parsing stopped, no later box in the file was flattened, and the oversized box's own range was not available to the redaction logic either. In addition, the XMP redaction code treated a missing or empty XMP buffer as an empty document and returned zero redaction ranges. On 15+, createXmpData also reset xmpOffsets to an empty array when the buffer was null, and it fell back to BOX_XMP. Net effect: a file with an oversized XMP box went through redaction with no XMP ranges redacted, which could leave location data inside the XMP (and potentially in later boxes) readable. On 14 the pre-fix code also allocated and read the full payload of every uuid box regardless of its UUID, with no size cap below Integer.MAX_VALUE.
How the fix works
- IsoInterface.java (14, BOX_UUID / BOX_XMP branches of the box parser): adds MAX_XMP_SIZE_BYTES = 1 MiB and a public XMP_UUID constant. For uuid boxes, data is now allocated and read only when box.uuid equals XMP_UUID. If the payload exceeds 1 MiB, the parser logs 'Skipping abnormally large uuid box' and leaves box.data null instead of returning null. BOX_XMP gets the same treatment ('Skipping abnormally large xmp box'). OutOfMemoryError is still caught but no longer aborts parsing. The box, with its range, is still returned, and later boxes are parsed.
- XmpInterface.java (14) constructor and process(): the rawXmp parameter becomes @Nullable. If rawXmp is null and xmpOffsets has at least two entries, process() adds xmpOffsets[0] and xmpOffsets[1] to mRedactedRanges (redacting the whole XMP box range) and returns without XML parsing. The constructor sets mRedactedXmp to an empty byte[] if it is still null. The guard that assigns mRedactedXmp now also accepts a zero-length array.
- XmpInterface.java (14) EXIF path: the range from exif.getAttributeRange(TAG_XMP), which is [start, length] per the code comment, is converted to [start, start+length], or to an empty array if it is null or too short.
- 15/16/17 IsoInterface.java: in the XMP_UUID and BOX_XMP branches, `if (box.data == null) return false;` is replaced by `if (box.data != null) { Os.read(...) }`, so when allocateBuffer returns null, parsing no longer aborts.
- 15/16/17 XmpDataParser.java getRedactionRanges(XmpData): if mRawXmp.length == 0 and mXmpOffsets.length > 0, it returns the full mXmpOffsets array as the redaction ranges without parsing. createXmpData(IsoInterface) now falls back to BOX_XMP only when buf is null AND xmpOffsets is empty, and it no longer clears xmpOffsets when buf is null.
- Tests: a new resource tests/res/raw/gps_test_xmp_big.mp4 and a new test testContainer_IsoRedactionRanges_OversizedXmp. The test asserts that the oversized box is still in the flattened list, that the redaction ranges array has exactly two entries (one [start, end] pair), and that the redacted XMP buffer is empty.
Completeness
- Covers both XMP carriers in ISO BMFF (BOX_XMP, and uuid boxes with XMP_UUID) and the failure modes visible in the diffs (size cap and OutOfMemoryError on 14; allocateBuffer returning null on 15+).
- On 14 the whole-range branch in XmpInterface.process() adds only xmpOffsets[0] and xmpOffsets[1]. If xmpOffsets contained more than one range pair, only the first would be redacted. The 15+ fix returns the full mXmpOffsets array instead.
- On 14 the branch only takes effect if the caller (fromContainer) actually passes a null rawXmp together with the box ranges. fromContainer is not in the diff, so this depends on existing 14 code that the material does not show (the new test exercises it).
- The 14 fix does not include the 15+ createXmpData change that stops the fallback from an oversized uuid-XMP box to BOX_XMP. Whether 14's fromContainer has such a fallback is not shown.
- Whole-range redaction applies only when rawXmp is null or empty and the offsets are non-empty. XMP that loads but fails XML parsing is handled as before (an existing test expects an IOException in that case).
Behavior changes
- Android 14 gains a 1 MiB cap on XMP box payloads in IsoInterface (previously only Integer.MAX_VALUE or OutOfMemoryError stopped it). XMP boxes above 1 MiB are no longer read, and the test shows their entire byte range is flagged for redaction.
- Android 14: the payload of non-XMP uuid boxes is no longer allocated or read, and an oversized non-XMP uuid box no longer aborts parsing.
- An oversized XMP box no longer stops parsing of later boxes, so the ranges of later boxes are now available.
- On 14, XmpInterface.getRedactedXmp() returns an empty array when the XMP payload was skipped.
- 15+: createXmpData no longer falls back to BOX_XMP when a uuid-XMP box exists but was too large, and it keeps that box's offsets.
- 14: EXIF XMP offsets passed to process() are now [start, end] instead of [start, length].
Per major
- Android 14: Adapted fix (commit 93cf0194cff2 in android-security-14.0.0_r29). On 14, IsoInterface allocates the box buffers directly with new byte[] instead of using allocateBuffer. The fix adds MAX_XMP_SIZE_BYTES and XMP_UUID to IsoInterface, restricts uuid reads to the XMP UUID, and adds the null-rawXmp whole-range branch and the EXIF [start, end] mapping in XmpInterface. 7 of 8 lines of the patched region are present at that tag.
- Android 15: Adapted fix (commit ec0834680bfe in android-security-15.0.0_r17). IsoInterface replaces `return false` with a null check around Os.read. XmpDataParser.getRedactionRanges returns the full offsets when mRawXmp is empty. createXmpData keeps the offsets and skips the BOX_XMP fallback for an oversized uuid-XMP box.
- Android 16: Same changes as 15 (commit cedfc5086b8d in android-security-16.0.0_r8), applied to a different IsoInterface base (hunk at line 189 instead of 188).
- Android 17: Same fix as 16 (commit 21270fd56118 in android-security-17.0.0_r1); the diff is identical to the 16 one.
Unknowns
- The Android 14 XmpInterface.fromContainer, getBoxBytes and getBoxRanges code is not in the material, so how a skipped box's null data and ranges reach process() on 14 is inferred from the test, not shown.
- Whether getBoxRanges can return multiple range pairs for several XMP boxes (which matters because the 14 branch only redacts xmpOffsets[0..1]) is not shown.
- The allocateBuffer implementation and its exact limit on 15+ are not shown.
- Whether xmpOffsets[1] was used elsewhere in the 14 process() before the [start, end] change is not shown.
- Which MediaProvider entry points invoke these parsers, and how the fix is distributed beyond the security tags, is not shown.