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
11pub 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 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
142fn 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}