// Copyright 2015 Ted Mielczarek. See the COPYRIGHT // file at the top-level directory of this distribution. // NOTE: we don't bother testing arm64_old, it should have identical code at // all times! use crate::*; use minidump::system_info::{Cpu, Os}; use std::collections::HashMap; use test_assembler::*; type Context = minidump::format::CONTEXT_ARM64; struct TestFixture { pub raw: Context, pub modules: MinidumpModuleList, pub symbols: HashMap, } impl TestFixture { pub fn new() -> TestFixture { TestFixture { raw: Context::default(), // Give the two modules reasonable standard locations and names // for tests to play with. modules: MinidumpModuleList::from_modules(vec![ MinidumpModule::new(0x40000000, 0x10000, "module1"), MinidumpModule::new(0x50000000, 0x10000, "module2"), ]), symbols: HashMap::new(), } } pub fn high_module() -> TestFixture { TestFixture { raw: Context::default(), // Same as new but with a really high module to stretch ptr auth stripping modules: MinidumpModuleList::from_modules(vec![ MinidumpModule::new(0x40000000, 0x10000, "module1"), MinidumpModule::new(0x50000000, 0x10000, "module2"), MinidumpModule::new(0x10000000000000, 0x10000, "high-module"), ]), symbols: HashMap::new(), } } pub fn highest_module() -> TestFixture { TestFixture { raw: Context::default(), // Same as new but with a module so high it sets the maximum address bit // effectively disabling stripping modules: MinidumpModuleList::from_modules(vec![ MinidumpModule::new(0x40000000, 0x10000, "module1"), MinidumpModule::new(0x50000000, 0x10000, "module2"), MinidumpModule::new(0xa000_0000_0000_0000, 0x10000, "highest-module"), ]), symbols: HashMap::new(), } } pub async fn walk_stack(&self, stack: Section) -> CallStack { let context = MinidumpContext { raw: MinidumpRawContext::Arm64(self.raw.clone()), valid: MinidumpContextValidity::All, }; let base = stack.start().value().unwrap(); let size = stack.size(); let stack = stack.get_contents().unwrap(); let stack_memory = MinidumpMemory { desc: Default::default(), base_address: base, size, bytes: &stack, endian: scroll::LE, }; let system_info = SystemInfo { os: Os::Windows, os_version: None, os_build: None, cpu: Cpu::Arm64, cpu_info: None, cpu_microcode_version: None, cpu_count: 1, }; let symbolizer = Symbolizer::new(string_symbol_supplier(self.symbols.clone())); let mut stack = CallStack::with_context(context); walk_stack( 0, (), &mut stack, Some(UnifiedMemory::Memory(&stack_memory)), &self.modules, &system_info, &symbolizer, ) .await; stack } pub fn add_symbols(&mut self, name: String, symbols: String) { self.symbols.insert(name, symbols); } } #[tokio::test] async fn test_simple() { let mut f = TestFixture::new(); let stack = Section::new(); stack.start().set_const(0x80000000); // There should be no references to the stack in this walk: we don't // provide any call frame information, so trying to reconstruct the // context frame's caller should fail. So there's no need for us to // provide stack contents. f.raw.set_register("pc", 0x4000c020); f.raw.set_register("fp", 0x80000000); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 1); let f = &s.frames[0]; let m = f.module.as_ref().unwrap(); assert_eq!(m.code_file(), "module1"); } #[tokio::test] async fn test_scan_without_symbols() { // Scanning should work without any symbols let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let frame1_sp = Label::new(); let frame2_sp = Label::new(); stack = stack // frame 0 .append_repeated(0, 16) // space .D64(0x40090000) // junk that's not .D64(0x60000000) // a return address .D64(return_address1) // actual return address // frame 1 .mark(&frame1_sp) .append_repeated(0, 16) // space .D64(0xF0000000) // more junk .D64(0x0000000D) .D64(return_address2) // actual return address // frame 2 .mark(&frame2_sp) .append_repeated(0, 64); // end of stack f.raw.set_register("pc", 0x40005510); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 3); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::Scan); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 2); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() ); } else { unreachable!(); } } { // Frame 2 let frame = &s.frames[2]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::Scan); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 2); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame2_sp.value().unwrap() ); } else { unreachable!(); } } } #[tokio::test] async fn test_scan_with_symbols() { // Test that we can refine our scanning using symbols. Specifically we // should be able to reject pointers that are in modules but don't map to // any FUNC/PUBLIC record. let mut f = TestFixture::new(); let mut stack = Section::new(); let stack_start = 0x80000000; stack.start().set_const(stack_start); let return_address = 0x50000200; let frame1_sp = Label::new(); stack = stack // frame 0 .append_repeated(0, 16) // space .D64(0x40090000) // junk that's not .D64(0x60000000) // a return address .D64(0x40001000) // a couple of plausible addresses .D64(0x5000F000) // that are not within functions .D64(return_address) // actual return address // frame 1 .mark(&frame1_sp) .append_repeated(0, 64); // end of stack f.raw.set_register("pc", 0x40000200); f.raw.set_register("sp", stack.start().value().unwrap()); f.add_symbols( String::from("module1"), // The youngest frame's function. String::from("FUNC 100 400 10 monotreme\n"), ); f.add_symbols( String::from("module2"), // The calling frame's function. String::from("FUNC 100 400 10 marsupial\n"), ); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::Scan); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 2); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() ); } else { unreachable!(); } } } #[tokio::test] async fn test_scan_first_frame() { // The first (context) frame gets extra long scans, this test checks that. let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let frame1_sp = Label::new(); let frame2_sp = Label::new(); stack = stack // frame 0 .append_repeated(0, 16) // space .D64(0x40090000) // junk that's not .D64(0x60000000) // a return address .append_repeated(0, 96) // more space .D64(return_address1) // actual return address // frame 1 .mark(&frame1_sp) .append_repeated(0, 32) // space .D64(0xF0000000) // more junk .D64(0x0000000D) .append_repeated(0, 336) // more space .D64(return_address2) // actual return address (won't be found) // frame 2 .mark(&frame2_sp) .append_repeated(0, 64); // end of stack f.raw.set_register("pc", 0x40005510); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::Scan); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 2); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() ); } else { unreachable!(); } } } #[tokio::test] async fn test_frame_pointer() { // Frame-pointer-based unwinding let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let frame1_sp = Label::new(); let frame2_sp = Label::new(); let frame0_fp = Label::new(); let frame1_fp = Label::new(); let frame2_fp = Label::new(); stack = stack // frame 0 .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame0_fp) // next fp will point to the next value .D64(&frame1_fp) // save current frame pointer .D64(return_address1) // save current link register .mark(&frame1_sp) // frame 1 .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame1_fp) .D64(&frame2_fp) .D64(return_address2) .mark(&frame2_sp) // frame 2 .append_repeated(0, 64) // Whatever values on the stack. .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address. .mark(&frame2_fp) // next fp will point to the next value .D64(0) .D64(0); f.raw.set_register("pc", 0x40005510); f.raw.set_register("lr", 0x1fe0fe10); f.raw.set_register("fp", frame0_fp.value().unwrap()); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 3); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame1_fp.value().unwrap() ); } else { unreachable!(); } } { // Frame 2 let frame = &s.frames[2]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame2_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame2_fp.value().unwrap() ); } else { unreachable!(); } } } #[tokio::test] async fn test_frame_pointer_stackless_leaf() { // Same as test_frame_pointer but frame0 is a stackless leaf. // // In the current implementation we will misunderstand this slightly // and basically "lose" frame 1, but still properly recover frame 2. // THIS TEST BREAKING MIGHT MEAN YOU'VE MADE THINGS WORK BETTER! let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let frame1_sp = Label::new(); let frame2_sp = Label::new(); let frame1_fp = Label::new(); let frame2_fp = Label::new(); stack = stack // frame 0 (all junk!) .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame1_sp) // frame 1 (this is sadly dropped) .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame1_fp) .D64(&frame2_fp) .D64(return_address2) .mark(&frame2_sp) // frame 2 .append_repeated(0, 64) // Whatever values on the stack. .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address. .mark(&frame2_fp) // next fp will point to the next value .D64(0) .D64(0); f.raw.set_register("pc", 0x40005510); f.raw.set_register("lr", return_address1); // we will sadly ignore this f.raw.set_register("fp", frame1_fp.value().unwrap()); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 2 (found as Frame 1) let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame2_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame2_fp.value().unwrap() ); } else { unreachable!(); } } } #[tokio::test] async fn test_frame_pointer_stackful_leaf() { // Same as test_frame_pointer but frame0 is a stackful leaf. // // In the current implementation we will misunderstand this slightly // and basically "lose" frame 1, but still properly recover frame 2. // THIS TEST BREAKING MIGHT MEAN YOU'VE MADE THINGS WORK BETTER! let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let frame1_sp = Label::new(); let frame2_sp = Label::new(); let frame1_fp = Label::new(); let frame2_fp = Label::new(); stack = stack // frame 0 (literally nothing!) .mark(&frame1_sp) // frame 1 (this is sadly dropped) .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame1_fp) .D64(&frame2_fp) .D64(return_address2) .mark(&frame2_sp) // frame 2 .append_repeated(0, 64) // Whatever values on the stack. .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address. .mark(&frame2_fp) // next fp will point to the next value .D64(0) .D64(0); f.raw.set_register("pc", 0x40005510); f.raw.set_register("lr", return_address1); // we will sadly ignore this f.raw.set_register("fp", frame1_fp.value().unwrap()); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 2 (found as Frame 1) let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame2_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame2_fp.value().unwrap() ); } else { unreachable!(); } } } #[tokio::test] async fn test_frame_pointer_ptr_auth_strip() { // Same as the basic frame pointer test but extra high bits have been set which // must be masked out. This is vaguely emulating Arm Pointer Authentication, // although very synthetically. This might break if we implement more accurate // stripping. But at that point we should have a better understanding of how // to make an "accurate" test! let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let authenticated_return_address1 = return_address1 | 0x0013_8000_0000_0000; let authenticated_return_address2 = return_address2 | 0x1110_0000_0000_0000; let frame1_sp = Label::new(); let frame2_sp = Label::new(); let frame0_fp = Label::new(); let frame1_fp = Label::new(); let frame2_fp = Label::new(); let authenticated_frame1_fp = Label::new(); let authenticated_frame2_fp = Label::new(); stack = stack // frame 0 .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame0_fp) // next fp will point to the next value .D64(&authenticated_frame1_fp) // save current frame pointer .D64(authenticated_return_address1) // save current link register .mark(&frame1_sp) // frame 1 .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame1_fp) .D64(&authenticated_frame2_fp) .D64(authenticated_return_address2) .mark(&frame2_sp) // frame 2 .append_repeated(0, 64) // Whatever values on the stack. .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address. .mark(&frame2_fp) // next fp will point to the next value .D64(0) .D64(0); authenticated_frame1_fp.set_const(frame1_fp.value().unwrap() | 0xa310_0000_0000_0000); authenticated_frame2_fp.set_const(frame2_fp.value().unwrap() | 0xf31e_8000_0000_0000); f.raw.set_register("pc", 0x40005510); f.raw.set_register("lr", 0x1fe0fe10); f.raw.set_register("fp", frame0_fp.value().unwrap()); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 3); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame1_fp.value().unwrap() ); } else { unreachable!(); } } { // Frame 2 let frame = &s.frames[2]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address2); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame2_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame2_fp.value().unwrap() ); } else { unreachable!(); } } } const CALLEE_SAVE_REGS: &[&str] = &[ "pc", "sp", "fp", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", ]; fn init_cfi_state_high_module() -> (TestFixture, Section, Context, MinidumpContextValidity) { init_cfi_state_common(TestFixture::high_module()) } fn init_cfi_state() -> (TestFixture, Section, Context, MinidumpContextValidity) { init_cfi_state_common(TestFixture::new()) } fn init_cfi_state_common( mut f: TestFixture, ) -> (TestFixture, Section, Context, MinidumpContextValidity) { let symbols = [ // The youngest frame's function. "FUNC 4000 1000 10 enchiridion\n", // Initially, nothing has been pushed on the stack, // and the return address is still in the link // register (x30). "STACK CFI INIT 4000 100 .cfa: sp 0 + .ra: x30\n", // Push x19, x20, the frame pointer and the link register. "STACK CFI 4001 .cfa: sp 32 + .ra: .cfa -8 + ^", " x19: .cfa -32 + ^ x20: .cfa -24 + ^ ", " x29: .cfa -16 + ^\n", // Save x19..x22 in x0..x3: verify that we populate // the youngest frame with all the values we have. "STACK CFI 4002 x19: x0 x20: x1 x21: x2 x22: x3\n", // Restore x19..x22. Save the non-callee-saves register x1. "STACK CFI 4003 .cfa: sp 40 + x1: .cfa 40 - ^", " x19: x19 x20: x20 x21: x21 x22: x22\n", // Move the .cfa back eight bytes, to point at the return // address, and restore the sp explicitly. "STACK CFI 4005 .cfa: sp 32 + x1: .cfa 32 - ^", " x29: .cfa 8 - ^ .ra: .cfa ^ sp: .cfa 8 +\n", // Recover the PC explicitly from a new stack slot; // provide garbage for the .ra. "STACK CFI 4006 .cfa: sp 40 + pc: .cfa 40 - ^\n", // The calling function. "FUNC 5000 1000 10 epictetus\n", // Mark it as end of stack. "STACK CFI INIT 5000 1000 .cfa: 0 .ra: 0\n", // A function whose CFI makes the stack pointer // go backwards. "FUNC 6000 1000 20 palinal\n", "STACK CFI INIT 6000 1000 .cfa: sp 8 - .ra: x30\n", // A function with CFI expressions that can't be // evaluated. "FUNC 7000 1000 20 rhetorical\n", "STACK CFI INIT 7000 1000 .cfa: moot .ra: ambiguous\n", ]; f.add_symbols(String::from("module1"), symbols.concat()); f.raw.set_register("pc", 0x0000_0000_4000_5510); f.raw.set_register("sp", 0x0000_0000_8000_0000); f.raw.set_register("fp", 0x0000_00a2_8112_e110); f.raw.set_register("x19", 0x5e68b5d5b5d55e68); f.raw.set_register("x20", 0x34f3ebd1ebd134f3); f.raw.set_register("x21", 0x74bca31ea31e74bc); f.raw.set_register("x22", 0x16b32dcb2dcb16b3); f.raw.set_register("x23", 0x21372ada2ada2137); f.raw.set_register("x24", 0x557dbbbbbbbb557d); f.raw.set_register("x25", 0x8ca748bf48bf8ca7); f.raw.set_register("x26", 0x21f0ab46ab4621f0); f.raw.set_register("x27", 0x146732b732b71467); f.raw.set_register("x28", 0xa673645fa673645f); let raw_valid = MinidumpContextValidity::All; let expected = f.raw.clone(); let expected_regs = CALLEE_SAVE_REGS; let expected_valid = MinidumpContextValidity::Some(expected_regs.iter().copied().collect()); let stack = Section::new(); stack .start() .set_const(f.raw.get_register("sp", &raw_valid).unwrap()); (f, stack, expected, expected_valid) } async fn check_cfi( f: TestFixture, stack: Section, expected: Context, expected_valid: MinidumpContextValidity, ) { let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 if let MinidumpContextValidity::Some(ref expected_regs) = expected_valid { let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::CallFrameInfo); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), expected_regs.len()); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { for reg in expected_regs { assert_eq!( ctx.get_register(reg, valid), expected.get_register(reg, &expected_valid), "{reg} registers didn't match!" ); } return; } else { unreachable!() } } } unreachable!(); } #[tokio::test] async fn test_cfi_at_4000() { let (mut f, mut stack, expected, expected_valid) = init_cfi_state(); stack = stack.append_repeated(0, 120); f.raw.set_register("pc", 0x0000000040004000); f.raw.set_register("lr", 0x0000000040005510); check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4001() { let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0x5e68b5d5b5d55e68) // saved x19 .D64(0x34f3ebd1ebd134f3) // saved x20 .D64(0x0000_00a2_8112_e110) // saved fp .D64(0x0000_0000_4000_5510) // return address .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); f.raw.set_register("pc", 0x0000000040004001); f.raw.set_register("x19", 0xadc9f635a635adc9); f.raw.set_register("x20", 0x623135ac35ac6231); f.raw.set_register("fp", 0x5fc4be14be145fc4); check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4002() { let (mut f, mut stack, mut expected, expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0x0000_00a2_8112_e110) // saved fp .D64(0x0000_0000_4000_5510) // return address .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); f.raw.set_register("pc", 0x0000000040004002); f.raw.iregs[0] = 0x5e68b5d5b5d55e68; // saved x19 f.raw.iregs[1] = 0x34f3ebd1ebd134f3; // saved x20 f.raw.iregs[2] = 0x74bca31ea31e74bc; // saved x21 f.raw.iregs[3] = 0x16b32dcb2dcb16b3; // saved x22 f.raw.iregs[19] = 0xadc9f635a635adc9; // distinct callee x19 f.raw.iregs[20] = 0x623135ac35ac6231; // distinct callee x20 f.raw.iregs[21] = 0xac4543564356ac45; // distinct callee x21 f.raw.iregs[22] = 0x2561562f562f2561; // distinct callee x22 f.raw.set_register("fp", 0x5fc4be14be145fc4); check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4003() { let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0xdd5a48c848c8dd5a) // saved x1 (even though it's not callee-saves) .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0x0000_00a2_8112_e110) // saved fp .D64(0x0000_0000_4000_5510) // return address .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); expected.iregs[1] = 0xdd5a48c848c8dd5a; if let MinidumpContextValidity::Some(ref mut which) = expected_valid { which.insert("x1"); } else { unreachable!(); } f.raw.set_register("pc", 0x0000000040004003); f.raw.iregs[1] = 0xfb756319fb756319; f.raw.set_register("fp", 0x5fc4be14be145fc4); check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4004() { // Should just be the same as 4003 let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0xdd5a48c848c8dd5a) // saved x1 (even though it's not callee-saves) .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0x0000_00a2_8112_e110) // saved fp .D64(0x0000_0000_4000_5510) // return address .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); expected.iregs[1] = 0xdd5a48c848c8dd5a; if let MinidumpContextValidity::Some(ref mut which) = expected_valid { which.insert("x1"); } else { unreachable!(); } f.raw.set_register("pc", 0x0000000040004004); f.raw.iregs[1] = 0xfb756319fb756319; f.raw.set_register("fp", 0x5fc4be14be145fc4); check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4005_ptr_auth_strip_apple() { // This is the same as the normal 4005 test but with extra garabage (auth) bits // set in the high 24 bits. This emulates what apple platforms looks like. let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0xdd5a48c848c8dd5a) // saved x1 (even though it's not callee-saves) .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0xae23_80a2_8112_e110) // saved fp WITH AUTH .D64(0xae1d_0000_4000_5510) // return address WITH AUTH .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); expected.iregs[1] = 0xdd5a48c848c8dd5a; if let MinidumpContextValidity::Some(ref mut which) = expected_valid { which.insert("x1"); } else { unreachable!(); } f.raw.set_register("pc", 0x0000000040004005); f.raw.iregs[1] = 0xfb756319fb756319; check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4005_ptr_auth_strip_high() { // This is the same as the normal 4005 test but with extra garabage (auth) bits // set in the **extra** high bits. This emulates what android platforms look like. let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state_high_module(); let frame1_sp = Label::new(); stack = stack .D64(0xdd5a48c848c8dd5a) // saved x1 (even though it's not callee-saves) .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0x1003_45a2_8112_e110) // saved fp WITH AUTH .D64(0x100d_f700_4000_5510) // return address WITH AUTH .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); expected.set_register("fp", 0x0003_45a2_8112_e110); expected.set_register("pc", 0x000d_f700_4000_5510); expected.iregs[1] = 0xdd5a48c848c8dd5a; if let MinidumpContextValidity::Some(ref mut which) = expected_valid { which.insert("x1"); } else { unreachable!(); } f.raw.set_register("pc", 0x0000000040004005); f.raw.iregs[1] = 0xfb756319fb756319; check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4005() { // Here we move the .cfa, but provide an explicit rule to recover the SP, // so again there should be no change in the registers recovered. let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0xdd5a48c848c8dd5a) // saved x1 (even though it's not callee-saves) .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0x0000_00a2_8112_e110) // saved fp .D64(0x0000_0000_4000_5510) // return address .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); expected.iregs[1] = 0xdd5a48c848c8dd5a; if let MinidumpContextValidity::Some(ref mut which) = expected_valid { which.insert("x1"); } else { unreachable!(); } f.raw.set_register("pc", 0x0000000040004005); f.raw.iregs[1] = 0xfb756319fb756319; check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_at_4006() { // Here we provide an explicit rule for the PC, and have the saved .ra be // bogus. let (mut f, mut stack, mut expected, mut expected_valid) = init_cfi_state(); let frame1_sp = Label::new(); stack = stack .D64(0x0000000040005510) // saved pc .D64(0xdd5a48c848c8dd5a) // saved x1 (even though it's not callee-saves) .D64(0xff3dfb81fb81ff3d) // no longer saved x19 .D64(0x34f3ebd1ebd134f3) // no longer saved x20 .D64(0x0000_00a2_8112_e110) // saved fp .D64(0xf8d157835783f8d1) // .ra rule recovers this, which is garbage .mark(&frame1_sp) .append_repeated(0, 120); expected.set_register("sp", frame1_sp.value().unwrap()); expected.iregs[1] = 0xdd5a48c848c8dd5a; if let MinidumpContextValidity::Some(ref mut which) = expected_valid { which.insert("x1"); } else { unreachable!(); } f.raw.set_register("pc", 0x0000000040004006); f.raw.iregs[1] = 0xfb756319fb756319; check_cfi(f, stack, expected, expected_valid).await; } #[tokio::test] async fn test_cfi_reject_backwards() { // Check that we reject rules that would cause the stack pointer to // move in the wrong direction. let (mut f, mut stack, _expected, _expected_valid) = init_cfi_state(); stack = stack.append_repeated(0, 120); f.raw.set_register("pc", 0x0000000040006000); f.raw.set_register("sp", 0x0000000080000000); f.raw.set_register("lr", 0x0000000040005510); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 1); } #[tokio::test] async fn test_cfi_reject_bad_exprs() { // Check that we reject rules whose expressions' evaluation fails. let (mut f, mut stack, _expected, _expected_valid) = init_cfi_state(); stack = stack.append_repeated(0, 120); f.raw.set_register("pc", 0x0000000040007000); f.raw.set_register("sp", 0x0000000080000000); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 1); } #[tokio::test] async fn test_frame_pointer_overflow() { // Make sure we don't explode when trying frame pointer analysis on a value // that will overflow. type Pointer = u64; let stack_max: Pointer = Pointer::MAX; let stack_size: Pointer = 1000; let bad_frame_ptr: Pointer = stack_max; let mut f = TestFixture::new(); let mut stack = Section::new(); let stack_start: Pointer = stack_max - stack_size; stack.start().set_const(stack_start); stack = stack // frame 0 .append_repeated(0, stack_size as usize); // junk, not important to the test f.raw.set_register("pc", 0x00007400c0000200); f.raw.set_register("fp", bad_frame_ptr); f.raw .set_register("sp", stack.start().value().unwrap() as Pointer); f.raw.set_register("lr", 0x00007500b0000110); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 1); // As long as we don't panic, we're good! } #[tokio::test] async fn test_frame_pointer_barely_no_overflow() { // This is a simple frame pointer test but with the all the values pushed // as close to the upper memory boundary as possible, to confirm that // our code doesn't randomly overflow *AND* isn't overzealous in // its overflow guards. // We set the highest module here to bypass ptr auth stripping entirely and stress overflows let mut f = TestFixture::highest_module(); let mut stack = Section::new(); type Pointer = u64; let stack_max: Pointer = Pointer::MAX; let pointer_size: Pointer = std::mem::size_of::() as Pointer; let stack_size: Pointer = pointer_size * 3; let stack_start: Pointer = stack_max - stack_size; let return_address: Pointer = 0x00007500b0000110; stack.start().set_const(stack_start); let frame0_fp = Label::new(); let frame1_sp = Label::new(); let frame1_fp = Label::new(); stack = stack // frame 0 .mark(&frame0_fp) .D64(&frame1_fp) // .D64(return_address) // actual return address // frame 1 .mark(&frame1_sp) .mark(&frame1_fp) // end of stack .D64(0); f.raw.set_register("pc", 0x00007400c0000200); f.raw .set_register("fp", frame0_fp.value().unwrap() as Pointer); f.raw .set_register("sp", stack.start().value().unwrap() as Pointer); f.raw.set_register("lr", return_address); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!( ctx.get_register("fp", valid).unwrap(), frame0_fp.value().unwrap() as Pointer ); } else { unreachable!(); } } { // Frame 1 let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() as Pointer ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame1_fp.value().unwrap() as Pointer ); } else { unreachable!(); } } } #[tokio::test] async fn test_frame_pointer_infinite_equality() { // Leaf functions on Arm are allowed to not update the stack pointer, so // it's valid for the frame pointer analysis to conclude that the stack // pointer doesn't change. However we must only provide this allowance // to the first stack frame, or else we're vulnerable to infinite loops. // // One of the CFI tests already checks that we allow the leaf case to work, // so here we test that we don't get stuck in an infinite loop for the // non-leaf case. // // This is just a copy-paste of test_frame_pointer except for the line // "EVIL INFINITE FRAME POINTER" has been changed from frame2_fp to frame1_fp. let mut f = TestFixture::new(); let mut stack = Section::new(); stack.start().set_const(0x80000000); let return_address1 = 0x50000100u64; let return_address2 = 0x50000900u64; let frame1_sp = Label::new(); let frame2_sp = Label::new(); let frame0_fp = Label::new(); let frame1_fp = Label::new(); let frame2_fp = Label::new(); stack = stack // frame 0 .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame0_fp) // next fp will point to the next value .D64(&frame0_fp) // EVIL INFINITE FRAME POINTER .D64(return_address1) // save current link register .mark(&frame1_sp) // frame 1 .append_repeated(0, 64) // space .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address .mark(&frame1_fp) .D64(&frame2_fp) .D64(return_address2) .mark(&frame2_sp) // frame 2 .append_repeated(0, 64) // Whatever values on the stack. .D64(0x0000000D) // junk that's not .D64(0xF0000000) // a return address. .mark(&frame2_fp) // next fp will point to the next value .D64(0) .D64(0); f.raw.set_register("pc", 0x40005510); f.raw.set_register("lr", 0x1fe0fe10); f.raw.set_register("fp", frame0_fp.value().unwrap()); f.raw.set_register("sp", stack.start().value().unwrap()); let s = f.walk_stack(stack).await; assert_eq!(s.frames.len(), 2); { // Frame 0 let frame = &s.frames[0]; assert_eq!(frame.trust, FrameTrust::Context); assert_eq!(frame.context.valid, MinidumpContextValidity::All); } { // Frame 1 (a messed up hybrid of frame0 and frame1) let frame = &s.frames[1]; let valid = &frame.context.valid; assert_eq!(frame.trust, FrameTrust::FramePointer); if let MinidumpContextValidity::Some(ref which) = valid { assert_eq!(which.len(), 3); } else { unreachable!(); } if let MinidumpRawContext::Arm64(ctx) = &frame.context.raw { assert_eq!(ctx.get_register("pc", valid).unwrap(), return_address1); assert_eq!( ctx.get_register("sp", valid).unwrap(), frame1_sp.value().unwrap() ); assert_eq!( ctx.get_register("fp", valid).unwrap(), frame0_fp.value().unwrap() ); } else { unreachable!(); } } // Never get to frame 2, alas! }