impl TestFixture { pubfn 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(),
}
}
pubfn 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(),
}
}
pubfn 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(),
}
}
pubasyncfn 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())); letmut stack = CallStack::with_context(context);
#[tokio::test] asyncfn test_simple() { letmut 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] asyncfn test_scan_without_symbols() { // Scanning should work without any symbols letmut f = TestFixture::new(); letmut 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
#[tokio::test] asyncfn 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. letmut f = TestFixture::new(); letmut 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
#[tokio::test] asyncfn test_scan_first_frame() { // The first (context) frame gets extra long scans, this test checks that. letmut f = TestFixture::new(); letmut 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
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);
#[tokio::test] asyncfn 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! letmut f = TestFixture::new(); letmut 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);
#[tokio::test] asyncfn 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! letmut f = TestFixture::new(); letmut 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);
#[tokio::test] asyncfn 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! letmut f = TestFixture::new(); letmut 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);
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)
}
asyncfn check_cfi(
f: TestFixture,
stack: Section,
expected: Context,
expected_valid: MinidumpContextValidity,
) { let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 2);
#[tokio::test] asyncfn 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.
#[tokio::test] asyncfn 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.
#[tokio::test] asyncfn 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.
#[tokio::test] asyncfn 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();
let s = f.walk_stack(stack).await;
assert_eq!(s.frames.len(), 1);
}
#[tokio::test] asyncfn 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;
letmut f = TestFixture::new(); letmut 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
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] asyncfn 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 letmut f = TestFixture::highest_module();
letmut stack = Section::new();
type Pointer = u64; let stack_max: Pointer = Pointer::MAX; let pointer_size: Pointer = std::mem::size_of::<Pointer>() 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);
#[tokio::test] asyncfn 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. letmut f = TestFixture::new(); letmut 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.