{"record":{"id":"204f010986dbfeb3","repo":"jdx/mise","slug":"malformed-fat-header-in","errorCode":null,"errorMessage":"malformed fat header in {}","messagePattern":"malformed fat header in (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/macho.rs","lineNumber":186,"sourceCode":"    Ok(true)\n}\n\n/// Patch load-command path strings in a Mach-O file (thin or fat).\n/// Returns whether anything changed.\npub fn patch(content: &mut [u8], replacements: &[Replacement], path: &Path) -> Result<bool> {\n    if content.len() < 8 {\n        return Ok(false);\n    }\n    let be_magic = u32::from_be_bytes(content[..4].try_into().unwrap());\n    if be_magic == FAT_MAGIC_BE {\n        let nfat = u32::from_be_bytes(content[4..8].try_into().unwrap()) as usize;\n        let mut changed = false;\n        // collect slice ranges first (fat headers are big-endian)\n        let mut ranges = vec![];\n        for i in 0..nfat {\n            let entry = 8 + i * 20;\n            if entry + 20 > content.len() {\n                bail!(\"malformed fat header in {}\", path.display());\n            }\n            let offset =\n                u32::from_be_bytes(content[entry + 8..entry + 12].try_into().unwrap()) as usize;\n            let size =\n                u32::from_be_bytes(content[entry + 12..entry + 16].try_into().unwrap()) as usize;\n            if offset + size > content.len() {\n                bail!(\"malformed fat arch in {}\", path.display());\n            }\n            ranges.push(offset..offset + size);\n        }\n        for range in ranges {\n            changed |= patch_slice(&mut content[range], replacements, path)?;\n        }\n        Ok(changed)\n    } else {\n        patch_slice(content, replacements, path)\n    }\n}","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/macho.rs#L168-L204","documentation":"A universal (fat) Mach-O begins with a big-endian 0xcafebabe header followed by nfat_arch entries of 20 bytes each. If the file ends before all fat-arch entries can be read, the header is truncated and the file is rejected before any slice is patched.","triggerScenarios":"patch_macho fat path: for some i in 0..nfat, entry = 8 + i*20 satisfies entry + 20 > content.len(). Caused by truncated downloads, corrupted caches, or a file whose nfat_arch field is garbage (random bytes after the magic).","commonSituations":"Interrupted bottle downloads; disk or cache corruption; non-Mach-O files that coincidentally begin with 0xcafebabe (e.g. Java class files use the same magic).","solutions":["Clear the bottle cache and re-download, verifying the checksum","Run lipo -info / file on the artifact to confirm whether it is a valid universal binary","Report upstream if the file validates elsewhere"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Fat header gate: file must contain the full nfat_arch entry array.\nfn fat_header_ok(content: &[u8]) -> bool {\n    content.len() >= 8\n        && u32::from_be_bytes(content[..4].try_into().unwrap()) == 0xcafebabe\n        && {\n            let nfat = u32::from_be_bytes(content[4..8].try_into().unwrap()) as usize;\n            8 + nfat * 20 <= content.len()\n        }\n}","typeGuard":"fn is_patchable_fat_macho(content: &[u8]) -> bool {\n    fat_header_ok(content)\n        && (0..u32::from_be_bytes(content[4..8].try_into().unwrap()) as usize).all(|i| {\n            let e = 8 + i * 20;\n            let off = u32::from_be_bytes(content[e + 8..e + 12].try_into().unwrap()) as usize;\n            let size = u32::from_be_bytes(content[e + 12..e + 16].try_into().unwrap()) as usize;\n            off + size <= content.len()\n        })\n}","tryCatchPattern":"if let Err(e) = patch_macho(&mut content, &replacements, &path) {\n    warn!(\"{}: malformed universal binary, not relocated: {e:#}\", path.display());\n}","preventionTips":["Verify bottle checksums before extraction","Remember 0xcafebabe is also the Java class magic — don't feed jars/classfiles to Mach-O tooling"],"tags":["brew","macho","macos","fat-binary","validation"],"backgroundTag":"mach-o-header-validation-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}