Skip to main content

libinject/
instrument.rs

1//! **Shadow-stack instrumentation:** Exports the `instruction_event` symbol, which can be
2//! registered with DynamoRIO to instrument basic blocks such that a _shadow_ callstack is
3//! maintained while to binary executes. At any point in execution this shadow-stack can be queried
4//! for the raw absolute frame addresses of the function in the current callstack.
5//!
6
7use crate::ffi::{bool_, dr_emit_flags_t, instr_t, instrlist_t};
8use crate::utils;
9use crate::{MOD_BASE_ADDR, MOD_SIZE, ffi};
10use std::os::raw::c_void;
11
12#[derive(Debug, Clone)]
13pub struct RawCallStack {
14    // Raw absolute frame addresses (captured on calls); we filter to in-module elsewhere.
15    pub stack: Vec<Option<usize>>,
16}
17
18impl RawCallStack {
19    pub fn new() -> Self {
20        Self {
21            stack: Vec::with_capacity(128),
22        }
23    }
24    pub fn push(&mut self, addr: Option<usize>) {
25        self.stack.push(addr);
26    }
27    pub fn pop(&mut self) {
28        self.stack.pop();
29    }
30}
31
32/// Return raw frame addresses from the shadow stack (order preserved).
33///
34/// Returns Some(frames) when the thread-local shadow stack is available,
35/// or None if the TLS pointer is null (e.g., uninitialized or torn down).
36pub fn stack_frames(drcontext: *mut ::std::os::raw::c_void) -> Option<Vec<Option<usize>>> {
37    use crate::drcore::log;
38    log(&format!("[stack frames inside] in1"));
39    unsafe {
40        let stack_ptr = ffi::drmgr_get_tls_field(drcontext, crate::tls_idx) as *mut RawCallStack;
41        if stack_ptr.is_null() {
42            return None;
43        }
44        Some((*stack_ptr).stack.clone())
45    }
46}
47
48/// Returns (module_base, module_size).
49pub fn module_bounds() -> (usize, usize) {
50    let base = *MOD_BASE_ADDR
51        .lock()
52        .expect("Failed to lock MOD_BASE_ADDR")
53        .as_ref()
54        .expect("Module base should always be known");
55    let size = *MOD_SIZE
56        .lock()
57        .expect("Failed to lock MOD_SIZE")
58        .as_ref()
59        .expect("Module size should always be known");
60    (base, size)
61}
62
63fn in_target_module(addr: usize) -> bool {
64    let (base, size) = module_bounds();
65    addr >= base && addr < base + size
66}
67
68#[unsafe(no_mangle)]
69pub unsafe extern "C" fn instruction_event(
70    drcontext: *mut ::std::os::raw::c_void,
71    _tag: *mut ::std::os::raw::c_void,
72    bb: *mut instrlist_t,
73    instr: *mut instr_t,
74    _for_trace: bool_,
75    _translating: bool_,
76    _user_data: *mut ::std::os::raw::c_void,
77) -> dr_emit_flags_t {
78    unsafe {
79        if ffi::instr_is_call_direct(instr) != 0 {
80            let instr_addr = ffi::instr_get_app_pc(instr);
81            let target_addr = ffi::instr_get_branch_target_pc(instr);
82            let target_addr_as_opnd = utils::opnd_create_uintptr(target_addr as usize);
83            let instr_addr_as_opnd = utils::opnd_create_uintptr(instr_addr as usize);
84            ffi::dr_insert_clean_call(
85                drcontext,
86                bb,
87                instr,
88                on_direct_call as *mut c_void,
89                false as bool_,
90                2,
91                instr_addr_as_opnd,
92                target_addr_as_opnd,
93            );
94        } else if ffi::instr_is_call_indirect(instr) != 0 {
95            ffi::dr_insert_mbr_instrumentation(
96                drcontext,
97                bb,
98                instr,
99                on_indirect_call as *mut c_void,
100                ffi::dr_spill_slot_t_SPILL_SLOT_1,
101            );
102        }
103
104        if ffi::instr_is_return(instr) != 0 {
105            // Insert call to your shadow stack pop handler (on_ret)
106            ffi::dr_insert_mbr_instrumentation(
107                drcontext,
108                bb,
109                instr,
110                on_return as *mut c_void,
111                ffi::dr_spill_slot_t_SPILL_SLOT_1,
112            );
113        }
114    }
115
116    ffi::dr_emit_flags_t_DR_EMIT_DEFAULT
117}
118
119// 32-bit Windows: ensure cdecl
120#[cfg(all(target_pointer_width = "32"))]
121extern "cdecl" fn on_direct_call(instr_addr: *mut c_void, target_addr: *mut c_void) {
122    on_direct_call_logic(instr_addr, target_addr)
123}
124
125// All other targets: standard C (maps to the platform C ABI)
126#[cfg(not(all(target_pointer_width = "32")))]
127extern "C" fn on_direct_call(instr_addr: *mut c_void, target_addr: *mut c_void) {
128    on_direct_call_logic(instr_addr, target_addr)
129}
130
131fn on_direct_call_logic(instr_addr: *mut c_void, target_addr: *mut c_void) {
132    // Only instrument calls from the target module.
133    if instr_addr.is_null() | !in_target_module(instr_addr as usize) {
134        return;
135    }
136
137    let addr = if target_addr.is_null() {
138        None
139    } else {
140        Some(target_addr as usize)
141    };
142
143    unsafe {
144        let drcontext = ffi::dr_get_current_drcontext();
145        let tls = ffi::drmgr_get_tls_field(drcontext, crate::tls_idx) as *mut RawCallStack;
146        if !tls.is_null() {
147            // Record even null direct calls to preserve callsite information
148            (*tls).push(addr);
149        }
150    }
151}
152
153// 32-bit Windows: ensure cdecl
154#[cfg(all(target_pointer_width = "32"))]
155extern "cdecl" fn on_indirect_call(branch_instr_addr: *const u8, target_addr: *const u8) {
156    on_indirect_call_logic(branch_instr_addr, target_addr)
157}
158
159// All other targets: standard C (maps to the platform C ABI)
160#[cfg(not(all(target_pointer_width = "32")))]
161extern "C" fn on_indirect_call(branch_instr_addr: *const u8, target_addr: *const u8) {
162    on_indirect_call_logic(branch_instr_addr, target_addr)
163}
164
165fn on_indirect_call_logic(branch_instr_addr: *const u8, target_addr: *const u8) {
166    unsafe {
167        if !in_target_module(branch_instr_addr as usize) {
168            return;
169        }
170        let drcontext = ffi::dr_get_current_drcontext();
171        let tls = ffi::drmgr_get_tls_field(drcontext, crate::tls_idx) as *mut RawCallStack;
172        if !tls.is_null() {
173            (*tls).push(Some(target_addr as usize));
174        }
175    }
176}
177
178// 32-bit Windows: ensure cdecl
179#[cfg(all(target_pointer_width = "32"))]
180extern "cdecl" fn on_return(branch_instr_addr: *const u8, target_addr: *const u8) {
181    on_return_logic(branch_instr_addr, target_addr)
182}
183
184// All other targets: standard C (maps to the platform C ABI)
185#[cfg(not(all(target_pointer_width = "32")))]
186extern "C" fn on_return(branch_instr_addr: *const u8, target_addr: *const u8) {
187    on_return_logic(branch_instr_addr, target_addr)
188}
189
190fn on_return_logic(_branch_instr_addr: *const u8, target_addr: *const u8) {
191    if !in_target_module(target_addr as usize) {
192        return;
193    }
194    unsafe {
195        let drcontext = ffi::dr_get_current_drcontext();
196        let tls = ffi::drmgr_get_tls_field(drcontext, crate::tls_idx) as *mut RawCallStack;
197        if !tls.is_null() {
198            (*tls).pop();
199        }
200    }
201}