fix(native): preserve unknown ARM64 subtype in hardening (#9600)

This commit is contained in:
morluto authored and GitHub committed 2026-11-09 19:34:28 +08:00
1 parent 897caa814d
commit cc13c5d095
3 files changed
+81 -9

No files matched your search

+19 -4
View File
@@ -5,6 +5,24 @@ const CPU_SUBTYPE_CAPABILITY_MASK = 0xff000000;
export const machoCpuSubtypeBase = (cpuSubtype: number): number =>
cpuSubtype & ~CPU_SUBTYPE_CAPABILITY_MASK;
/** Known ARM64 subtype identity, distinct from the family display fallback. */
export const machoKnownArm64Subtype = (
cpuSubtype: number,
): string | undefined => {
switch (machoCpuSubtypeBase(cpuSubtype)) {
case 0:
return "arm64";
case 1:
return "arm64v8";
case 2:
return "arm64e";
case 12:
return "arm64e.x1";
default:
return undefined;
}
};
/**
* Display known byte-header identities without inferring pointer-auth ABI names.
* Subtype 12: llvm-project b8007a8e4020b8bca2b12e941660e10bf5bf6716,
@@ -16,10 +34,7 @@ export const machoArchitectureName = (
): string => {
const subtype = machoCpuSubtypeBase(cpuSubtype);
if (cpuType === 0x0100000c) {
if (subtype === 12) return "arm64e.x1";
if (subtype === 2) return "arm64e";
if (subtype === 1) return "arm64v8";
return "arm64";
return machoKnownArm64Subtype(cpuSubtype) ?? "arm64";
}
if (cpuType === 0x0200000c) return "arm64_32";
if (cpuType === 0x01000007) return subtype === 8 ? "x86_64h" : "x86_64";
+14 -4
View File
@@ -1,5 +1,8 @@
import { validateFatSliceIdentity } from "../../domain/apple/machoContainer.js";
import { machoArchitectureName } from "../../domain/apple/machoCpuIdentity.js";
import {
machoArchitectureName,
machoKnownArm64Subtype,
} from "../../domain/apple/machoCpuIdentity.js";
import {
MACHO_HEADER_FLAGS_OFFSET,
readMachoLinkeditDataCommand,
@@ -386,7 +389,6 @@ const buildRows = (
flagLocation,
);
};
const architecture = machoArchitectureName(header.cpuType, header.cpuSubtype);
const pie =
header.fileType === MH_DYLIB || header.fileType === MH_BUNDLE
? row(
@@ -435,7 +437,7 @@ const buildRows = (
},
),
encryptionRow(encrypted),
arm64Row(header.cpuType, architecture, ref.offset),
arm64Row(header.cpuType, header.cpuSubtype, ref.offset),
stackRow(bytes, ref, header.wide, symtab),
row(
"hardened_runtime",
@@ -523,7 +525,7 @@ const encryptionRow = (encrypted: MachoSliceEncryption): BinaryHardeningRow => {
const arm64Row = (
cpuType: number,
architecture: string,
cpuSubtype: number,
sliceOffset: number,
): BinaryHardeningRow => {
const location = { offset: sliceOffset + 8, bytes: 4 };
@@ -534,6 +536,14 @@ const arm64Row = (
`CPU type 0x${cpuType.toString(16)} is not ARM64.`,
location,
);
const architecture = machoKnownArm64Subtype(cpuSubtype);
if (architecture === undefined)
return row(
"arm64e",
"unknown",
`Unrecognized ARM64 CPU subtype 0x${(cpuSubtype >>> 0).toString(16)}; arm64e status is unknown.`,
{ offset: sliceOffset + 8, bytes: 4 },
);
const enabled = architecture === "arm64e" || architecture === "arm64e.x1";
return row(
"arm64e",
+48 -1
View File
@@ -3,7 +3,10 @@ import { join } from "node:path";
import { expect } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { createServer } from "../../../src/server/createServer.js";
import { elfHardeningFixture } from "../../../src/native/hardening/BinaryHardening.fixture.js";
import {
elfHardeningFixture,
machoHardeningFixture,
} from "../../../src/native/hardening/BinaryHardening.fixture.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { mcpTest } from "../../support/mcp/mcpFixture.js";
@@ -63,3 +66,47 @@ mcpTest(
},
60_000,
);
mcpTest(
"reports an unknown ARM64 subtype without treating its display fallback as arm64e proof",
async ({ mcp }) => {
const session = createTestBinarySession(() => {
throw new Error("Must not start a deep provider");
});
const client = await mcp.connect(
createServer({ kind: "session", session }),
);
try {
const root = await createTestTempDirectory("rea-arm64-subtype-");
const path = join(root, "image");
for (const [subtype, state] of [
[0, "disabled"],
[1, "disabled"],
[2, "enabled"],
[0x8000000c, "enabled"],
[3, "unknown"],
] as const) {
const bytes = machoHardeningFixture({ cpuSubtype: subtype });
await writeFile(path, bytes);
const response = await client.callTool({
name: "inspect_binary_hardening",
arguments: { path },
});
expect(response.isError, JSON.stringify(response.content)).not.toBe(
true,
);
const evidence = toolContract(
"inspect_binary_hardening",
).outputSchema.parse(response.structuredContent);
const image = evidence.normalized_result.images[0];
expect(image?.status).toBe("parsed");
expect(
image?.rows.find((row) => row.property === "arm64e")?.state,
).toBe(state);
}
} finally {
await session.close();
}
},
60_000,
);