use crate::{
ptrauth::{PacKey, PtrAuth},
util::{read_u16_le_at, read_u32_le_at, read_u64_le_at},
};
const SIZEOF_CHAINED_FIXUPS_HEADER: usize = 28;
const SIZEOF_STARTS_IN_SEGMENT_HEADER: usize = 22;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerFormat {
Arm64e,
Ptr64,
Ptr64Offset,
Arm64eKernel,
Arm64eUserland,
Arm64eUserland24,
Arm64eSharedCache,
Other(u16),
}
impl PointerFormat {
#[inline]
pub fn from_raw(raw: u16) -> Self {
match raw {
1 => Self::Arm64e,
2 => Self::Ptr64,
6 => Self::Ptr64Offset,
7 => Self::Arm64eKernel,
9 => Self::Arm64eUserland,
12 => Self::Arm64eUserland24,
13 => Self::Arm64eSharedCache,
other => Self::Other(other),
}
}
#[inline]
pub fn is_supported(self) -> bool {
!matches!(self, Self::Other(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImportsFormat {
Plain,
Addend,
Addend64,
Other(u32),
}
impl ImportsFormat {
#[inline]
pub fn from_raw(raw: u32) -> Self {
match raw {
1 => Self::Plain,
2 => Self::Addend,
3 => Self::Addend64,
other => Self::Other(other),
}
}
#[inline]
pub fn entry_size(self) -> Option<usize> {
match self {
Self::Plain => Some(4),
Self::Addend => Some(8),
Self::Addend64 => Some(16),
Self::Other(_) => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ChainedSegment {
pub seg_index: u32,
pub size: u32,
pub page_size: u16,
pub pointer_format: PointerFormat,
pub raw_pointer_format: u16,
pub segment_offset: u64,
pub max_valid_pointer: u32,
pub page_count: u16,
#[allow(dead_code)]
pub(crate) starts_offset: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct ChainedImport<'a> {
pub lib_ordinal: i16,
pub weak_import: bool,
pub name: &'a str,
pub addend: i64,
}
#[derive(Debug, Clone, Copy)]
pub struct ChainedFixups<'a> {
data: &'a [u8],
base: usize,
fixups_version: u32,
starts_offset: u32,
imports_offset: u32,
symbols_offset: u32,
imports_count: u32,
imports_format: ImportsFormat,
raw_imports_format: u32,
symbols_format: u32,
}
impl<'a> ChainedFixups<'a> {
pub fn parse(data: &'a [u8], base: usize, size: usize) -> Option<Self> {
let end = base.checked_add(size)?;
let payload = data.get(base..end)?;
if payload.len() < SIZEOF_CHAINED_FIXUPS_HEADER {
return None;
}
let fixups_version = read_u32_le_at(payload, 0)?;
let starts_offset = read_u32_le_at(payload, 4)?;
let imports_offset = read_u32_le_at(payload, 8)?;
let symbols_offset = read_u32_le_at(payload, 12)?;
let imports_count = read_u32_le_at(payload, 16)?;
let raw_imports_format = read_u32_le_at(payload, 20)?;
let symbols_format = read_u32_le_at(payload, 24)?;
let payload_len = u32::try_from(payload.len()).ok()?;
if starts_offset >= payload_len
|| imports_offset > payload_len
|| symbols_offset > payload_len
{
return None;
}
Some(Self {
data,
base,
fixups_version,
starts_offset,
imports_offset,
symbols_offset,
imports_count,
imports_format: ImportsFormat::from_raw(raw_imports_format),
raw_imports_format,
symbols_format,
})
}
pub fn version(&self) -> u32 {
self.fixups_version
}
pub fn imports_count(&self) -> u32 {
self.imports_count
}
pub fn imports_format(&self) -> ImportsFormat {
self.imports_format
}
pub fn raw_imports_format(&self) -> u32 {
self.raw_imports_format
}
pub fn symbols_format(&self) -> u32 {
self.symbols_format
}
pub fn segments(&self) -> ChainedSegmentIter<'a> {
let starts_in_image_off = self.base.saturating_add(self.starts_offset as usize);
let seg_count = read_u32_le_at(self.data, starts_in_image_off).unwrap_or(0);
ChainedSegmentIter {
data: self.data,
starts_in_image_off,
seg_count,
cursor: 0,
}
}
pub fn imports(&self) -> ChainedImportIter<'a> {
let stride = match self.imports_format.entry_size() {
Some(s) => s,
None => return ChainedImportIter::empty(),
};
let imports_base = match self.base.checked_add(self.imports_offset as usize) {
Some(v) => v,
None => return ChainedImportIter::empty(),
};
let symbols_base = match self.base.checked_add(self.symbols_offset as usize) {
Some(v) => v,
None => return ChainedImportIter::empty(),
};
ChainedImportIter {
data: self.data,
imports_base,
symbols_base,
stride,
format: self.imports_format,
count: self.imports_count,
cursor: 0,
}
}
}
pub struct ChainedSegmentIter<'a> {
data: &'a [u8],
starts_in_image_off: usize,
seg_count: u32,
cursor: u32,
}
impl<'a> Iterator for ChainedSegmentIter<'a> {
type Item = ChainedSegment;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.cursor >= self.seg_count {
return None;
}
let i = self.cursor;
self.cursor = self.cursor.checked_add(1)?;
let entry_base = self
.starts_in_image_off
.checked_add(4)?
.checked_add((i as usize).checked_mul(4)?)?;
let seg_info_offset = read_u32_le_at(self.data, entry_base)?;
if seg_info_offset == 0 {
continue;
}
let starts_off = self
.starts_in_image_off
.checked_add(seg_info_offset as usize)?;
let header = self.data.get(starts_off..)?;
if header.len() < SIZEOF_STARTS_IN_SEGMENT_HEADER {
continue;
}
let size = read_u32_le_at(header, 0)?;
let page_size = read_u16_le_at(header, 4)?;
let raw_format = read_u16_le_at(header, 6)?;
let segment_offset = read_u64_le_at(header, 8)?;
let max_valid_pointer = read_u32_le_at(header, 16)?;
let page_count = read_u16_le_at(header, 20)?;
return Some(ChainedSegment {
seg_index: i,
size,
page_size,
pointer_format: PointerFormat::from_raw(raw_format),
raw_pointer_format: raw_format,
segment_offset,
max_valid_pointer,
page_count,
starts_offset: starts_off,
});
}
}
}
pub struct ChainedImportIter<'a> {
data: &'a [u8],
imports_base: usize,
symbols_base: usize,
stride: usize,
format: ImportsFormat,
count: u32,
cursor: u32,
}
impl<'a> ChainedImportIter<'a> {
fn empty() -> Self {
Self {
data: &[],
imports_base: 0,
symbols_base: 0,
stride: 0,
format: ImportsFormat::Other(0),
count: 0,
cursor: 0,
}
}
}
impl<'a> Iterator for ChainedImportIter<'a> {
type Item = ChainedImport<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.count {
return None;
}
let i = self.cursor;
self.cursor = self.cursor.checked_add(1)?;
let entry_base = self
.imports_base
.checked_add((i as usize).checked_mul(self.stride)?)?;
let entry_end = entry_base.checked_add(self.stride)?;
let entry = self.data.get(entry_base..entry_end)?;
let (lib_ordinal, weak, name_off_in_pool, addend) = match self.format {
ImportsFormat::Plain => decode_import_plain(entry)?,
ImportsFormat::Addend => decode_import_addend(entry)?,
ImportsFormat::Addend64 => decode_import_addend64(entry)?,
ImportsFormat::Other(_) => return None,
};
let name = read_pool_string(self.data, self.symbols_base, name_off_in_pool).unwrap_or("");
Some(ChainedImport {
lib_ordinal,
weak_import: weak,
name,
addend,
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct Rebase {
seg_index: u32,
segment_offset: u64,
vm_address: u64,
target_vmaddr: u64,
raw_slot: u64,
ptr_auth: Option<PtrAuth>,
high8: Option<u8>,
pointer_format: PointerFormat,
}
impl Rebase {
pub fn segment_index(&self) -> u32 {
self.seg_index
}
pub fn segment_offset(&self) -> u64 {
self.segment_offset
}
pub fn vm_address(&self) -> u64 {
self.vm_address
}
pub fn target_vmaddr(&self) -> u64 {
self.target_vmaddr
}
pub fn raw_slot(&self) -> u64 {
self.raw_slot
}
pub fn ptr_auth(&self) -> Option<PtrAuth> {
self.ptr_auth
}
pub fn high8(&self) -> Option<u8> {
self.high8
}
pub fn pointer_format(&self) -> PointerFormat {
self.pointer_format
}
}
#[derive(Debug, Clone, Copy)]
pub struct Bind<'a> {
seg_index: u32,
segment_offset: u64,
vm_address: u64,
import_ordinal: u32,
addend: i64,
name: &'a str,
dylib: &'a str,
is_weak: bool,
ptr_auth: Option<PtrAuth>,
pointer_format: PointerFormat,
raw_slot: u64,
}
impl<'a> Bind<'a> {
pub fn segment_index(&self) -> u32 {
self.seg_index
}
pub fn segment_offset(&self) -> u64 {
self.segment_offset
}
pub fn vm_address(&self) -> u64 {
self.vm_address
}
pub fn import_ordinal(&self) -> u32 {
self.import_ordinal
}
pub fn addend(&self) -> i64 {
self.addend
}
pub fn name(&self) -> &'a str {
self.name
}
pub fn dylib(&self) -> &'a str {
self.dylib
}
pub fn is_weak(&self) -> bool {
self.is_weak
}
pub fn ptr_auth(&self) -> Option<PtrAuth> {
self.ptr_auth
}
pub fn pointer_format(&self) -> PointerFormat {
self.pointer_format
}
pub fn raw_slot(&self) -> u64 {
self.raw_slot
}
}
pub struct RebaseIter<'a> {
inner: std::vec::IntoIter<Rebase>,
_phantom: core::marker::PhantomData<&'a [u8]>,
}
impl<'a> RebaseIter<'a> {
pub(crate) fn empty() -> Self {
Self {
inner: Vec::new().into_iter(),
_phantom: core::marker::PhantomData,
}
}
}
impl<'a> Iterator for RebaseIter<'a> {
type Item = Rebase;
fn next(&mut self) -> Option<Rebase> {
self.inner.next()
}
}
pub struct BindIter<'a> {
inner: std::vec::IntoIter<Bind<'a>>,
}
impl<'a> BindIter<'a> {
pub(crate) fn empty() -> Self {
Self {
inner: Vec::new().into_iter(),
}
}
}
impl<'a> Iterator for BindIter<'a> {
type Item = Bind<'a>;
fn next(&mut self) -> Option<Bind<'a>> {
self.inner.next()
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SegmentLoc {
pub chained: ChainedSegment,
pub vmaddr: u64,
pub fileoff: u64,
pub filesize: u64,
}
pub(crate) fn build_rebase_iter<'a>(
data: &'a [u8],
cf: &ChainedFixups<'a>,
locs: &[SegmentLoc],
image_base: u64,
) -> RebaseIter<'a> {
let mut out: Vec<Rebase> = Vec::new();
for loc in locs {
decode_segment_chains(data, loc, image_base, &mut out, &mut Vec::new(), &[], &[]);
}
RebaseIter {
inner: out.into_iter(),
_phantom: core::marker::PhantomData,
}
.skip_unused(cf)
}
impl<'a> RebaseIter<'a> {
fn skip_unused(self, _cf: &ChainedFixups<'a>) -> Self {
self
}
}
pub(crate) fn build_bind_iter<'a>(
data: &'a [u8],
_cf: &ChainedFixups<'a>,
locs: &[SegmentLoc],
image_base: u64,
imports: &[ChainedImport<'a>],
dylib_names: &[&'a str],
) -> BindIter<'a> {
let mut binds: Vec<Bind<'a>> = Vec::new();
let mut throwaway_rebases: Vec<Rebase> = Vec::new();
for loc in locs {
decode_segment_chains(
data,
loc,
image_base,
&mut throwaway_rebases,
&mut binds,
imports,
dylib_names,
);
}
BindIter {
inner: binds.into_iter(),
}
}
const PTR_START_NONE: u16 = 0xFFFF;
const PTR_START_MULTI: u16 = 0x8000;
fn decode_segment_chains<'a>(
data: &'a [u8],
loc: &SegmentLoc,
image_base: u64,
rebases_out: &mut Vec<Rebase>,
binds_out: &mut Vec<Bind<'a>>,
imports: &[ChainedImport<'a>],
dylib_names: &[&'a str],
) {
if !loc.chained.pointer_format.is_supported() {
return;
}
let page_starts_off = loc
.chained
.starts_offset
.saturating_add(SIZEOF_STARTS_IN_SEGMENT_HEADER);
for page_idx in 0..loc.chained.page_count {
let entry_off = match (page_idx as usize)
.checked_mul(2)
.and_then(|x| page_starts_off.checked_add(x))
{
Some(o) => o,
None => return,
};
let raw_start = match read_u16_le_at(data, entry_off) {
Some(v) => v,
None => return,
};
if raw_start == PTR_START_NONE {
continue;
}
if (raw_start & PTR_START_MULTI) != 0 {
continue;
}
let mut chain_off = raw_start as u64;
#[allow(clippy::while_let_loop)]
loop {
let page_byte_off = match (page_idx as u64)
.checked_mul(loc.chained.page_size as u64)
.and_then(|x| x.checked_add(chain_off))
{
Some(v) => v,
None => break,
};
if page_byte_off >= loc.filesize {
break;
}
let abs_off = match loc.fileoff.checked_add(page_byte_off) {
Some(v) => v,
None => break,
};
let abs_off_usize = match usize::try_from(abs_off) {
Ok(v) => v,
Err(_) => break,
};
let slot = match read_u64_le_at(data, abs_off_usize) {
Some(v) => v,
None => break,
};
let segment_offset = page_byte_off;
let vm_address = loc.vmaddr.saturating_add(segment_offset);
let next_stride: u64;
let ctx = SlotCtx {
image_base,
seg_index: loc.chained.seg_index,
segment_offset,
vm_address,
imports,
dylib_names,
};
match decode_chain_entry(slot, loc.chained.pointer_format, &ctx) {
Some((entry, next)) => {
next_stride = next;
match entry {
Entry::Rebase(r) => rebases_out.push(r),
Entry::Bind(b) => binds_out.push(b),
}
}
None => break,
}
if next_stride == 0 {
break;
}
let stride_bytes = match loc.chained.pointer_format {
PointerFormat::Arm64e
| PointerFormat::Arm64eUserland
| PointerFormat::Arm64eUserland24
| PointerFormat::Arm64eSharedCache => 8u64,
PointerFormat::Ptr64 | PointerFormat::Ptr64Offset | PointerFormat::Arm64eKernel => {
4u64
}
PointerFormat::Other(_) => break,
};
let advance = next_stride.saturating_mul(stride_bytes);
chain_off = chain_off.saturating_add(advance);
}
}
}
enum Entry<'a> {
Rebase(Rebase),
Bind(Bind<'a>),
}
struct SlotCtx<'a, 'b> {
image_base: u64,
seg_index: u32,
segment_offset: u64,
vm_address: u64,
imports: &'b [ChainedImport<'a>],
dylib_names: &'b [&'a str],
}
fn decode_chain_entry<'a>(
slot: u64,
format: PointerFormat,
ctx: &SlotCtx<'a, '_>,
) -> Option<(Entry<'a>, u64)> {
match format {
PointerFormat::Ptr64 | PointerFormat::Ptr64Offset => {
let bind_bit = (slot.wrapping_shr(63)) & 0x1;
let next = (slot.wrapping_shr(51)) & 0x0fff;
if bind_bit == 0 {
let target = slot & 0x0000_000f_ffff_ffff; let high8 = ((slot.wrapping_shr(36)) & 0xff) as u8;
let target_vmaddr = if matches!(format, PointerFormat::Ptr64Offset) {
ctx.image_base.saturating_add(target)
} else {
target
};
let r = Rebase {
seg_index: ctx.seg_index,
segment_offset: ctx.segment_offset,
vm_address: ctx.vm_address,
target_vmaddr,
raw_slot: slot,
ptr_auth: None,
high8: Some(high8),
pointer_format: format,
};
Some((Entry::Rebase(r), next))
} else {
let ordinal = (slot & 0x00ff_ffff) as u32; let slot_addend = ((slot.wrapping_shr(24)) & 0xff) as u8 as i64;
let (name, dylib, is_weak, import_addend) =
resolve_import(ordinal, ctx.imports, ctx.dylib_names);
let b = Bind {
seg_index: ctx.seg_index,
segment_offset: ctx.segment_offset,
vm_address: ctx.vm_address,
import_ordinal: ordinal,
addend: slot_addend.saturating_add(import_addend),
name,
dylib,
is_weak,
ptr_auth: None,
pointer_format: format,
raw_slot: slot,
};
Some((Entry::Bind(b), next))
}
}
PointerFormat::Arm64e
| PointerFormat::Arm64eUserland
| PointerFormat::Arm64eUserland24
| PointerFormat::Arm64eKernel
| PointerFormat::Arm64eSharedCache => decode_arm64e_slot(slot, format, ctx),
PointerFormat::Other(_) => None,
}
}
fn decode_arm64e_slot<'a>(
slot: u64,
format: PointerFormat,
ctx: &SlotCtx<'a, '_>,
) -> Option<(Entry<'a>, u64)> {
let auth_bit = (slot.wrapping_shr(63)) & 0x1;
let bind_bit = (slot.wrapping_shr(62)) & 0x1;
let next = (slot.wrapping_shr(51)) & 0x07ff;
let pa = if auth_bit != 0 {
Some(PtrAuth {
diversity: ((slot.wrapping_shr(32)) & 0xffff) as u16,
addr_div: ((slot.wrapping_shr(48)) & 0x1) != 0,
key: PacKey::from_bits(((slot.wrapping_shr(49)) & 0x3) as u8),
})
} else {
None
};
if bind_bit == 0 {
let target_vmaddr = if auth_bit != 0 {
let target = slot & 0xffff_ffff;
ctx.image_base.saturating_add(target)
} else {
let target = slot & 0x0000_07ff_ffff_ffff;
match format {
PointerFormat::Arm64e => target, _ => ctx.image_base.saturating_add(target),
}
};
let high8 = if auth_bit == 0 {
Some(((slot.wrapping_shr(43)) & 0xff) as u8)
} else {
None
};
let r = Rebase {
seg_index: ctx.seg_index,
segment_offset: ctx.segment_offset,
vm_address: ctx.vm_address,
target_vmaddr,
raw_slot: slot,
ptr_auth: pa,
high8,
pointer_format: format,
};
Some((Entry::Rebase(r), next))
} else {
let (ordinal, addend_bits) = match format {
PointerFormat::Arm64eUserland24 => (
(slot & 0x00ff_ffff) as u32, if auth_bit != 0 {
0i64
} else {
extract_signed_19(slot)
},
),
_ => (
(slot & 0xffff) as u32, if auth_bit != 0 {
0i64
} else {
extract_signed_19(slot)
},
),
};
let (name, dylib, is_weak, import_addend) =
resolve_import(ordinal, ctx.imports, ctx.dylib_names);
let b = Bind {
seg_index: ctx.seg_index,
segment_offset: ctx.segment_offset,
vm_address: ctx.vm_address,
import_ordinal: ordinal,
addend: addend_bits.saturating_add(import_addend),
name,
dylib,
is_weak,
ptr_auth: pa,
pointer_format: format,
raw_slot: slot,
};
Some((Entry::Bind(b), next))
}
}
fn extract_signed_19(slot: u64) -> i64 {
let raw = (slot.wrapping_shr(32)) & 0x0007_ffff;
if (raw & 0x0004_0000) != 0 {
(raw | 0xffff_ffff_fff8_0000) as i64
} else {
raw as i64
}
}
fn resolve_import<'a>(
ordinal: u32,
imports: &[ChainedImport<'a>],
dylib_names: &[&'a str],
) -> (&'a str, &'a str, bool, i64) {
let idx = ordinal as usize;
let import = match imports.get(idx) {
Some(i) => *i,
None => {
return ("", "", false, 0);
}
};
let dylib = resolve_lib_ordinal(import.lib_ordinal, dylib_names);
(import.name, dylib, import.weak_import, import.addend)
}
pub(crate) fn resolve_lib_ordinal<'a>(ordinal: i16, dylib_names: &[&'a str]) -> &'a str {
match ordinal {
n if n > 0 => {
let idx = (n as usize).saturating_sub(1);
dylib_names.get(idx).copied().unwrap_or("")
}
0 => "self",
-1 => "main-executable",
-2 => "flat-namespace",
-3 => "weak",
_ => "",
}
}
fn decode_import_plain(entry: &[u8]) -> Option<(i16, bool, u32, i64)> {
let raw = read_u32_le_at(entry, 0)?;
let lib_ordinal_raw = (raw & 0xff) as u8;
let weak = (raw & 0x100) != 0;
let name_off = raw.wrapping_shr(9);
Some((sign_extend_i8(lib_ordinal_raw), weak, name_off, 0))
}
fn decode_import_addend(entry: &[u8]) -> Option<(i16, bool, u32, i64)> {
let raw = read_u32_le_at(entry, 0)?;
let lib_ordinal_raw = (raw & 0xff) as u8;
let weak = (raw & 0x100) != 0;
let name_off = raw.wrapping_shr(9);
let addend = read_u32_le_at(entry, 4)? as i32 as i64;
Some((sign_extend_i8(lib_ordinal_raw), weak, name_off, addend))
}
fn decode_import_addend64(entry: &[u8]) -> Option<(i16, bool, u32, i64)> {
let lib_ordinal = read_u16_le_at(entry, 0)? as i16;
let flags = read_u16_le_at(entry, 2)?;
let weak = (flags & 0x1) != 0;
let name_off = read_u32_le_at(entry, 4)?;
let addend = read_u64_le_at(entry, 8)? as i64;
Some((lib_ordinal, weak, name_off, addend))
}
fn sign_extend_i8(raw: u8) -> i16 {
raw as i8 as i16
}
fn read_pool_string(data: &[u8], pool_base: usize, name_off: u32) -> Option<&str> {
let start = pool_base.checked_add(name_off as usize)?;
crate::util::read_cstr_at(data, start)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pointer_format_round_trip() {
for (raw, want) in [
(1u16, PointerFormat::Arm64e),
(2, PointerFormat::Ptr64),
(6, PointerFormat::Ptr64Offset),
(7, PointerFormat::Arm64eKernel),
(9, PointerFormat::Arm64eUserland),
(12, PointerFormat::Arm64eUserland24),
(13, PointerFormat::Arm64eSharedCache),
] {
assert_eq!(PointerFormat::from_raw(raw), want);
assert!(want.is_supported());
}
for raw in [0u16, 3, 4, 5, 8, 10, 11, 14, 999] {
assert_eq!(PointerFormat::from_raw(raw), PointerFormat::Other(raw));
assert!(!PointerFormat::Other(raw).is_supported());
}
}
#[test]
fn imports_format_entry_size() {
assert_eq!(ImportsFormat::Plain.entry_size(), Some(4));
assert_eq!(ImportsFormat::Addend.entry_size(), Some(8));
assert_eq!(ImportsFormat::Addend64.entry_size(), Some(16));
assert_eq!(ImportsFormat::Other(99).entry_size(), None);
}
#[test]
fn import_plain_decodes_lib_ordinal_weak_and_name_offset() {
let entry = [0x01, 0x0a, 0x00, 0x00];
let (lib, weak, name_off, addend) = decode_import_plain(&entry).unwrap();
assert_eq!(lib, 1);
assert!(!weak);
assert_eq!(name_off, 5);
assert_eq!(addend, 0);
}
#[test]
fn import_plain_sign_extends_special_lib_ordinal() {
let entry = [0xfe, 0x01, 0x00, 0x00];
let (lib, weak, name_off, _) = decode_import_plain(&entry).unwrap();
assert_eq!(lib, -2);
assert!(weak);
assert_eq!(name_off, 0);
}
#[test]
fn import_addend_carries_signed_addend() {
let entry = [0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff];
let (lib, _, _, addend) = decode_import_addend(&entry).unwrap();
assert_eq!(lib, 1);
assert_eq!(addend, -1);
}
#[test]
fn import_addend64_decodes_full_fields() {
let mut entry = [0u8; 16];
entry[0..2].copy_from_slice(&0x1234u16.to_le_bytes());
entry[2..4].copy_from_slice(&0x0001u16.to_le_bytes());
entry[4..8].copy_from_slice(&0x10u32.to_le_bytes());
entry[8..16].copy_from_slice(&0xdead_beef_cafe_f00du64.to_le_bytes());
let (lib, weak, name_off, addend) = decode_import_addend64(&entry).unwrap();
assert_eq!(lib, 0x1234i16);
assert!(weak);
assert_eq!(name_off, 0x10);
assert_eq!(addend as u64, 0xdead_beef_cafe_f00d);
}
fn empty_ctx<'a, 'b>() -> SlotCtx<'a, 'b> {
SlotCtx {
image_base: 0x1_0000_0000,
seg_index: 3,
segment_offset: 0x80,
vm_address: 0x1_0001_0080,
imports: &[],
dylib_names: &[],
}
}
#[test]
fn arm64e_unauth_rebase_arm64e_format_uses_va_directly() {
let slot: u64 = 0x0008_0000_1000_4000;
let (entry, next) = decode_arm64e_slot(slot, PointerFormat::Arm64e, &empty_ctx()).unwrap();
match entry {
Entry::Rebase(r) => {
assert_eq!(r.target_vmaddr(), 0x1000_4000);
assert_eq!(r.high8(), Some(0));
assert!(r.ptr_auth().is_none());
}
_ => panic!("expected rebase"),
}
assert_eq!(next, 1);
}
#[test]
fn arm64e_unauth_rebase_userland_adds_image_base() {
let slot: u64 = 0x0008_0000_0000_4000;
let (entry, _) =
decode_arm64e_slot(slot, PointerFormat::Arm64eUserland, &empty_ctx()).unwrap();
match entry {
Entry::Rebase(r) => assert_eq!(r.target_vmaddr(), 0x1_0000_4000),
_ => panic!("expected rebase"),
}
}
#[test]
fn arm64e_auth_rebase_extracts_pac_metadata() {
let slot: u64 = (0x0000_4000u64)
| (0xBEEFu64 << 32)
| (1u64 << 48)
| (0b10u64 << 49)
| (1u64 << 51)
| (1u64 << 63);
let (entry, next) =
decode_arm64e_slot(slot, PointerFormat::Arm64eUserland, &empty_ctx()).unwrap();
match entry {
Entry::Rebase(r) => {
let pa = r.ptr_auth().expect("auth bit set ⇒ ptr_auth Some");
assert_eq!(pa.diversity, 0xBEEF);
assert!(pa.addr_div);
assert_eq!(pa.key, PacKey::DA);
assert_eq!(r.target_vmaddr(), 0x1_0000_4000);
assert!(r.high8().is_none(), "auth slots have no high8");
}
_ => panic!("expected rebase"),
}
assert_eq!(next, 1);
}
#[test]
fn arm64e_auth_bind_carries_pac_and_zero_addend() {
let slot: u64 = 5u64 | (2u64 << 51) | (1u64 << 62) | (1u64 << 63);
let (entry, next) =
decode_arm64e_slot(slot, PointerFormat::Arm64eUserland, &empty_ctx()).unwrap();
match entry {
Entry::Bind(b) => {
assert_eq!(b.import_ordinal(), 5);
assert_eq!(b.addend(), 0, "auth_bind carries no slot addend");
let pa = b.ptr_auth().unwrap();
assert_eq!(pa.key, PacKey::IA);
}
_ => panic!("expected bind"),
}
assert_eq!(next, 2);
}
#[test]
fn arm64e_userland24_bind_uses_24_bit_ordinal() {
let slot: u64 = 0x00ab_cdefu64 | (1u64 << 51) | (1u64 << 62);
let (entry, _) =
decode_arm64e_slot(slot, PointerFormat::Arm64eUserland24, &empty_ctx()).unwrap();
match entry {
Entry::Bind(b) => assert_eq!(b.import_ordinal(), 0x00ab_cdef),
_ => panic!("expected bind"),
}
}
#[test]
fn arm64e_unauth_bind_addend_sign_extends_19_bits() {
let slot: u64 = 1u64 | (0x7ffffu64 << 32) | (1u64 << 51) | (1u64 << 62);
let (entry, _) =
decode_arm64e_slot(slot, PointerFormat::Arm64eUserland, &empty_ctx()).unwrap();
match entry {
Entry::Bind(b) => assert_eq!(b.addend(), -1),
_ => panic!("expected bind"),
}
}
#[test]
fn read_pool_string_truncates_at_nul_and_handles_overrun() {
let pool = b"hello\0world\0";
assert_eq!(read_pool_string(pool, 0, 0), Some("hello"));
assert_eq!(read_pool_string(pool, 0, 6), Some("world"));
assert_eq!(read_pool_string(pool, 0, 11), Some(""));
assert_eq!(read_pool_string(pool, 0, 999), None);
}
}