Skip to main content

macho_core/format/
fat.rs

1use crate::error::{ContextFrame, Error, Result};
2use crate::format::ParseLimits;
3use crate::format::io::endian::Endian;
4use crate::format::io::pod::{self, RawFatArch32, RawFatArch64, RawFatHeader};
5use crate::format::macho::parse_macho_file_with_limits;
6use crate::model::addr::FatFileOffset;
7use crate::model::container::{FatArch, FatBinary};
8use crate::model::header::{ArchSpec, FatHeader, FatMagic};
9use crate::model::header::{CpuSubtype, CpuType};
10
11/// Performs parse_fat_binary.
12pub fn parse_fat_binary(data: &[u8]) -> Result<FatBinary<'_>> {
13    parse_fat_binary_with_limits(data, &ParseLimits::default())
14}
15
16pub(crate) fn parse_fat_binary_with_limits<'data>(
17    data: &'data [u8],
18    limits: &ParseLimits,
19) -> Result<FatBinary<'data>> {
20    if data.len() < 8 {
21        return Err(Error::format("file too small for fat header"));
22    }
23
24    // Fat headers are always big-endian per spec
25    let endian = Endian::Big;
26
27    let raw_header: RawFatHeader = pod::read_pod(data, 0)?;
28    let magic_val = endian.interpret_u32(raw_header.magic);
29    let magic = FatMagic::from_u32(magic_val)?;
30    let nfat_arch = endian.interpret_u32(raw_header.nfat_arch);
31
32    if nfat_arch == 0 {
33        return Err(Error::format("fat binary has zero architectures"));
34    }
35    if nfat_arch as usize > limits.max_fat_arches {
36        return Err(Error::limit(format!(
37            "fat binary claims {nfat_arch} architectures, exceeding max_fat_arches={}",
38            limits.max_fat_arches
39        )));
40    }
41
42    let header = FatHeader::new(magic, nfat_arch);
43    let mut arches = Vec::with_capacity(nfat_arch as usize);
44
45    if magic.is_64bit() {
46        let arch_size = size_of::<RawFatArch64>();
47        for i in 0..nfat_arch as usize {
48            let arch_off = 8usize
49                .checked_add(i.checked_mul(arch_size).ok_or_else(|| {
50                    Error::format(format!("fat arch {i}: index * stride overflows usize"))
51                })?)
52                .ok_or_else(|| {
53                    Error::format(format!("fat arch {i}: header offset overflows usize"))
54                })?;
55            let raw: RawFatArch64 = pod::read_pod(data, arch_off)
56                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
57            let offset = endian.interpret_u64(raw.offset);
58            let size = endian.interpret_u64(raw.size);
59            let align = endian.interpret_u32(raw.align);
60
61            validate_arch_bounds(data.len(), offset, size, i)
62                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
63            let (start, end) = arch_bounds_as_usize(offset, size, i)
64                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
65            let slice = &data[start..end];
66            let macho = parse_macho_file_with_limits(slice, limits)
67                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
68
69            arches.push(
70                FatArch::try_new(
71                    ArchSpec {
72                        cpu_type: CpuType(endian.interpret_i32(raw.cputype)),
73                        cpu_subtype: CpuSubtype(endian.interpret_i32(raw.cpusubtype)),
74                    },
75                    FatFileOffset(offset),
76                    size,
77                    align,
78                    endian.interpret_u32(raw.reserved),
79                    macho,
80                    data.len(),
81                )
82                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?,
83            );
84        }
85    } else {
86        let arch_size = size_of::<RawFatArch32>();
87        for i in 0..nfat_arch as usize {
88            let arch_off = 8usize
89                .checked_add(i.checked_mul(arch_size).ok_or_else(|| {
90                    Error::format(format!("fat arch {i}: index * stride overflows usize"))
91                })?)
92                .ok_or_else(|| {
93                    Error::format(format!("fat arch {i}: header offset overflows usize"))
94                })?;
95            let raw: RawFatArch32 = pod::read_pod(data, arch_off)
96                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
97            let offset = endian.interpret_u32(raw.offset) as u64;
98            let size = endian.interpret_u32(raw.size) as u64;
99            let align = endian.interpret_u32(raw.align);
100
101            validate_arch_bounds(data.len(), offset, size, i)
102                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
103            let (start, end) = arch_bounds_as_usize(offset, size, i)
104                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
105            let slice = &data[start..end];
106            let macho = parse_macho_file_with_limits(slice, limits)
107                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
108
109            arches.push(
110                FatArch::try_new(
111                    ArchSpec {
112                        cpu_type: CpuType(endian.interpret_i32(raw.cputype)),
113                        cpu_subtype: CpuSubtype(endian.interpret_i32(raw.cpusubtype)),
114                    },
115                    FatFileOffset(offset),
116                    size,
117                    align,
118                    0,
119                    macho,
120                    data.len(),
121                )
122                .map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?,
123            );
124        }
125    }
126
127    FatBinary::try_new(header, arches, data)
128}
129
130fn validate_arch_bounds(file_len: usize, offset: u64, size: u64, index: usize) -> Result<()> {
131    let end = offset
132        .checked_add(size)
133        .ok_or_else(|| Error::format(format!("fat arch {index}: offset + size overflows")))?;
134    if end > file_len as u64 {
135        return Err(Error::format(format!(
136            "fat arch {index}: slice {offset:#x}..{end:#x} exceeds file size {file_len:#x}"
137        )));
138    }
139    Ok(())
140}
141
142/// Convert validated `u64` slice bounds into `usize` for indexing.
143///
144/// `validate_arch_bounds` has already proven `offset + size <= file_len as u64`
145/// and that `file_len <= usize::MAX`, so both fit in `usize` on any target where
146/// the file was mapped into memory. The explicit conversion guards against
147/// cross-compiling to a 32-bit target where a valid-looking 64-bit fat offset
148/// would otherwise truncate during a raw `as usize` cast.
149fn arch_bounds_as_usize(offset: u64, size: u64, index: usize) -> Result<(usize, usize)> {
150    let start = usize::try_from(offset).map_err(|_| {
151        Error::format(format!(
152            "fat arch {index}: offset {offset:#x} exceeds addressable memory"
153        ))
154    })?;
155    let end_u64 = offset
156        .checked_add(size)
157        .ok_or_else(|| Error::format(format!("fat arch {index}: offset + size overflows")))?;
158    let end = usize::try_from(end_u64).map_err(|_| {
159        Error::format(format!(
160            "fat arch {index}: end {end_u64:#x} exceeds addressable memory"
161        ))
162    })?;
163    Ok((start, end))
164}