Add macOS headerpad fix for x64 binaries
Some checks reported errors
farmcontrol/farmcontrol-ui/pipeline/head Something is wrong with the build of this commit
Some checks reported errors
farmcontrol/farmcontrol-ui/pipeline/head Something is wrong with the build of this commit
- Introduced `fix-macos-headerpad.mjs` to address issues with Mach-O headerpad in x64 binaries, preventing corruption during code signing. - Updated `ensure-electrobun-core.mjs` to call the new headerpad fix function, ensuring compatibility with macOS environments. - This change resolves issue #485 by freeing load-command slots before signing binaries, enhancing stability on Intel Macs.
This commit is contained in:
parent
c1e0921c3e
commit
21a88e00d0
@ -9,6 +9,7 @@ import { spawnSync } from "node:child_process";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { getReleaseArch } from "./release-artifact-utils.mjs";
|
import { getReleaseArch } from "./release-artifact-utils.mjs";
|
||||||
|
import { fixMacosHeaderpad } from "./fix-macos-headerpad.mjs";
|
||||||
|
|
||||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
const electrobunDir = path.join(rootDir, "node_modules/electrobun");
|
const electrobunDir = path.join(rootDir, "node_modules/electrobun");
|
||||||
@ -124,4 +125,7 @@ const targetArch = getReleaseArch(
|
|||||||
const archesToEnsure = new Set([hostArch, targetArch]);
|
const archesToEnsure = new Set([hostArch, targetArch]);
|
||||||
for (const arch of archesToEnsure) {
|
for (const arch of archesToEnsure) {
|
||||||
await ensureCoreDependencies(targetOS, arch);
|
await ensureCoreDependencies(targetOS, arch);
|
||||||
|
// electrobun#485: x64 core binaries lack Mach-O headerpad and get corrupted
|
||||||
|
// by codesign; free a load-command slot before anything copies or signs them.
|
||||||
|
fixMacosHeaderpad(getPlatformPaths(targetOS, arch).platformDistDir);
|
||||||
}
|
}
|
||||||
|
|||||||
152
scripts/fix-macos-headerpad.mjs
Normal file
152
scripts/fix-macos-headerpad.mjs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// Workaround for https://github.com/blackboardsh/electrobun/issues/485
|
||||||
|
//
|
||||||
|
// Electrobun's darwin-x64 core binaries (launcher, extractor, libasar.dylib,
|
||||||
|
// bsdiff, zig-asar in v1.18.1) are Zig-built with zero/near-zero Mach-O
|
||||||
|
// headerpad and no code signature. When codesign adds the 16-byte
|
||||||
|
// LC_CODE_SIGNATURE load command it silently overwrites the first bytes of
|
||||||
|
// __text, and the signed binary segfaults on launch (Intel Macs only;
|
||||||
|
// arm64 binaries always ship with a signature slot that is re-signed in
|
||||||
|
// place).
|
||||||
|
//
|
||||||
|
// This script frees room in the load-command area by dropping the 16-byte
|
||||||
|
// LC_SOURCE_VERSION command (LC_UUID as a fallback) from any thin x86_64
|
||||||
|
// Mach-O that has less than 16 bytes of headerpad and no existing code
|
||||||
|
// signature. It is idempotent and a no-op once upstream ships rebuilt
|
||||||
|
// binaries with headerpad_size set.
|
||||||
|
|
||||||
|
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const MH_MAGIC_64 = 0xfeedfacf;
|
||||||
|
const CPU_TYPE_X86_64 = 0x01000007;
|
||||||
|
const LC_SEGMENT_64 = 0x19;
|
||||||
|
const LC_UUID = 0x1b;
|
||||||
|
const LC_CODE_SIGNATURE = 0x1d;
|
||||||
|
const LC_SOURCE_VERSION = 0x2a;
|
||||||
|
const HEADER_SIZE = 32;
|
||||||
|
const CODE_SIGNATURE_CMD_SIZE = 16;
|
||||||
|
|
||||||
|
function analyzeMachO(buffer) {
|
||||||
|
if (buffer.length < HEADER_SIZE || buffer.readUInt32LE(0) !== MH_MAGIC_64) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cputype = buffer.readInt32LE(4);
|
||||||
|
const ncmds = buffer.readUInt32LE(16);
|
||||||
|
const sizeofcmds = buffer.readUInt32LE(20);
|
||||||
|
|
||||||
|
let offset = HEADER_SIZE;
|
||||||
|
let firstSectionOffset = null;
|
||||||
|
let hasCodeSignature = false;
|
||||||
|
let sourceVersionCmd = null;
|
||||||
|
let uuidCmd = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < ncmds; i += 1) {
|
||||||
|
const cmd = buffer.readUInt32LE(offset);
|
||||||
|
const cmdsize = buffer.readUInt32LE(offset + 4);
|
||||||
|
|
||||||
|
if (cmd === LC_CODE_SIGNATURE) {
|
||||||
|
hasCodeSignature = true;
|
||||||
|
} else if (cmd === LC_SOURCE_VERSION) {
|
||||||
|
sourceVersionCmd = { offset, cmdsize };
|
||||||
|
} else if (cmd === LC_UUID) {
|
||||||
|
uuidCmd = { offset, cmdsize };
|
||||||
|
} else if (cmd === LC_SEGMENT_64) {
|
||||||
|
const nsects = buffer.readUInt32LE(offset + 64);
|
||||||
|
let sectionOffset = offset + 72;
|
||||||
|
for (let s = 0; s < nsects; s += 1) {
|
||||||
|
const size = Number(buffer.readBigUInt64LE(sectionOffset + 40));
|
||||||
|
const fileOffset = buffer.readUInt32LE(sectionOffset + 48);
|
||||||
|
if (fileOffset > 0 && size > 0) {
|
||||||
|
firstSectionOffset =
|
||||||
|
firstSectionOffset === null
|
||||||
|
? fileOffset
|
||||||
|
: Math.min(firstSectionOffset, fileOffset);
|
||||||
|
}
|
||||||
|
sectionOffset += 80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += cmdsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cputype,
|
||||||
|
ncmds,
|
||||||
|
sizeofcmds,
|
||||||
|
headerpad:
|
||||||
|
firstSectionOffset === null
|
||||||
|
? Infinity
|
||||||
|
: firstSectionOffset - (HEADER_SIZE + sizeofcmds),
|
||||||
|
hasCodeSignature,
|
||||||
|
removableCmd: sourceVersionCmd ?? uuidCmd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixFile(filePath) {
|
||||||
|
const buffer = readFileSync(filePath);
|
||||||
|
const info = analyzeMachO(buffer);
|
||||||
|
const name = path.basename(filePath);
|
||||||
|
|
||||||
|
if (!info || info.cputype !== CPU_TYPE_X86_64) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.hasCodeSignature || info.headerpad >= CODE_SIGNATURE_CMD_SIZE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.removableCmd) {
|
||||||
|
console.warn(
|
||||||
|
`fix-macos-headerpad: ${name} has headerpad ${info.headerpad} but no removable load command; signing may corrupt it`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { offset, cmdsize } = info.removableCmd;
|
||||||
|
const loadCommandsEnd = HEADER_SIZE + info.sizeofcmds;
|
||||||
|
|
||||||
|
// Shift the remaining load commands over the removed one, then zero the
|
||||||
|
// freed tail so codesign finds clean padding.
|
||||||
|
buffer.copyWithin(offset, offset + cmdsize, loadCommandsEnd);
|
||||||
|
buffer.fill(0, loadCommandsEnd - cmdsize, loadCommandsEnd);
|
||||||
|
buffer.writeUInt32LE(info.ncmds - 1, 16);
|
||||||
|
buffer.writeUInt32LE(info.sizeofcmds - cmdsize, 20);
|
||||||
|
|
||||||
|
writeFileSync(filePath, buffer);
|
||||||
|
console.log(
|
||||||
|
`fix-macos-headerpad: ${name} freed ${cmdsize} bytes for LC_CODE_SIGNATURE (headerpad was ${info.headerpad})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fixMacosHeaderpad(directory) {
|
||||||
|
if (process.platform !== "darwin") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries;
|
||||||
|
try {
|
||||||
|
entries = readdirSync(directory);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const filePath = path.join(directory, entry);
|
||||||
|
if (statSync(filePath).isFile()) {
|
||||||
|
fixFile(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : null;
|
||||||
|
const modulePath = fileURLToPath(import.meta.url);
|
||||||
|
|
||||||
|
if (invokedPath === modulePath) {
|
||||||
|
const rootDir = path.resolve(path.dirname(modulePath), "..");
|
||||||
|
const target =
|
||||||
|
process.argv[2] ||
|
||||||
|
path.join(rootDir, "node_modules/electrobun/dist-macos-x64");
|
||||||
|
fixMacosHeaderpad(path.resolve(target));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user