mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
refactor(macho): share header declarations across native readers
This commit is contained in:
1 parent
c11fb48c98
commit
ed7b3a2a10
3 files changed
+50
-20
No files matched your search
@@ -10,8 +10,8 @@ export interface MachoLoadCommandTableHeader {
|
||||
readonly commandBytes: number;
|
||||
}
|
||||
|
||||
/** Read only the thin header's load-command framing; other formats return null. */
|
||||
export const readMachoLoadCommandTableHeader = (
|
||||
/** Decode thin-header declarations without admitting their table extent. */
|
||||
export const readMachoLoadCommandDeclarations = (
|
||||
bytes: Uint8Array,
|
||||
fileSize: number,
|
||||
): Result<MachoLoadCommandTableHeader | null, string> => {
|
||||
@@ -28,10 +28,6 @@ export const readMachoLoadCommandTableHeader = (
|
||||
);
|
||||
const littleEndian = magic === 0xcefaedfe || magic === 0xcffaedfe;
|
||||
const commandBytes = view.getUint32(20, littleEndian);
|
||||
if (commandBytes > fileSize - headerSize)
|
||||
return err(
|
||||
`truncated Mach-O load commands: the header declares ${commandBytes} bytes after its ${headerSize}-byte header; the file has ${fileSize}`,
|
||||
);
|
||||
return ok({
|
||||
headerSize,
|
||||
wide,
|
||||
@@ -41,6 +37,21 @@ export const readMachoLoadCommandTableHeader = (
|
||||
});
|
||||
};
|
||||
|
||||
/** Admit the declared command table within the complete file's extent. */
|
||||
export const readMachoLoadCommandTableHeader = (
|
||||
bytes: Uint8Array,
|
||||
fileSize: number,
|
||||
): Result<MachoLoadCommandTableHeader | null, string> => {
|
||||
const parsed = readMachoLoadCommandDeclarations(bytes, fileSize);
|
||||
if (!parsed.ok || parsed.value === null) return parsed;
|
||||
const { commandBytes, headerSize } = parsed.value;
|
||||
if (commandBytes > fileSize - headerSize)
|
||||
return err(
|
||||
`truncated Mach-O load commands: the header declares ${commandBytes} bytes after its ${headerSize}-byte header; the file has ${fileSize}`,
|
||||
);
|
||||
return parsed;
|
||||
};
|
||||
|
||||
/** One owner's count, alignment and extent proof for a sequential command walk. */
|
||||
export class MachoLoadCommandCursor {
|
||||
#index = 0;
|
||||
|
||||
@@ -8,18 +8,21 @@ import {
|
||||
type LoadRegion,
|
||||
type ZeroFillMapping,
|
||||
} from "./BinaryContainer.js";
|
||||
import { MachoLoadCommandCursor } from "../../domain/apple/machoLoadCommands.js";
|
||||
import {
|
||||
MachoLoadCommandCursor,
|
||||
readMachoLoadCommandDeclarations,
|
||||
} from "../../domain/apple/machoLoadCommands.js";
|
||||
|
||||
/** Read thin Mach-O images, retaining the slice's exact file-backed mappings. */
|
||||
export const readMachoImage = (bytes: Buffer): BinaryContainer => {
|
||||
const magic = bytes.readUInt32BE(0);
|
||||
const little = magic === 0xcefaedfe || magic === 0xcffaedfe;
|
||||
const bits = magic === 0xfeedfacf || magic === 0xcffaedfe ? 64 : 32;
|
||||
const parsed = readMachoLoadCommandDeclarations(bytes, bytes.length);
|
||||
if (!parsed.ok) throw new BinaryFormatFailure("malformed", parsed.error);
|
||||
if (parsed.value === null)
|
||||
throw new BinaryFormatFailure("malformed", "Unrecognized Mach-O header");
|
||||
const table = parsed.value;
|
||||
const { headerSize, commandBytes, littleEndian: little } = table;
|
||||
const bits = table.wide ? 64 : 32;
|
||||
const reader = new BinaryReader(bytes, little);
|
||||
const headerSize = bits === 64 ? 32 : 28;
|
||||
reader.range(0, headerSize, "Mach-O header");
|
||||
const count = reader.u32(16);
|
||||
const commandBytes = reader.u32(20);
|
||||
const commandStart = reader.table(
|
||||
BigInt(headerSize),
|
||||
commandBytes,
|
||||
@@ -32,12 +35,7 @@ export const readMachoImage = (bytes: Buffer): BinaryContainer => {
|
||||
const zeroFills: ZeroFillMapping[] = [];
|
||||
const sections: ImageSection[] = [];
|
||||
let chainedFixups = false;
|
||||
const commands = new MachoLoadCommandCursor({
|
||||
wide: bits === 64,
|
||||
littleEndian: little,
|
||||
commandCount: count,
|
||||
commandBytes,
|
||||
});
|
||||
const commands = new MachoLoadCommandCursor(table);
|
||||
while (commands.nextOffset !== null) {
|
||||
const offset = commandStart + commands.nextOffset;
|
||||
const frame = commands.read(bytes.subarray(offset, offset + 8));
|
||||
|
||||
@@ -2,6 +2,9 @@ import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { BinaryResourceFailure } from "../../../src/native/metadata/BinaryContainer.js";
|
||||
import { readMachoLoadCommandTableHeader } from "../../../src/domain/apple/machoLoadCommands.js";
|
||||
import { readBinaryContainer } from "../../../src/native/metadata/readBinaryContainer.js";
|
||||
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
|
||||
import { thinMach } from "../../../src/domain/binaryTarget.fixture.js";
|
||||
import { parseExecutableHeader } from "../../../src/domain/binaryTarget.js";
|
||||
@@ -46,6 +49,7 @@ it.each([
|
||||
{ label: "undersized", field: 36, value: 4 },
|
||||
{ label: "unaligned", field: 36, value: 12 },
|
||||
{ label: "outside table", field: 36, value: 0xffffffff },
|
||||
{ label: "truncated declared table", field: 20, value: 24 },
|
||||
{ label: "too many commands", field: 16, value: 2 },
|
||||
{ label: "unclaimed table bytes", field: 16, value: 0 },
|
||||
{ label: "trailing table bytes", field: 36, value: 8 },
|
||||
@@ -60,6 +64,7 @@ it.each([
|
||||
);
|
||||
await writeFile(path, bytes);
|
||||
expect(parseExecutableHeader(bytes, "arm64").ok).toBe(false);
|
||||
expect(() => readBinaryContainer(bytes)).toThrow(/load command/iu);
|
||||
const resolved = await parseBinaryTarget(path);
|
||||
expect(resolved.ok).toBe(false);
|
||||
if (resolved.ok || !(resolved.error instanceof BinaryTargetError))
|
||||
@@ -86,6 +91,11 @@ it.each([
|
||||
"image",
|
||||
);
|
||||
await writeFile(path, bytes);
|
||||
expect(readBinaryContainer(bytes)).toMatchObject({
|
||||
format: "macho",
|
||||
bits: wide ? 64 : 32,
|
||||
byte_order: littleEndian ? "little" : "big",
|
||||
});
|
||||
expect(await parseBinaryTarget(path)).toMatchObject({
|
||||
ok: true,
|
||||
value: { format: "mach-o", architecture: wide ? "arm64" : "x86" },
|
||||
@@ -150,3 +160,14 @@ it.each([null, 0, 1])(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves native capacity priority when the command table also exceeds the file", () => {
|
||||
const bytes = commandImage(true, true, [16]);
|
||||
bytes.writeUInt32LE(16 * 1024 * 1024 + 1, 20);
|
||||
expect(() => readBinaryContainer(bytes)).toThrow(BinaryResourceFailure);
|
||||
const bounded = readMachoLoadCommandTableHeader(bytes, bytes.length);
|
||||
expect(bounded).toMatchObject({
|
||||
ok: false,
|
||||
error: expect.stringContaining("truncated Mach-O load commands"),
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user