jdx/mise · error · eyre::Report
malformed Mach-O in {}
Error message
malformed Mach-O in {} What it means
The Mach-O relocation pass found a 64-bit LE header whose sizeofcmds field declares a load-command table larger than the file slice itself. The file is truncated or corrupt, and patching is refused before any offsets are trusted.
Source
Thrown at src/system/packages/brew/macho.rs:74
.position(|w| w == r.placeholder)
{
out.splice(pos..pos + r.placeholder.len(), r.value.iter().cloned());
}
}
out
}
/// Patch one 64-bit LE Mach-O slice in place. Returns whether it changed.
fn patch_slice(slice: &mut [u8], replacements: &[Replacement], path: &Path) -> Result<bool> {
if slice.len() < HEADER_SIZE_64 || u32_at(slice, 0) != MH_MAGIC_64_LE {
// not a 64-bit LE Mach-O (32-bit or big-endian) — nothing modern on
// arm64 macOS; leave it to the caller's generic byte-level pass
return Ok(false);
}
let ncmds = u32_at(slice, 16) as usize;
let sizeofcmds = u32_at(slice, 20) as usize;
if HEADER_SIZE_64 + sizeofcmds > slice.len() {
bail!("malformed Mach-O in {}", path.display());
}
// upper bound for growing the load-command table: the first byte of
// section data (everything between sizeofcmds and there is padding)
let lc_end = HEADER_SIZE_64 + sizeofcmds;
let mut first_data = slice.len();
{
let mut off = HEADER_SIZE_64;
for _ in 0..ncmds {
if off + 8 > lc_end {
bail!("malformed load command table in {}", path.display());
}
let cmd = u32_at(slice, off);
let cmdsize = u32_at(slice, off + 4) as usize;
if cmdsize < 8 || off + cmdsize > lc_end {
bail!("malformed load command in {}", path.display());
}
if cmd == LC_SEGMENT_64 {View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Delete the cached bottle and re-pour, verifying checksums
- Run otool -hv / file on the artifact — tools that agree it is malformed confirm corruption rather than a parser bug
- If standard tools parse it fine, report the file upstream with its header bytes
Defensive patterns
Strategy: validation
Validate before calling
// Header sanity gate before Mach-O patching.
fn macho_header_ok(slice: &[u8]) -> bool {
slice.len() >= 32
&& u32::from_le_bytes(slice[0..4].try_into().unwrap()) == 0xfeedfacf
&& 32 + u32::from_le_bytes(slice[20..24].try_into().unwrap()) as usize <= slice.len()
} Type guard
fn is_patchable_macho64(slice: &[u8]) -> bool {
slice.len() >= 32
&& u32::from_le_bytes(slice[0..4].try_into().unwrap()) == 0xfeedfacf
&& (32usize + u32_at(slice, 20) as usize) <= slice.len()
} Try / catch
// Per-artifact: propagate with the file path attached so the failing bottle
// is identifiable in the pour log.
patch_macho(&mut content, &replacements, &path)
.wrap_err_with(|| format!("relocating {}", path.display()))?; Prevention
- Verify bottle checksums on download so truncated Mach-O files never reach relocation
- Screen artifacts with otool -hv in CI when pouring third-party bottles
When it happens
Trigger: patch_slice (via patch_macho on a bottle artifact): slice.len() >= 32 and magic 0xfeedfacf, but HEADER_SIZE_64 + sizeofcmds > slice.len(). Typical for a half-downloaded bottle, a fat-binary slice clipped by truncation, or a non-Mach-O file that happens to start with the magic.
Common situations: Interrupted bottle download leaving truncated binaries; corrupted cache; slicing bugs in fat-binary handling of unusual files.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- malformed load command table in {}
- malformed load command in {}
- cannot relocate {}: not enough padding to grow load commands
- malformed fat header in {}
- malformed fat arch in {}
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/e12ab172df56e6c3.
Report an issue: GitHub.