use crate::error::{ContextFrame, Error, Result};
use crate::format::ParseLimits;
use crate::format::io::endian::Endian;
use crate::format::io::pod::{self, RawFatArch32, RawFatArch64, RawFatHeader};
use crate::format::macho::parse_macho_file_with_limits;
use crate::model::addr::FatFileOffset;
use crate::model::container::{FatArch, FatBinary};
use crate::model::header::{ArchSpec, FatHeader, FatMagic};
use crate::model::header::{CpuSubtype, CpuType};
pub fn parse_fat_binary(data: &[u8]) -> Result<FatBinary<'_>> {
parse_fat_binary_with_limits(data, &ParseLimits::default())
}
pub(crate) fn parse_fat_binary_with_limits<'data>(
data: &'data [u8],
limits: &ParseLimits,
) -> Result<FatBinary<'data>> {
if data.len() < 8 {
return Err(Error::format("file too small for fat header"));
}
let endian = Endian::Big;
let raw_header: RawFatHeader = pod::read_pod(data, 0)?;
let magic_val = endian.interpret_u32(raw_header.magic);
let magic = FatMagic::from_u32(magic_val)?;
let nfat_arch = endian.interpret_u32(raw_header.nfat_arch);
if nfat_arch == 0 {
return Err(Error::format("fat binary has zero architectures"));
}
if nfat_arch as usize > limits.max_fat_arches {
return Err(Error::limit(format!(
"fat binary claims {nfat_arch} architectures, exceeding max_fat_arches={}",
limits.max_fat_arches
)));
}
let header = FatHeader::new(magic, nfat_arch);
let mut arches = Vec::with_capacity(nfat_arch as usize);
if magic.is_64bit() {
let arch_size = size_of::<RawFatArch64>();
for i in 0..nfat_arch as usize {
let arch_off = 8usize
.checked_add(i.checked_mul(arch_size).ok_or_else(|| {
Error::format(format!("fat arch {i}: index * stride overflows usize"))
})?)
.ok_or_else(|| {
Error::format(format!("fat arch {i}: header offset overflows usize"))
})?;
let raw: RawFatArch64 = pod::read_pod(data, arch_off)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
let offset = endian.interpret_u64(raw.offset);
let size = endian.interpret_u64(raw.size);
let align = endian.interpret_u32(raw.align);
validate_arch_bounds(data.len(), offset, size, i)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
let (start, end) = arch_bounds_as_usize(offset, size, i)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
let slice = &data[start..end];
let macho = parse_macho_file_with_limits(slice, limits)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
arches.push(
FatArch::try_new(
ArchSpec {
cpu_type: CpuType(endian.interpret_i32(raw.cputype)),
cpu_subtype: CpuSubtype(endian.interpret_i32(raw.cpusubtype)),
},
FatFileOffset(offset),
size,
align,
endian.interpret_u32(raw.reserved),
macho,
data.len(),
)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?,
);
}
} else {
let arch_size = size_of::<RawFatArch32>();
for i in 0..nfat_arch as usize {
let arch_off = 8usize
.checked_add(i.checked_mul(arch_size).ok_or_else(|| {
Error::format(format!("fat arch {i}: index * stride overflows usize"))
})?)
.ok_or_else(|| {
Error::format(format!("fat arch {i}: header offset overflows usize"))
})?;
let raw: RawFatArch32 = pod::read_pod(data, arch_off)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
let offset = endian.interpret_u32(raw.offset) as u64;
let size = endian.interpret_u32(raw.size) as u64;
let align = endian.interpret_u32(raw.align);
validate_arch_bounds(data.len(), offset, size, i)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
let (start, end) = arch_bounds_as_usize(offset, size, i)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
let slice = &data[start..end];
let macho = parse_macho_file_with_limits(slice, limits)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?;
arches.push(
FatArch::try_new(
ArchSpec {
cpu_type: CpuType(endian.interpret_i32(raw.cputype)),
cpu_subtype: CpuSubtype(endian.interpret_i32(raw.cpusubtype)),
},
FatFileOffset(offset),
size,
align,
0,
macho,
data.len(),
)
.map_err(|error| error.with_context(ContextFrame::FatArchitecture { index: i }))?,
);
}
}
FatBinary::try_new(header, arches, data)
}
fn validate_arch_bounds(file_len: usize, offset: u64, size: u64, index: usize) -> Result<()> {
let end = offset
.checked_add(size)
.ok_or_else(|| Error::format(format!("fat arch {index}: offset + size overflows")))?;
if end > file_len as u64 {
return Err(Error::format(format!(
"fat arch {index}: slice {offset:#x}..{end:#x} exceeds file size {file_len:#x}"
)));
}
Ok(())
}
fn arch_bounds_as_usize(offset: u64, size: u64, index: usize) -> Result<(usize, usize)> {
let start = usize::try_from(offset).map_err(|_| {
Error::format(format!(
"fat arch {index}: offset {offset:#x} exceeds addressable memory"
))
})?;
let end_u64 = offset
.checked_add(size)
.ok_or_else(|| Error::format(format!("fat arch {index}: offset + size overflows")))?;
let end = usize::try_from(end_u64).map_err(|_| {
Error::format(format!(
"fat arch {index}: end {end_u64:#x} exceeds addressable memory"
))
})?;
Ok((start, end))
}