fix(native): inspect Apple dispatch metadata in FAT64 binaries (#590)
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
This commit is contained in:
parent
b37e47c9f7
commit
f3e6582e9e
|
|
@ -117,3 +117,77 @@ describe("Apple dispatch binary metadata", () => {
|
|||
).toThrow("Truncated");
|
||||
});
|
||||
});
|
||||
|
||||
describe("universal Apple dispatch metadata", () => {
|
||||
it.each([false, true])("decodes selected FAT slices (fat64=%s)", (fat64) => {
|
||||
const thin = fixture();
|
||||
const offset = 256;
|
||||
const bytes = Buffer.alloc(offset + thin.length);
|
||||
bytes.writeUInt32BE(fat64 ? 0xcafebabf : 0xcafebabe, 0);
|
||||
bytes.writeUInt32BE(1, 4);
|
||||
bytes.writeUInt32BE(0x0100000c, 8);
|
||||
if (fat64) {
|
||||
bytes.writeBigUInt64BE(BigInt(offset), 16);
|
||||
bytes.writeBigUInt64BE(BigInt(thin.length), 24);
|
||||
} else {
|
||||
bytes.writeUInt32BE(offset, 16);
|
||||
bytes.writeUInt32BE(thin.length, 20);
|
||||
}
|
||||
thin.copy(bytes, offset);
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.objc_classes[0]).toMatchObject({
|
||||
name: "Fixture",
|
||||
location: { address: "0x100000200", file_offset: offset + 512 },
|
||||
});
|
||||
expect(result.objc_dispatch_implementations[0]).toMatchObject({
|
||||
implementation_address: "0x100000700",
|
||||
location: { file_offset: offset + 0x488 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("FAT64 dispatch slice validation", () => {
|
||||
const wrapped = () => {
|
||||
const thin = fixture();
|
||||
const bytes = Buffer.alloc(256 + thin.length);
|
||||
bytes.writeUInt32BE(0xcafebabf, 0);
|
||||
bytes.writeUInt32BE(1, 4);
|
||||
bytes.writeUInt32BE(0x0100000c, 8);
|
||||
bytes.writeBigUInt64BE(256n, 16);
|
||||
bytes.writeBigUInt64BE(BigInt(thin.length), 24);
|
||||
thin.copy(bytes, 256);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
it("rejects a truncated architecture table", () => {
|
||||
expect(() =>
|
||||
decodeAppleDispatchMetadata(wrapped().subarray(0, 32), 100, provenance),
|
||||
).toThrow("Malformed FAT architecture table");
|
||||
});
|
||||
|
||||
it.each([16, 24])(
|
||||
"checks the full 64-bit slice extent at field %i",
|
||||
(field) => {
|
||||
const bytes = wrapped();
|
||||
bytes.writeBigUInt64BE(0x100000000n, field);
|
||||
expect(() => decodeAppleDispatchMetadata(bytes, 100, provenance)).toThrow(
|
||||
"FAT slice exceeds file",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects ambiguous matching architectures", () => {
|
||||
const bytes = wrapped();
|
||||
bytes.writeUInt32BE(2, 4);
|
||||
bytes.copy(bytes, 40, 8, 40);
|
||||
expect(() => decodeAppleDispatchMetadata(bytes, 100, provenance)).toThrow(
|
||||
"Ambiguous FAT architecture slice",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a missing selected architecture", () => {
|
||||
expect(() =>
|
||||
decodeAppleDispatchMetadata(wrapped(), 100, provenance, "x86_64"),
|
||||
).toThrow("Requested FAT architecture is absent");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,30 +31,8 @@ export const decodeAppleDispatchMetadata = (
|
|||
provenance: { path: string; sha256: string },
|
||||
architecture = "arm64",
|
||||
): ObjcSwiftMetadata => {
|
||||
let slice = 0;
|
||||
let sliceEnd = bytes.length;
|
||||
if (bytes.length < 32) throw new RangeError("Truncated Mach-O header");
|
||||
if (bytes.readUInt32BE(0) === 0xcafebabe) {
|
||||
const count = bytes.readUInt32BE(4);
|
||||
if (count > 128 || 8 + count * 20 > bytes.length)
|
||||
throw new RangeError("Malformed FAT architecture table");
|
||||
const cpu = architecture === "arm64" ? 0x0100000c : 0x01000007;
|
||||
let selected: number | undefined;
|
||||
for (let index = 0; index < count; index++) {
|
||||
const offset = 8 + index * 20;
|
||||
if (bytes.readUInt32BE(offset) === cpu) {
|
||||
if (selected !== undefined)
|
||||
throw new TypeError("Ambiguous FAT architecture slice");
|
||||
selected = bytes.readUInt32BE(offset + 8);
|
||||
sliceEnd = selected + bytes.readUInt32BE(offset + 12);
|
||||
if (sliceEnd > bytes.length || selected < 8 + count * 20)
|
||||
throw new RangeError("FAT slice exceeds file");
|
||||
}
|
||||
}
|
||||
if (selected === undefined)
|
||||
throw new TypeError("Requested FAT architecture is absent");
|
||||
slice = selected;
|
||||
}
|
||||
const { slice, sliceEnd } = selectMachoSlice(bytes, architecture);
|
||||
if (slice + 32 > sliceEnd || bytes.readUInt32LE(slice) !== 0xfeedfacf)
|
||||
throw new TypeError(
|
||||
"Only little-endian 64-bit Mach-O metadata is supported",
|
||||
|
|
@ -672,3 +650,35 @@ export const inspectAppleDispatchMetadata = async (
|
|||
await handle.close();
|
||||
}
|
||||
};
|
||||
|
||||
const selectMachoSlice = (bytes: Buffer, architecture: string) => {
|
||||
const magic = bytes.readUInt32BE(0);
|
||||
if (magic !== 0xcafebabe && magic !== 0xcafebabf)
|
||||
return { slice: 0, sliceEnd: bytes.length };
|
||||
const fat64 = magic === 0xcafebabf;
|
||||
const stride = fat64 ? 32 : 20;
|
||||
const count = bytes.readUInt32BE(4);
|
||||
const headerEnd = 8 + count * stride;
|
||||
if (count > 128 || headerEnd > bytes.length)
|
||||
throw new RangeError("Malformed FAT architecture table");
|
||||
const cpu = architecture === "arm64" ? 0x0100000c : 0x01000007;
|
||||
let selected: { slice: number; sliceEnd: number } | undefined;
|
||||
for (let index = 0; index < count; index++) {
|
||||
const offset = 8 + index * stride;
|
||||
if (bytes.readUInt32BE(offset) !== cpu) continue;
|
||||
if (selected !== undefined)
|
||||
throw new TypeError("Ambiguous FAT architecture slice");
|
||||
const start = fat64
|
||||
? bytes.readBigUInt64BE(offset + 8)
|
||||
: BigInt(bytes.readUInt32BE(offset + 8));
|
||||
const size = fat64
|
||||
? bytes.readBigUInt64BE(offset + 16)
|
||||
: BigInt(bytes.readUInt32BE(offset + 12));
|
||||
if (start < BigInt(headerEnd) || start + size > BigInt(bytes.length))
|
||||
throw new RangeError("FAT slice exceeds file");
|
||||
selected = { slice: Number(start), sliceEnd: Number(start + size) };
|
||||
}
|
||||
if (selected === undefined)
|
||||
throw new TypeError("Requested FAT architecture is absent");
|
||||
return selected;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue