jdx/mise · error · eyre::Report

malformed fat arch in {}

Error message

malformed fat arch in {}

What it means

Within a fat Mach-O, each arch entry carries an offset and size delimiting its slice. An entry whose offset+size exceeds the file length describes data that does not exist, so the slicer refuses to patch rather than index out of bounds or patch the wrong bytes.

Source

Thrown at src/system/packages/brew/macho.rs:193

        return Ok(false);
    }
    let be_magic = u32::from_be_bytes(content[..4].try_into().unwrap());
    if be_magic == FAT_MAGIC_BE {
        let nfat = u32::from_be_bytes(content[4..8].try_into().unwrap()) as usize;
        let mut changed = false;
        // collect slice ranges first (fat headers are big-endian)
        let mut ranges = vec![];
        for i in 0..nfat {
            let entry = 8 + i * 20;
            if entry + 20 > content.len() {
                bail!("malformed fat header in {}", path.display());
            }
            let offset =
                u32::from_be_bytes(content[entry + 8..entry + 12].try_into().unwrap()) as usize;
            let size =
                u32::from_be_bytes(content[entry + 12..entry + 16].try_into().unwrap()) as usize;
            if offset + size > content.len() {
                bail!("malformed fat arch in {}", path.display());
            }
            ranges.push(offset..offset + size);
        }
        for range in ranges {
            changed |= patch_slice(&mut content[range], replacements, path)?;
        }
        Ok(changed)
    } else {
        patch_slice(content, replacements, path)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::system::packages::brew::relocate::tests::test_replacements;

    /// build a minimal 64-bit Mach-O: header + LC_SEGMENT_64 (one section)

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Re-download the bottle from scratch and retry the pour
  2. Verify with lipo -detailed_info — slices outside the file will fail there too
  3. Report upstream if the artifact is reproducibly valid in other tooling
Defensive patterns

Strategy: validation

Validate before calling

// Every fat-arch slice must lie inside the file before patching.
fn fat_slices_in_bounds(content: &[u8]) -> bool {
    if content.len() < 8 { return false; }
    let nfat = u32::from_be_bytes(content[4..8].try_into().unwrap()) as usize;
    (0..nfat).all(|i| {
        let e = 8 + i * 20;
        e + 20 <= content.len() && {
            let off = u32::from_be_bytes(content[e + 8..e + 12].try_into().unwrap()) as usize;
            let size = u32::from_be_bytes(content[e + 12..e + 16].try_into().unwrap()) as usize;
            off.checked_add(size).is_some_and(|end| end <= content.len())
        }
    })
}

Type guard

fn fat_macho_wellformed(content: &[u8]) -> bool {
    fat_header_ok(content) && fat_slices_in_bounds(content)
}

Try / catch

match patch_macho(&mut content, &replacements, &path) {
    Ok(changed) => changed,
    Err(e) => { warn!("{}: {e:#}", path.display()); false }
}

Prevention

When it happens

Trigger: patch_macho fat path: offset + size > content.len() for some arch entry. Produced by truncated files (slices at the end missing), corrupted arch tables, or files with stale headers after being truncated/re-padded.

Common situations: Partially downloaded universal bottles; artifacts damaged in transit; fat binaries post-processed by tools that stripped slices without updating the header.

Understand the failure class

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/8ad53328e1d7998e. Report an issue: GitHub.