fix(go): classify JVM class files instead of universal Mach-O for CAFEBABE bytes (#2458)
* test(go): pin JVM class files as unsupported Go images
The regression fails on main: readGoBinaryImage(javaClassFile()) reports
"Universal/fat Mach-O containers require selecting a thin slice" for a
JVM class file whose bytes 4..7 are minor/major version, not nfat_arch.
* fix(go): classify JVM class files instead of universal Mach-O
Route the four FAT magics through the shared readFatContainerHeader and
classifyFatContainerIdentity facts added for the same disambiguation in
the artifact and target lanes (921bb9575). Keep the thin-slice failure
only when the container identity is confirmed Mach-O; a JVM class file
now falls through to the generic only-ELF/PE/thin-Mach-O failure.
* test(go): pin universal Mach-O thin-slice failure with FAT fixtures
Cover the kept behavior with the repository's own fatImage/machoImage
builders (narrow and FAT64 tables) so the regression also proves real
universal containers keep the slice-selection failure.
* test(go): cover little-endian FAT magics in the universal container control
* fix(go): require positive universal Mach-O identity
Require positive Mach-O identity before reporting a universal thin-slice remedy; malformed FAT-shaped bytes are not proof. Add the JVM constant-pool CPU lookalike to the existing fixture control.
Source inspection and git diff --check; tests/builds/typechecks/CI reruns skipped at explicit user request.
* fix(go): retain malformed FAT header diagnostics
Preserve the owning FAT header parser reason for malformed headers while requiring positive Mach-O proof before the universal-slice remedy.
Source inspection and git diff --check only. Tests, builds and typechecks skipped by explicit user request. GitHub review writes paused globally.
---------
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
parent
003707f712
commit
48b92e1be1
|
|
@ -8,6 +8,10 @@ import {
|
|||
type GoBinaryContainer,
|
||||
type GoFileMapping,
|
||||
} from "./GoBinaryContainer.js";
|
||||
import {
|
||||
classifyFatContainerIdentity,
|
||||
readFatContainerHeader,
|
||||
} from "../domain/apple/machoContainer.js";
|
||||
import { readGoElfImage } from "./GoElfImage.js";
|
||||
import { readGoMachoImage } from "./GoMachoImage.js";
|
||||
import { readGoPeImage } from "./GoPeImage.js";
|
||||
|
|
@ -187,11 +191,27 @@ const readContainer = (bytes: Buffer): GoBinaryContainer => {
|
|||
return readGoPeImage(bytes);
|
||||
if (bytes.length >= 4) {
|
||||
const magic = bytes.readUInt32BE();
|
||||
if ([0xcafebabe, 0xcafebabf, 0xbebafeca, 0xbfbafeca].includes(magic))
|
||||
throw new GoBinaryFormatFailure(
|
||||
"unsupported",
|
||||
"Universal/fat Mach-O containers require selecting a thin slice",
|
||||
);
|
||||
if ([0xcafebabe, 0xcafebabf, 0xbebafeca, 0xbfbafeca].includes(magic)) {
|
||||
const fatHeader = readFatContainerHeader(bytes, bytes.length);
|
||||
if (fatHeader.state === "malformed")
|
||||
throw new GoBinaryFormatFailure("malformed", fatHeader.reason);
|
||||
const identity =
|
||||
fatHeader.state === "parsed"
|
||||
? classifyFatContainerIdentity(fatHeader, (offset) => {
|
||||
if (offset >= bytes.length) return undefined;
|
||||
if (offset + 4 > bytes.length) return null;
|
||||
return bytes.readUInt32BE(offset);
|
||||
})
|
||||
: undefined;
|
||||
// An invalid FAT-shaped table is not proof of a Mach-O container: JVM
|
||||
// constant-pool bytes can share both the magic and a known CPU value.
|
||||
const confirmedFatMachO = identity?.state === "mach-o";
|
||||
if (confirmedFatMachO)
|
||||
throw new GoBinaryFormatFailure(
|
||||
"unsupported",
|
||||
"Universal/fat Mach-O containers require selecting a thin slice",
|
||||
);
|
||||
}
|
||||
if ([0xfeedface, 0xfeedfacf, 0xcefaedfe, 0xcffaedfe].includes(magic))
|
||||
return readGoMachoImage(bytes);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import { expect, it } from "vitest";
|
||||
import { javaClassFile } from "../../fixtures/javaClassFile.js";
|
||||
import {
|
||||
CPU,
|
||||
fatImage,
|
||||
machoImage,
|
||||
} from "../../../src/artifacts/apple/MachoImage.fixture.js";
|
||||
import { readGoBinaryImage } from "../../../src/go/GoBinaryImage.js";
|
||||
import {
|
||||
createGoBinaryFixture,
|
||||
|
|
@ -221,6 +227,35 @@ it("distinguishes unsupported containers from malformed recognized images", () =
|
|||
expect(() => readGoBinaryImage(pe.bytes)).toThrowError(/range|truncated/i);
|
||||
});
|
||||
|
||||
it("reports JVM class files as unsupported images instead of fat Mach-O containers", () => {
|
||||
expect(() => readGoBinaryImage(javaClassFile())).toThrowError(
|
||||
/Only ELF, PE, and thin Mach-O native images are supported/,
|
||||
);
|
||||
expect(() => readGoBinaryImage(javaClassFile(4000))).toThrowError(
|
||||
/Only ELF, PE, and thin Mach-O native images are supported/,
|
||||
);
|
||||
expect(() => readGoBinaryImage(javaClassFile(4000, true))).toThrowError(
|
||||
/Only ELF, PE, and thin Mach-O native images are supported/,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the thin-slice failure for confirmed universal Mach-O containers", () => {
|
||||
const slices = [
|
||||
{ cpu: CPU.x86_64, bytes: machoImage({ cpu: CPU.x86_64 }) },
|
||||
{ cpu: CPU.arm64, bytes: machoImage({ cpu: CPU.arm64 }) },
|
||||
];
|
||||
for (const universal of [
|
||||
fatImage(slices),
|
||||
fatImage(slices, true),
|
||||
fatImage(slices, false, true),
|
||||
fatImage(slices, true, true),
|
||||
]) {
|
||||
expect(() => readGoBinaryImage(Buffer.from(universal))).toThrowError(
|
||||
/fat Mach-O containers require selecting a thin slice/,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects ELF header tables overlapping the native header", () => {
|
||||
const fixture = createGoBinaryFixture({ sectionless: true });
|
||||
fixture.bytes.writeBigUInt64LE(0n, 32);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/** Build a valid minimal JVM class file, optionally with an unused UTF-8 constant. */
|
||||
export const javaClassFile = (extraUtf8Bytes = 0): Buffer => {
|
||||
export const javaClassFile = (extraUtf8Bytes = 0, fatCpuLookalike = false): Buffer => {
|
||||
const utf8 = (value: string): Buffer => {
|
||||
const bytes = Buffer.from(value, "utf8");
|
||||
const entry = Buffer.alloc(3 + bytes.length);
|
||||
|
|
@ -13,6 +13,7 @@ export const javaClassFile = (extraUtf8Bytes = 0): Buffer => {
|
|||
utf8("Hello"),
|
||||
Buffer.from([7, 0, 4]),
|
||||
utf8("java/lang/Object"),
|
||||
...(fatCpuLookalike ? [utf8("x"), Buffer.from([3, 0, 0, 0, 7])] : []),
|
||||
...(extraUtf8Bytes === 0 ? [] : [utf8("x".repeat(extraUtf8Bytes))]),
|
||||
];
|
||||
const headerAndPool = Buffer.alloc(10);
|
||||
|
|
|
|||
Loading…
Reference in New Issue