Files
rea/src/domain/binaryTarget.fixture.ts
T
Kaoru0822andClaude Opus 5.5 0b04d384cc fix(native): refuse truncated executable headers at target admission
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>
2026-10-09 03:51:37 +00:00

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;
};