CVE-2024-40673 AI-generated analysis
platform/libcore, ojluni native zip support (java.util.ZipFile, zip_util.c)
libcore's native ZipFile open path (ZIP_Put_In_Cache0 in ojluni/src/main/native/zip_util.c) now rejects any zip file whose first 4 bytes are neither a local file header signature (LOCSIG) nor an end-of-central-directory signature (ENDSIG). This aligns java.util.ZipFile with libziparchive except for empty archives.
Root cause
In ZIP_Put_In_Cache0, the code read the first 4 bytes of the file only to record whether a local file header signature was present (zip->locsig = LOCSIG_AT(errbuf)). It then kept opening the archive whatever the result. The commit message cites the zip specification (APPNOTE 4.3.6 / 4.3.1): a non-empty zip must begin with a local file header, and an empty one with the end-of-central-directory record. According to the commit, libziparchive enforces this but ZipFile's native parser (zip_util.c) did not. So files with an invalid header at offset zero were accepted by ZipFile and rejected by libziparchive.
How the fix works
- ojluni/src/main/native/zip_util.c:ZIP_Put_In_Cache0: the change goes inside the existing block that runs when readFullyAt(zfd, errbuf, 4, 0) succeeds, after zip->locsig is set. A new Android-changed block checks `!LOCSIG_AT(errbuf) && !ENDSIG_AT(errbuf)`. If neither signature is present, it sets *pmsg to "Entry at offset zero has invalid LFH signature." (when pmsg is non-NULL), calls ZFILE_Close(zfd) and freeZip(zip), and returns NULL. This happens before the IO_Lseek length check and the rest of the open processing.
- It is a single commit, and the diff has no Java-side changes. The commit message says tests were skipped in this branch because udc-dev uses ART module prebuilts but builds tests from source.
Completeness
- The check only inspects the 4 bytes at offset zero. It accepts any file that starts with LOCSIG or ENDSIG and does no further validation of the header.
- The check is inside `if (readFullyAt(...) != -1)`. If that read fails, the new rejection is skipped and the existing later length/error handling applies.
- Files starting with ENDSIG (empty archives) are still accepted, although the commit message says libziparchive rejects them. The commit explicitly describes the alignment as "modulo empty zip files".
- The check is only in ZIP_Put_In_Cache0. According to the index search, ZipFile_open consults ZIP_Get_From_Cache first. The material does not show how that cache path behaves.
Behavior changes
- Opening a file through java.util.ZipFile whose first 4 bytes are not LOCSIG or ENDSIG now fails, with the native error message "Entry at offset zero has invalid LFH signature."
- Files that have data before the first local file header at offset zero are now rejected by ZipFile.
- Empty archives (starting with the end-of-central-directory record) are still accepted.
Per major
- Android 14: Same fix: commit b17fd2f8fe46 was cherry-picked into android-security-14.0.0_r13 (Ignore-AOSP-First). All 4 lines of the patched region are present at android-security-14.0.0_r29.
- Android 15: Not affected according to the bulletin. The patched region (4/4 lines) is present in ojluni/src/main/native/zip_util.c at android-security-15.0.0_r17.
- Android 16: Not affected according to the bulletin. ojluni/src/main/native/zip_util.c is absent at android-security-16.0.0_r8.
- Android 17: Not affected according to the bulletin. ojluni/src/main/native/zip_util.c is absent at android-security-17.0.0_r1.
Unknowns
- The material does not show how the ZipFile/libziparchive parsing mismatch leads to the bulletin's RCE classification, or what the concrete trigger is.
- The referenced tests (CtsLibcoreTestCases, CtsLibcoreOjTestCases) are not in the diff.
- The material does not show whether or how the same change was applied to Android 12, 12L and 13, which the bulletin lists as affected.
- Why the file is absent in Android 16/17 is not shown.
- How the fix reaches devices that update ART through the mainline module, as opposed to the platform build, is not shown.
- How ZIP_Get_From_Cache entries are created and checked is not shown.