mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
861e17a3a7
* fix(native): admit thin arm64_32 Mach-O targets for static inspection Target admission mapped Mach-O CPU types only to x86, x86_64, arm and arm64, so a thin arm64_32 object was rejected as "unsupported Mach-O architecture" before list-architectures, inspect-macho or inspect-signature could run, although the same slice inside a FAT file was inventoried. Recognize CPU_TYPE_ARM64_32 as its own Mach-O-only architecture with a dedicated thin target variant, so ELF, PE and raw-binary paths cannot carry it. Add its slice policy so 32-bit dispatch metadata refuses it like other 32-bit Mach-O images instead of reporting a CPU mismatch. Ghidra, Hopper and IDA have no verified arm64_32 lane and decline it explicitly; Hopper's declined families now share one predicate. Fixes #5713 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ci(apple): run the thin arm64_32 CLI regression on macOS The regression compiles an arm64_32 object with Apple clang, so it skips on the Ubuntu jobs. List it in the macOS Apple artifact job and its change scope so the job runs it and changes to it select that job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
171 lines
5.3 KiB
TypeScript
171 lines
5.3 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { readFile } from "node:fs/promises";
|
|
import type {
|
|
BinaryTarget,
|
|
HeaderArchitecture,
|
|
} from "../../src/domain/binaryTargetTypes.js";
|
|
import type { InspectSignature } from "../../src/domain/native/nativeInspection.js";
|
|
import { ok } from "../../src/domain/result.js";
|
|
import type {
|
|
NativeCommandCapture,
|
|
NativeCommandOptions,
|
|
NativeCommandRunner,
|
|
} from "../../src/native/CommandRunner.js";
|
|
|
|
/** Replay captured native command outputs through the production runner seam. */
|
|
export class NativeFixtureRunner implements NativeCommandRunner {
|
|
constructor(
|
|
private readonly overrides: Readonly<Record<string, string>> = {},
|
|
private readonly exitCode = 0,
|
|
) {}
|
|
|
|
async run(
|
|
tool: string,
|
|
arguments_: readonly string[],
|
|
_options?: NativeCommandOptions,
|
|
) {
|
|
const output =
|
|
this.overrides[`${tool}:${arguments_[0] ?? ""}`] ??
|
|
this.overrides[tool] ??
|
|
(await outputFor(tool, arguments_));
|
|
return ok({
|
|
...capture(tool, arguments_, output),
|
|
exitCode: this.exitCode,
|
|
});
|
|
}
|
|
}
|
|
|
|
const outputFor = async (
|
|
tool: string,
|
|
arguments_: readonly string[],
|
|
): Promise<string> => {
|
|
if (tool === "lipo") return nativeFixture("lipo-fat.txt");
|
|
if (tool === "otool") return nativeFixture("otool-load.txt");
|
|
if (tool === "nm") return "_main\n_$s4Test3fooyyF\n";
|
|
if (tool === "dyld_info")
|
|
return nativeFixture(
|
|
arguments_[0] === "-imports" ? "dyld-imports.txt" : "dyld-exports.txt",
|
|
);
|
|
if (tool === "dwarfdump")
|
|
return `UUID: 01234567-89AB-CDEF-0123-456789ABCDEF (arm64) ${arguments_.at(-1)}\n`;
|
|
if (tool === "file")
|
|
return arguments_.at(-1)?.endsWith(".plist") === true
|
|
? "XML 1.0 document text\n"
|
|
: "Mach-O 64-bit executable arm64\n";
|
|
if (tool === "vtool") return "Load command 3 LC_BUILD_VERSION\n";
|
|
if (tool === "swift-demangle") return nativeFixture("demangle.txt");
|
|
if (tool === "plutil") return nativeFixture("plist.json");
|
|
if (tool === "codesign") {
|
|
if (arguments_.includes("--entitlements"))
|
|
return nativeFixture("entitlements.xml");
|
|
if (arguments_.includes("-r-"))
|
|
return "designated => identifier com.example.fixture\n";
|
|
return nativeFixture("codesign.txt");
|
|
}
|
|
throw new Error(`Unexpected fixture tool ${tool}`);
|
|
};
|
|
|
|
/**
|
|
* codesign prints display diagnostics to stderr and requirements or
|
|
* entitlements to stdout.
|
|
*/
|
|
const fixtureStreams = (
|
|
tool: string,
|
|
arguments_: readonly string[],
|
|
output: string,
|
|
) => {
|
|
const stderr =
|
|
tool === "codesign" &&
|
|
!arguments_.includes("-r-") &&
|
|
!arguments_.includes("--entitlements");
|
|
return {
|
|
stdout: stderr ? "" : output,
|
|
stderr: stderr ? output : "",
|
|
stdoutBytes: Buffer.byteLength(stderr ? "" : output),
|
|
stderrBytes: Buffer.byteLength(stderr ? output : ""),
|
|
};
|
|
};
|
|
|
|
const capture = (
|
|
tool: string,
|
|
arguments_: readonly string[],
|
|
output: string,
|
|
): NativeCommandCapture => ({
|
|
tool,
|
|
executable: `/usr/bin/${tool}`,
|
|
executableSha256: "a".repeat(64),
|
|
toolVersion: null,
|
|
versionReason: "fixture",
|
|
arguments: [...arguments_],
|
|
...fixtureStreams(tool, arguments_, output),
|
|
exitCode: 0,
|
|
signal: null,
|
|
});
|
|
|
|
/** Build the Mach-O target identity used by native boundary fixtures. */
|
|
export const nativeMachoTarget = (
|
|
path: string,
|
|
sourcePath?: string,
|
|
): BinaryTarget & {
|
|
readonly kind: "executable";
|
|
readonly format: "mach-o";
|
|
readonly architecture: HeaderArchitecture;
|
|
} => ({
|
|
path,
|
|
...(sourcePath === undefined ? {} : { sourcePath }),
|
|
sha256: "0".repeat(64),
|
|
kind: "executable",
|
|
format: "mach-o",
|
|
architecture: "arm64",
|
|
availableArchitectures: ["x86_64", "arm64"],
|
|
});
|
|
|
|
/** Register the actual bytes of a filesystem-backed native test target. */
|
|
export const nativeMachoTargetForFile = async (
|
|
path: string,
|
|
sourcePath?: string,
|
|
): Promise<ReturnType<typeof nativeMachoTarget>> => ({
|
|
...nativeMachoTarget(path, sourcePath),
|
|
sha256: createHash("sha256")
|
|
.update(await readFile(path))
|
|
.digest("hex"),
|
|
});
|
|
|
|
/** Load one captured native tool output. */
|
|
export const nativeFixture = (name: string): Promise<string> =>
|
|
readFile(new URL(`./native-macos/${name}`, import.meta.url), "utf8");
|
|
|
|
/** Add consistent Mach command-count metadata to a command-only test capture. */
|
|
export const withOtoolMachHeader = (output: string): string => {
|
|
const count = [...output.matchAll(/^Load command (\d+)$/gmu)].length;
|
|
const sizes = [...output.matchAll(/^\s*cmdsize\s+(\d+)\s*$/gmu)].map(
|
|
([, size]) => Number(size),
|
|
);
|
|
if (sizes.length !== count)
|
|
throw new TypeError("command-only fixture must provide every cmdsize");
|
|
const bytes = sizes.reduce((sum, size) => sum + size, 0);
|
|
return [
|
|
"Mach header",
|
|
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
|
|
` 0xfeedfacf 16777228 0 0x00 2 ${count} ${bytes} 0x00000000`,
|
|
output,
|
|
].join("\n");
|
|
};
|
|
|
|
/** Parsed signature facts for lifecycle controls that do not invoke native tools. */
|
|
export const signatureInspectionFixture = (): InspectSignature => ({
|
|
signed: true,
|
|
identifier: "rea.lifecycle-fixture",
|
|
team_identifier: null,
|
|
format: "Mach-O thin (arm64)",
|
|
cdhashes: ["0123456789abcdef"],
|
|
hash_algorithms: ["sha256"],
|
|
authorities: [],
|
|
designated_requirement: null,
|
|
entitlements: null,
|
|
timestamp: null,
|
|
hardened_runtime: null,
|
|
provenance: [],
|
|
limitations: ["Controlled parsed facts; no native signing tool invoked."],
|
|
});
|