Target admission accepted a thin Mach-O after reading only its magic and CPU type, so a 12-byte prefix opened as a valid arm64 executable and the first provider query failed as a generic execution failure. ELF headers were likewise accepted after 20 bytes, and a FAT file whose selected slice lies beyond its end was admitted. Require the complete fixed header (Mach-O 28 or 32 bytes, ELF 52 or 64), Mach-O load commands that fit in the file, and a selected FAT slice within the file, checking commitments against the file size rather than the probed prefix. Each refusal names the failed constraint. Fixes #1189 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
/** Build a minimal ELF header for binary-target parser tests. */
|
|
export const elf = (
|
|
class_: number,
|
|
endian: number,
|
|
machine: number,
|
|
): Buffer => {
|
|
const bytes = Buffer.alloc(class_ === 1 ? 52 : 64);
|
|
bytes.set([0x7f, 0x45, 0x4c, 0x46, class_, endian]);
|
|
if (endian === 1) bytes.writeUInt16LE(machine, 18);
|
|
else bytes.writeUInt16BE(machine, 18);
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a minimal thin Mach-O header for binary-target parser tests. */
|
|
export const thinMach = (magic: number, cpu: number): Buffer => {
|
|
const bytes = Buffer.alloc(
|
|
magic === 0xfeedfacf || magic === 0xcffaedfe ? 32 : 28,
|
|
);
|
|
bytes.writeUInt32BE(magic, 0);
|
|
if (magic === 0xcefaedfe || magic === 0xcffaedfe) bytes.writeUInt32LE(cpu, 4);
|
|
else bytes.writeUInt32BE(cpu, 4);
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a minimal FAT Mach-O table for binary-target parser tests. */
|
|
export const fat = (
|
|
cpus: readonly number[],
|
|
declared = cpus.length,
|
|
): Buffer => {
|
|
const bytes = Buffer.alloc(8 + cpus.length * 20);
|
|
bytes.writeUInt32BE(0xcafebabe, 0);
|
|
bytes.writeUInt32BE(declared, 4);
|
|
cpus.forEach((cpu, index) => bytes.writeUInt32BE(cpu, 8 + index * 20));
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a bounded PE image with optional managed metadata. */
|
|
export const pe = (
|
|
machine: number,
|
|
offset = 64,
|
|
characteristics = 0x0002,
|
|
managed = false,
|
|
): Buffer => {
|
|
const optionalHeaderSize =
|
|
machine === 0x014c || machine === 0x01c0 ? 224 : 240;
|
|
const bytes = Buffer.alloc(Math.max(512, offset + 24 + optionalHeaderSize));
|
|
bytes.write("MZ", 0, "ascii");
|
|
bytes.writeUInt32LE(offset, 0x3c);
|
|
bytes.write("PE\0\0", offset, "binary");
|
|
bytes.writeUInt16LE(machine, offset + 4);
|
|
bytes.writeUInt16LE(optionalHeaderSize, offset + 20);
|
|
bytes.writeUInt16LE(characteristics, offset + 22);
|
|
const optionalHeader = offset + 24;
|
|
const pe32 = machine === 0x014c || machine === 0x01c0;
|
|
bytes.writeUInt16LE(pe32 ? 0x10b : 0x20b, optionalHeader);
|
|
const directoryOffset = pe32 ? 96 : 112;
|
|
bytes.writeUInt32LE(16, optionalHeader + directoryOffset - 4);
|
|
if (managed) {
|
|
bytes.writeUInt32LE(0x2000, optionalHeader + directoryOffset + 14 * 8);
|
|
bytes.writeUInt32LE(72, optionalHeader + directoryOffset + 14 * 8 + 4);
|
|
}
|
|
return bytes;
|
|
};
|
|
|
|
/** Build a DOS MZ image with a 32-byte header and a 16-bit entry point. */
|
|
export const dosMz = (moduleSize = 128): Buffer => {
|
|
const size = 32 + moduleSize;
|
|
const bytes = Buffer.alloc(size);
|
|
bytes.write("MZ", 0, "ascii");
|
|
bytes.writeUInt16LE(size % 512, 2);
|
|
bytes.writeUInt16LE(Math.ceil(size / 512), 4);
|
|
bytes.writeUInt16LE(2, 8);
|
|
bytes.writeUInt16LE(0xffff, 12);
|
|
bytes.writeUInt16LE(28, 24);
|
|
bytes.set([0xb8, 0x00, 0x4c, 0xcd, 0x21], 32);
|
|
// This is module data, not an e_lfanew field in a Windows DOS stub.
|
|
if (size >= 64) bytes.writeUInt32LE(0xfedcba98, 60);
|
|
return bytes;
|
|
};
|