Files
reverse-engineering-anything/src/native/MachoArchitectureSelection.ts
T
Lewis WangandClaude Opus 5.5 b9138a1261 fix(native): select versioned arm64e slices for codesign by family name (#8713)
* fix(native): select versioned arm64e slices for codesign by family name

Current clang marks arm64e slices with pointer-authentication ABI
version 1 (subtype 0x81000002, lipo name arm64e.v1). The codesign
selector table accepted only ABI version 0, so inspect-signature failed
with capability_unavailable whenever such a slice was selected, for
example on any universal arm64e + x86_64 binary built today.

codesign has no versioned name: -a arm64e.v1 is "unknown architecture
name", and -a arm64e resolves to the first FAT slice with the arm64e
base subtype, whatever its ABI version. A selector is therefore exact
only for that first slice. Accept any versioned pointer-authentication
ABI for arm64e, and return a selector only when the slice is the first
in table order to carry that name; a later slice sharing it stays
unknown instead of being inspected through the earlier one. This also
removes the separate duplicate-name check from signature coverage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(native): keep arm64e selectors unknown behind unmapped variants

codesign resolves a name to the first FAT slice with the same CPU type and
base subtype, so an earlier capability variant whose own name is unknown may
shadow a later arm64e slice. Compare identities rather than successful name
mappings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 19:45:46 +08:00

144 lines
4.8 KiB
TypeScript

import { machoCpuSubtypeBase } from "../domain/apple/machoCpuIdentity.js";
import type { LipoArchitecture } from "./parsers/lipo.js";
interface MachoArchitecturePolicy {
readonly cpuType: number;
readonly subtypes: readonly {
readonly subtype: number;
readonly name: string;
}[];
readonly variantName?: RegExp;
}
/** One preference order for FAT subtype identities and Apple tool slice names. */
export const MACHO_ARCHITECTURE_POLICIES: Readonly<
Record<string, MachoArchitecturePolicy>
> = {
x86: { cpuType: 7, subtypes: [{ subtype: 3, name: "i386" }] },
x86_64: {
cpuType: 0x01000007,
subtypes: [{ subtype: 3, name: "x86_64" }],
},
arm: { cpuType: 12, subtypes: [], variantName: /^arm$/u },
arm64: {
cpuType: 0x0100000c,
subtypes: [
{ subtype: 0, name: "arm64" },
{ subtype: 2, name: "arm64e" },
{ subtype: 1, name: "arm64v8" },
],
variantName: /^arm64e\.[A-Za-z0-9_]+$/u,
},
arm64_32: {
cpuType: 0x0200000c,
subtypes: [{ subtype: 1, name: "arm64_32" }],
},
};
/** Unlisted subtypes follow the preferred slices in FAT table order. */
export const machoSubtypeRank = (
policy: MachoArchitecturePolicy,
subtype: number,
): number => {
const rank = policy.subtypes.findIndex(
(candidate) => candidate.subtype === machoCpuSubtypeBase(subtype),
);
return rank < 0 ? policy.subtypes.length : rank;
};
/** codesign's architecture name for one FAT CPU identity, when known. */
const codeSignArchitectureName = (
cpuType: number,
cpuSubtype: number,
): string | undefined => {
const capabilities = (cpuSubtype >>> 24) & 0xff;
const base = machoCpuSubtypeBase(cpuSubtype);
// codesign names arm64e by its base subtype whatever its versioned
// pointer-authentication ABI (CPU_SUBTYPE_PTRAUTH_ABI plus a version), and
// x86_64 LIB64 as x86_64. Other capability variants may have distinct
// names; do not collapse them.
if (
capabilities !== 0 &&
!(cpuType === 0x0100000c && base === 2 && (capabilities & 0xf0) === 0x80) &&
!(cpuType === 0x01000007 && base === 3 && capabilities === 0x80)
)
return undefined;
return Object.values(MACHO_ARCHITECTURE_POLICIES)
.find((policy) => policy.cpuType === cpuType)
?.subtypes.find(({ subtype }) => subtype === base)?.name;
};
/**
* Return the codesign selector that names exactly the FAT slice at `index`.
* codesign resolves a name to the first slice in table order with that CPU
* type and base subtype, whatever its capability bits. A later slice sharing
* them has no exact selector, even behind an earlier variant whose own name
* is unknown.
*/
export const machoKnownCodeSignSelector = (
slices: readonly { readonly cpuType: number; readonly cpuSubtype: number }[],
index: number,
): string | undefined => {
const slice = slices[index];
if (slice === undefined) return undefined;
const base = machoCpuSubtypeBase(slice.cpuSubtype);
return slices.findIndex(
(other) =>
other.cpuType === slice.cpuType &&
machoCpuSubtypeBase(other.cpuSubtype) === base,
) === index
? codeSignArchitectureName(slice.cpuType, slice.cpuSubtype)
: undefined;
};
/** Keep the selected lipo record, including its container coordinates. */
export const selectMachoArchitecture = (
policy: MachoArchitecturePolicy,
available: readonly LipoArchitecture[],
): LipoArchitecture | undefined => {
let selected: LipoArchitecture | undefined;
let bestRank = Number.POSITIVE_INFINITY;
for (const architecture of available) {
if (
architecture.cpu_type_code !== null &&
architecture.cpu_type_code !== policy.cpuType
)
continue;
const namedSubtype = policy.subtypes.find(
({ name }) => name === architecture.name,
);
const recognizedName =
namedSubtype !== undefined || policy.variantName?.test(architecture.name);
if (
!recognizedName &&
(architecture.cpu_type_code === null ||
architecture.cpu_subtype_base === null)
)
continue;
// Parsed identity is authoritative. Names support thin/partial lipo records.
const subtype = architecture.cpu_subtype_base ?? namedSubtype?.subtype;
const rank =
subtype === undefined
? policy.subtypes.length
: machoSubtypeRank(policy, subtype);
if (rank >= bestRank) continue;
selected = architecture;
bestRank = rank;
}
return selected;
};
/** lipo may label ARM64 V8 as arm64 even though tools select it as arm64v8. */
export const machoToolArchitectureName = (
policy: MachoArchitecturePolicy,
architecture: LipoArchitecture,
): string => {
const named = policy.subtypes.find(({ name }) => name === architecture.name);
const actual = policy.subtypes.find(
({ subtype }) => subtype === architecture.cpu_subtype_base,
);
return named !== undefined && actual !== undefined && named !== actual
? actual.name
: architecture.name;
};