use std::collections::HashSet;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TrapTableTarget {
Direct(u32),
Protected {
last_head: u32,
logical_successor: u32,
},
}
#[cfg(test)]
pub(crate) fn resolve_trap_table_target(
raw_target: u32,
read_long: impl FnMut(u32) -> Option<u32>,
) -> Option<TrapTableTarget> {
resolve_trap_table_target_with_provenance(raw_target, read_long, |_| true)
}
pub(crate) fn resolve_trap_table_target_with_provenance(
raw_target: u32,
mut read_long: impl FnMut(u32) -> Option<u32>,
mut is_protected_head: impl FnMut(u32) -> bool,
) -> Option<TrapTableTarget> {
let mut target = raw_target;
let mut last_head = None;
let mut visited = HashSet::new();
loop {
if !is_protected_head(target) || read_long(target) != Some(COME_FROM_PATCH_SIGNATURE) {
return Some(match last_head {
Some(last_head) => TrapTableTarget::Protected {
last_head,
logical_successor: target,
},
None => TrapTableTarget::Direct(target),
});
}
if !visited.insert(target) {
return None;
}
last_head = Some(target);
target = read_long(target.checked_add(4)?)?;
}
}
pub(crate) const OS_TRAP_TABLE_BASE: u32 = 0x0400;
pub(crate) const TOOLBOX_TRAP_TABLE_BASE: u32 = 0x0E00;
pub(crate) const OS_TRAP_TABLE_SLOTS: u16 = 0x0100;
pub(crate) const TOOLBOX_TRAP_TABLE_SLOTS: u16 = 0x0400;
pub(crate) const COME_FROM_PATCH_SIGNATURE: u32 = 0x6006_4EF9;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum OsRoutineVariant {
Unclassified,
CurrentHeap,
SystemHeap,
CurrentHeapClear,
SystemHeapClear,
LowerText,
StripText,
UpperText,
StripUpperText,
TextCompareFoldCaseAndMarks,
TextCompareFoldCase,
TextCompareStripMarks,
TextCompareExact,
UpperStringPreserveMarks,
UpperStringStripMarks,
ParameterBlockSynchronous,
ParameterBlockImmediate,
ParameterBlockAsynchronous,
TimeTaskOriginal,
TimeTaskExtended,
TrapAddressLegacy,
TrapAddressNewOs,
TrapAddressNewTool,
GestaltQuery,
GestaltRegister,
GestaltReplace,
SleepQueueInstall,
SleepQueueRemove,
PowerIdleUpdate,
PowerIdleState,
PowerSerial,
DriverInstall,
DriverInstallReserveMemory,
FileSynchronous,
FileAsynchronous,
FileHfsSynchronous,
FileHfsAsynchronous,
}
impl OsRoutineVariant {
pub(crate) const fn text_comparison_sensitivity(self) -> Option<(bool, bool)> {
match self {
Self::TextCompareFoldCaseAndMarks => Some((false, false)),
Self::TextCompareFoldCase => Some((false, true)),
Self::TextCompareStripMarks => Some((true, false)),
Self::TextCompareExact => Some((true, true)),
_ => None,
}
}
}
const fn classify_os_routine_variant(raw_word: u16) -> OsRoutineVariant {
if (raw_word & 0x0800) != 0 {
return OsRoutineVariant::Unclassified;
}
let slot = raw_word & 0x00FF;
let routine_bits = raw_word & 0x0600;
match (slot, routine_bits) {
(0x1E | 0x22, 0x0000) => OsRoutineVariant::CurrentHeap,
(0x1E | 0x22, 0x0200) => OsRoutineVariant::CurrentHeapClear,
(0x1E | 0x22, 0x0400) => OsRoutineVariant::SystemHeap,
(0x1E | 0x22, 0x0600) => OsRoutineVariant::SystemHeapClear,
(0x1C | 0x1D | 0x27 | 0x28 | 0x40 | 0x4C | 0x4D | 0x61 | 0x62 | 0x66, 0x0000) => {
OsRoutineVariant::CurrentHeap
}
(0x1C | 0x1D | 0x27 | 0x28 | 0x40 | 0x4C | 0x4D | 0x61 | 0x62 | 0x66, 0x0400) => {
OsRoutineVariant::SystemHeap
}
(0x56, 0x0000) => OsRoutineVariant::LowerText,
(0x56, 0x0200) => OsRoutineVariant::StripText,
(0x56, 0x0400) => OsRoutineVariant::UpperText,
(0x56, 0x0600) => OsRoutineVariant::StripUpperText,
(0x3C | 0x50, 0x0000) => OsRoutineVariant::TextCompareFoldCaseAndMarks,
(0x3C | 0x50, 0x0200) => OsRoutineVariant::TextCompareFoldCase,
(0x3C | 0x50, 0x0400) => OsRoutineVariant::TextCompareStripMarks,
(0x3C | 0x50, 0x0600) => OsRoutineVariant::TextCompareExact,
(0x54, 0x0000) => OsRoutineVariant::UpperStringPreserveMarks,
(0x54, 0x0200) => OsRoutineVariant::UpperStringStripMarks,
(0x01..=0x06, 0x0000) => OsRoutineVariant::ParameterBlockSynchronous,
(0x01..=0x06, 0x0200) => OsRoutineVariant::ParameterBlockImmediate,
(0x01..=0x06, 0x0400) => OsRoutineVariant::ParameterBlockAsynchronous,
(0x58, 0x0000) => OsRoutineVariant::TimeTaskOriginal,
(0x58, 0x0400) => OsRoutineVariant::TimeTaskExtended,
(0x46 | 0x47, 0x0000) => OsRoutineVariant::TrapAddressLegacy,
(0x46 | 0x47, 0x0200) => OsRoutineVariant::TrapAddressNewOs,
(0x46 | 0x47, 0x0600) => OsRoutineVariant::TrapAddressNewTool,
(0xAD, 0x0000) => OsRoutineVariant::GestaltQuery,
(0xAD, 0x0200) => OsRoutineVariant::GestaltRegister,
(0xAD, 0x0400) => OsRoutineVariant::GestaltReplace,
(0x8A, 0x0200) => OsRoutineVariant::SleepQueueInstall,
(0x8A, 0x0400) => OsRoutineVariant::SleepQueueRemove,
(0x85, 0x0200) => OsRoutineVariant::PowerIdleUpdate,
(0x85, 0x0400) => OsRoutineVariant::PowerIdleState,
(0x85, 0x0600) => OsRoutineVariant::PowerSerial,
(0x3D, 0x0000) => OsRoutineVariant::DriverInstall,
(0x3D, 0x0400) => OsRoutineVariant::DriverInstallReserveMemory,
(
0x07 | 0x08 | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D | 0x10 | 0x11 | 0x12 | 0x13 | 0x14
| 0x15 | 0x18 | 0x41 | 0x42 | 0x43 | 0x44 | 0x45,
0x0000,
) => OsRoutineVariant::FileSynchronous,
(
0x07 | 0x08 | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D | 0x10 | 0x11 | 0x12 | 0x13 | 0x14
| 0x15 | 0x18 | 0x41 | 0x42 | 0x43 | 0x44 | 0x45,
0x0400,
) => OsRoutineVariant::FileAsynchronous,
(
0x07 | 0x08 | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D | 0x10 | 0x14 | 0x15 | 0x41 | 0x42,
0x0200,
) => OsRoutineVariant::FileHfsSynchronous,
(
0x07 | 0x08 | 0x09 | 0x0A | 0x0B | 0x0C | 0x0D | 0x10 | 0x14 | 0x15 | 0x41 | 0x42,
0x0600,
) => OsRoutineVariant::FileHfsAsynchronous,
_ => OsRoutineVariant::Unclassified,
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct RawTrapRoute {
pub(crate) raw_word: u16,
pub(crate) canonical_word: u16,
pub(crate) table_slot: u16,
pub(crate) table_index: u16,
pub(crate) table_address: u32,
pub(crate) is_toolbox: bool,
pub(crate) os_flags: u16,
pub(crate) os_routine_variant: OsRoutineVariant,
pub(crate) os_returns_a0: bool,
pub(crate) toolbox_auto_pop: bool,
}
const EMPTY_RAW_TRAP_ROUTE: RawTrapRoute = RawTrapRoute {
raw_word: 0,
canonical_word: 0,
table_slot: 0,
table_index: 0,
table_address: 0,
is_toolbox: false,
os_flags: 0,
os_routine_variant: OsRoutineVariant::Unclassified,
os_returns_a0: false,
toolbox_auto_pop: false,
};
const fn generate_raw_trap_routes() -> [RawTrapRoute; 4096] {
let mut routes = [EMPTY_RAW_TRAP_ROUTE; 4096];
let mut low_word = 0u16;
while low_word < 4096 {
let raw_word = 0xA000 | low_word;
let is_toolbox = (raw_word & 0x0800) != 0;
let table_slot = if is_toolbox {
raw_word & 0x03FF
} else {
raw_word & 0x00FF
};
routes[low_word as usize] = RawTrapRoute {
raw_word,
canonical_word: if is_toolbox {
0xA800 | table_slot
} else {
0xA000 | table_slot
},
table_slot,
table_index: if is_toolbox {
OS_TRAP_TABLE_SLOTS + table_slot
} else {
table_slot
},
table_address: if is_toolbox {
TOOLBOX_TRAP_TABLE_BASE + table_slot as u32 * 4
} else {
OS_TRAP_TABLE_BASE + table_slot as u32 * 4
},
is_toolbox,
os_flags: if is_toolbox { 0 } else { raw_word & 0x0700 },
os_routine_variant: classify_os_routine_variant(raw_word),
os_returns_a0: !is_toolbox && (raw_word & 0x0100) != 0,
toolbox_auto_pop: is_toolbox && (raw_word & 0x0400) != 0,
};
low_word += 1;
}
routes
}
const RAW_TRAP_ROUTES: [RawTrapRoute; 4096] = generate_raw_trap_routes();
pub(crate) fn raw_trap_route(trap_word: u16) -> &'static RawTrapRoute {
&RAW_TRAP_ROUTES[usize::from(trap_word & 0x0FFF)]
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TrapTableKind {
Legacy,
OperatingSystem,
Toolbox,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TrapManagerSetError {
InvalidComeFromHead,
UnreadableTable,
MalformedComeFromChain,
WriteRejected,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TrapManagerMemoryOp {
ReadLong(u32),
WriteLong { address: u32, value: u32 },
WriteProtectedLong { address: u32, value: u32 },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TrapManagerMemoryResult {
Long(u32),
Written,
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct TrapManager;
impl TrapManager {
pub(crate) fn canonical_trap_word(trap_word: u16, kind: TrapTableKind) -> u16 {
let trap_num = trap_word & 0x03FF;
let typed_word = match kind {
TrapTableKind::OperatingSystem => 0xA000 | (trap_num & 0x00FF),
TrapTableKind::Toolbox => 0xA800 | trap_num,
TrapTableKind::Legacy => {
if matches!(trap_num, 0x000..=0x04F | 0x054 | 0x057) {
0xA000 | (trap_num & 0x00FF)
} else {
0xA800 | trap_num
}
}
};
raw_trap_route(typed_word).canonical_word
}
pub(crate) fn table_address(trap_word: u16, kind: TrapTableKind) -> u32 {
raw_trap_route(Self::canonical_trap_word(trap_word, kind)).table_address
}
#[cfg(test)]
pub(crate) fn get_address(
trap_word: u16,
kind: TrapTableKind,
mut access: impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult>,
) -> Option<u32> {
let raw = Self::read_long(&mut access, Self::table_address(trap_word, kind))?;
match resolve_trap_table_target(raw, |address| Self::read_long(&mut access, address))? {
TrapTableTarget::Direct(target) => Some(target),
TrapTableTarget::Protected {
logical_successor, ..
} => Some(logical_successor),
}
}
pub(crate) fn get_address_with_provenance(
trap_word: u16,
kind: TrapTableKind,
mut access: impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult>,
mut is_protected_head: impl FnMut(u32) -> bool,
) -> Option<u32> {
let raw = Self::read_long(&mut access, Self::table_address(trap_word, kind))?;
match resolve_trap_table_target_with_provenance(
raw,
|address| Self::read_long(&mut access, address),
&mut is_protected_head,
)? {
TrapTableTarget::Direct(target) => Some(target),
TrapTableTarget::Protected {
logical_successor, ..
} => Some(logical_successor),
}
}
#[cfg(test)]
pub(crate) fn validate_handler(
handler: u32,
mut read_long: impl FnMut(u32) -> Option<u32>,
) -> Result<(), TrapManagerSetError> {
if read_long(handler) == Some(COME_FROM_PATCH_SIGNATURE) {
Err(TrapManagerSetError::InvalidComeFromHead)
} else {
Ok(())
}
}
pub(crate) fn validate_handler_with_provenance(
handler: u32,
mut read_long: impl FnMut(u32) -> Option<u32>,
mut is_protected_head: impl FnMut(u32) -> bool,
) -> Result<(), TrapManagerSetError> {
if is_protected_head(handler) && read_long(handler) == Some(COME_FROM_PATCH_SIGNATURE) {
Err(TrapManagerSetError::InvalidComeFromHead)
} else {
Ok(())
}
}
#[cfg(test)]
pub(crate) fn set_address(
trap_word: u16,
kind: TrapTableKind,
handler: u32,
mut access: impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult>,
) -> Result<(), TrapManagerSetError> {
Self::validate_handler(handler, |address| Self::read_long(&mut access, address))?;
let table_address = Self::table_address(trap_word, kind);
let raw = Self::read_long(&mut access, table_address)
.ok_or(TrapManagerSetError::UnreadableTable)?;
let target =
resolve_trap_table_target(raw, |address| Self::read_long(&mut access, address))
.ok_or(TrapManagerSetError::MalformedComeFromChain)?;
match target {
TrapTableTarget::Direct(_) => Self::write_long(
&mut access,
TrapManagerMemoryOp::WriteLong {
address: table_address,
value: handler,
},
)
.ok_or(TrapManagerSetError::WriteRejected),
TrapTableTarget::Protected { last_head, .. } => {
let link = last_head
.checked_add(4)
.ok_or(TrapManagerSetError::MalformedComeFromChain)?;
Self::write_long(
&mut access,
TrapManagerMemoryOp::WriteProtectedLong {
address: link,
value: handler,
},
)
.ok_or(TrapManagerSetError::WriteRejected)
}
}
}
pub(crate) fn set_address_with_provenance(
trap_word: u16,
kind: TrapTableKind,
handler: u32,
mut access: impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult>,
mut is_protected_head: impl FnMut(u32) -> bool,
) -> Result<(), TrapManagerSetError> {
Self::validate_handler_with_provenance(
handler,
|address| Self::read_long(&mut access, address),
&mut is_protected_head,
)?;
let table_address = Self::table_address(trap_word, kind);
let raw = Self::read_long(&mut access, table_address)
.ok_or(TrapManagerSetError::UnreadableTable)?;
let target = resolve_trap_table_target_with_provenance(
raw,
|address| Self::read_long(&mut access, address),
&mut is_protected_head,
)
.ok_or(TrapManagerSetError::MalformedComeFromChain)?;
match target {
TrapTableTarget::Direct(_) => Self::write_long(
&mut access,
TrapManagerMemoryOp::WriteLong {
address: table_address,
value: handler,
},
)
.ok_or(TrapManagerSetError::WriteRejected),
TrapTableTarget::Protected { last_head, .. } => {
let link = last_head
.checked_add(4)
.ok_or(TrapManagerSetError::MalformedComeFromChain)?;
Self::write_long(
&mut access,
TrapManagerMemoryOp::WriteProtectedLong {
address: link,
value: handler,
},
)
.ok_or(TrapManagerSetError::WriteRejected)
}
}
}
fn read_long(
access: &mut impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult>,
address: u32,
) -> Option<u32> {
match access(TrapManagerMemoryOp::ReadLong(address))? {
TrapManagerMemoryResult::Long(value) => Some(value),
TrapManagerMemoryResult::Written => None,
}
}
fn write_long(
access: &mut impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult>,
operation: TrapManagerMemoryOp,
) -> Option<()> {
match access(operation)? {
TrapManagerMemoryResult::Long(_) => None,
TrapManagerMemoryResult::Written => Some(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn read_write_access<'a>(
words: &'a mut HashMap<u32, u32>,
operations: &'a mut Vec<TrapManagerMemoryOp>,
) -> impl FnMut(TrapManagerMemoryOp) -> Option<TrapManagerMemoryResult> + 'a {
move |operation| {
operations.push(operation);
match operation {
TrapManagerMemoryOp::ReadLong(address) => words
.get(&address)
.copied()
.map(TrapManagerMemoryResult::Long),
TrapManagerMemoryOp::WriteLong { address, value }
| TrapManagerMemoryOp::WriteProtectedLong { address, value } => {
words.insert(address, value);
Some(TrapManagerMemoryResult::Written)
}
}
}
}
#[test]
fn service_get_and_set_follow_a_protected_chain() {
let trap_word = 0xA078;
let table_address = TrapManager::table_address(trap_word, TrapTableKind::OperatingSystem);
let head = 0x0010_0000;
let successor = 0x0020_0000;
let replacement = 0x0030_0000;
let mut words = HashMap::new();
words.insert(table_address, head);
words.insert(head, COME_FROM_PATCH_SIGNATURE);
words.insert(head + 4, successor);
let mut get_operations = Vec::new();
let address = TrapManager::get_address(
trap_word,
TrapTableKind::OperatingSystem,
read_write_access(&mut words, &mut get_operations),
);
assert_eq!(address, Some(successor));
let mut set_operations = Vec::new();
let result = TrapManager::set_address(
trap_word,
TrapTableKind::OperatingSystem,
replacement,
read_write_access(&mut words, &mut set_operations),
);
assert_eq!(result, Ok(()));
assert_eq!(words.get(&table_address), Some(&head));
assert_eq!(words.get(&(head + 4)), Some(&replacement));
assert!(set_operations.iter().any(|operation| matches!(
operation,
TrapManagerMemoryOp::WriteProtectedLong {
address,
value
} if *address == head + 4 && *value == replacement
)));
}
#[test]
fn service_rejects_a_come_from_head_before_writing_the_table() {
let trap_word = 0xA078;
let table_address = TrapManager::table_address(trap_word, TrapTableKind::OperatingSystem);
let head = 0x0010_0000;
let original = 0x0020_0000;
let mut words = HashMap::new();
words.insert(table_address, original);
words.insert(head, COME_FROM_PATCH_SIGNATURE);
let mut operations = Vec::new();
let result = TrapManager::set_address(
trap_word,
TrapTableKind::OperatingSystem,
head,
read_write_access(&mut words, &mut operations),
);
assert_eq!(result, Err(TrapManagerSetError::InvalidComeFromHead));
assert_eq!(words.get(&table_address), Some(&original));
assert!(!operations.iter().any(|operation| matches!(
operation,
TrapManagerMemoryOp::ReadLong(address) if *address == table_address
)));
assert!(!operations.iter().any(|operation| matches!(
operation,
TrapManagerMemoryOp::WriteLong { .. } | TrapManagerMemoryOp::WriteProtectedLong { .. }
)));
}
#[test]
fn service_reports_unreadable_and_malformed_tables_and_rejected_writes() {
let trap_word = 0xA047;
let table_address = TrapManager::table_address(trap_word, TrapTableKind::OperatingSystem);
let mut unreadable_words = HashMap::new();
let mut unreadable_operations = Vec::new();
let unreadable = TrapManager::set_address(
trap_word,
TrapTableKind::OperatingSystem,
0x0020_0000,
read_write_access(&mut unreadable_words, &mut unreadable_operations),
);
assert_eq!(unreadable, Err(TrapManagerSetError::UnreadableTable));
let head = 0x0010_0000;
let mut cyclic_words = HashMap::new();
cyclic_words.insert(table_address, head);
cyclic_words.insert(head, COME_FROM_PATCH_SIGNATURE);
cyclic_words.insert(head + 4, head);
let mut cyclic_operations = Vec::new();
let malformed = TrapManager::set_address(
trap_word,
TrapTableKind::OperatingSystem,
0x0020_0000,
read_write_access(&mut cyclic_words, &mut cyclic_operations),
);
assert_eq!(malformed, Err(TrapManagerSetError::MalformedComeFromChain));
let original = 0x0020_0000;
let replacement = 0x0030_0000;
let mut rejected_words = HashMap::new();
rejected_words.insert(table_address, original);
let rejected = TrapManager::set_address(
trap_word,
TrapTableKind::OperatingSystem,
replacement,
|operation| match operation {
TrapManagerMemoryOp::ReadLong(address) => rejected_words
.get(&address)
.copied()
.map(TrapManagerMemoryResult::Long),
TrapManagerMemoryOp::WriteLong { .. }
| TrapManagerMemoryOp::WriteProtectedLong { .. } => None,
},
);
assert_eq!(rejected, Err(TrapManagerSetError::WriteRejected));
assert_eq!(rejected_words.get(&table_address), Some(&original));
}
#[test]
fn provenance_keeps_a_signature_in_writable_memory_as_a_direct_target() {
let head = 0x0010_0000;
let successor = 0x0020_0000;
let read = |address| match address {
address if address == head => Some(COME_FROM_PATCH_SIGNATURE),
address if address == head + 4 => Some(successor),
_ => None,
};
assert_eq!(
resolve_trap_table_target_with_provenance(head, read, |_| false),
Some(TrapTableTarget::Direct(head))
);
assert_eq!(
resolve_trap_table_target_with_provenance(head, read, |address| address == head),
Some(TrapTableTarget::Protected {
last_head: head,
logical_successor: successor,
})
);
}
}