fix(apple): validate JVM interface identity

This commit is contained in:
morluto committed 2026-10-27 11:49:22 +08:00
1 parent c79bd41b5d
commit cd7a0eefd8
2 files changed
+42 -1

No files matched your search

@@ -12,6 +12,7 @@ import { javaClassFile } from "../../../tests/fixtures/javaClassFile.js";
import { classifyArtifactBytes } from "../inventory/ArtifactGraphConstruction.js";
import { createArtifactClassificationProbe } from "../ArtifactClassificationProbe.js";
import { parseExecutableHeader } from "../../domain/binaryTarget.js";
import { isJvmClassFile } from "../../domain/apple/jvmClassFile.js";
import {
classifyFatContainerIdentity,
classifyFatContainerEvidence,
@@ -313,6 +314,39 @@ describe("FAT Mach-O identity", () => {
});
describe("FAT slice header identity and bounds", () => {
const classWithInterface = (reference: number): Buffer => {
// Retain enough bytes for the coincident FAT table claim to be readable;
// otherwise rejecting JVM identity would still yield not-fat on truncation.
const source = javaClassFile(2048, true);
const interfaceCountOffset = source.length - 8;
const bytes = Buffer.concat([
source.subarray(0, interfaceCountOffset + 2),
Buffer.alloc(2),
source.subarray(interfaceCountOffset + 2),
]);
bytes.writeUInt16BE(1, interfaceCountOffset);
bytes.writeUInt16BE(reference, interfaceCountOffset + 2);
return bytes;
};
it("preserves JVM identity for class-tagged interface references", () => {
const bytes = classWithInterface(3);
expect(isJvmClassFile(bytes)).toBe(true);
expect(readFatContainerHeader(bytes, bytes.length)).toEqual({
state: "not-fat",
});
});
it.each([0, 2, 8, 65535])(
"does not suppress FAT examination for invalid JVM interface index %i",
(reference) => {
// Zero, a UTF-8 entry, and out-of-pool indices are not class references.
const bytes = classWithInterface(reference);
expect(isJvmClassFile(bytes)).toBe(false);
expect(readFatContainerHeader(bytes, bytes.length).state).toBe("parsed");
},
);
it("keeps Java CAFEBABE class files outside FAT and Mach-O identity", async () => {
const bytes = javaClassFile();
expect(readFatContainerHeader(bytes, bytes.byteLength)).toEqual({
+8 -1
View File
@@ -256,7 +256,14 @@ export const isJvmClassFile = (bytes: Uint8Array): boolean => {
)
return false;
const interfaces = cursor.u2();
if (interfaces === null || !cursor.skip(interfaces * 2)) return false;
if (interfaces === null) return false;
// JVMS 4.1: every interface index must name CONSTANT_Class_info, just
// like this_class and nonzero super_class. Framing alone is insufficient
// to establish JVM identity when the same magic also identifies FAT.
for (let index = 0; index < interfaces; index += 1) {
const reference = cursor.u2();
if (reference === null || !poolHasTag(pool, reference, 7)) return false;
}
if (!readMembers(cursor, pool) || !readMembers(cursor, pool)) return false;
const attributes = cursor.u2();
return (