panda/callbacks/ppp_closures.rs
1use std::{
2 collections::HashMap,
3 ffi::c_void,
4 sync::{
5 atomic::{AtomicU64, Ordering},
6 Mutex,
7 },
8};
9
10/// A reference to a given callback slot which can be used to install,
11/// enable, disable, or otherwise reference, a closure-based callback for
12/// PANDA plugin-to-plugin ("PPP") callbacks.
13///
14/// Since this is a reference to a callback slot and does not include storage
15/// for the callback itself, it can be trivially copied, as well as included in
16/// the callback itself (for the purposes of enabling/disabling).
17///
18/// In order to actually install the callback, you will need to import the trait
19/// for the specific plugin whose callbacks you want to run. In the example below,
20/// the [`ProcStartLinuxCallbacks`] trait provides the [`on_rec_auxv`] method in
21/// order to add the callback.
22///
23/// [`ProcStartLinuxCallbacks`]: crate::plugins::proc_start_linux::ProcStartLinuxCallbacks
24/// [`on_rec_auxv`]: crate::plugins::proc_start_linux::ProcStartLinuxCallbacks::on_rec_auxv
25///
26/// ## Example
27///
28/// ```
29/// use panda::plugins::proc_start_linux::ProcStartLinuxCallbacks;
30/// use panda::PppCallback;
31/// use panda::prelude::*;
32///
33/// PppCallback::new().on_rec_auxv(|_, _, auxv| {
34/// dbg!(auxv);
35/// });
36///
37/// Panda::new().generic("x86_64").replay("test").run();
38/// ```
39///
40/// The above installs a callback to print out the contents of the auxillary vector
41/// using [`dbg`] whenever a new process is spawned in the guest.
42///
43/// Example output:
44///
45/// ```no_run
46/// ...
47/// [panda-rs/examples/closures.rs:18] auxv = AuxvValues {
48/// argc: 3,
49/// argv_ptr_ptr: 0x7fffffffebb8,
50/// arg_ptr: [
51/// 0x7fffffffede6,
52/// 0x7fffffffedeb,
53/// 0x7fffffffedee,
54/// ],
55/// argv: [
56/// "bash",
57/// "-c",
58/// "echo test2",
59/// ],
60/// envc: 20,
61/// env_ptr_ptr: 0x7fffffffebd8,
62/// env_ptr: [
63/// 0x7fffffffedf9,
64/// 0x7fffffffee04,
65/// // ...
66/// 0x7fffffffefc2,
67/// 0x7fffffffefe2,
68/// ],
69/// envp: [
70/// "LS_COLORS=",
71/// "LESSCLOSE=/usr/bin/lesspipe %s %s",
72/// "LANG=C.UTwcap2: 0,F-8",
73/// "INVOCATION_ID=0b2d5ea4eb39435388bf53e507047b2f",
74/// "XDG_SESSION_ID=1",
75/// "HUSHLOGIN=FALSE",
76/// "USER=root",
77/// "PWD=/root",
78/// "HOME=/root",
79/// "JOURNAL_STREAM=9:16757",
80/// "XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop",
81/// "MAIL=/var/mail/root",
82/// "SHELL=/bin/bash",
83/// "TERM=vt220",
84/// "SHLVL=1",
85/// "LOGNAME=root",
86/// "XDG_RUNTIME_DIR=/run/user/0",
87/// "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin",
88/// "LESSOPEN=| /usr/bin/lesspipe %s",
89/// "_=/bin/bash",
90/// ],
91/// execfn_ptr: 0x7fffffffefee,
92/// execfn: "/bin/bash",
93/// phdr: 0x555555554040,
94/// entry: 0x555555585520,
95/// ehdr: 0x7ffff7ffa000,
96/// // ...
97/// }
98/// Replay completed successfully
99/// Exiting cpu_handle_exception loop
100/// ```
101///
102/// ## Note
103///
104/// Callback closures must have a `'static` lifetime in order to live past the end of the
105/// function. This means that the only references a callback can include are references
106/// to static variables or leaked objects on the heap (See [`Box::leak`] for more info).
107///
108/// If you'd like to reference shared data without leaking, this can be accomplished via
109/// reference counting. See [`Arc`] for more info. If you want to capture data owned
110/// by the current function without sharing it, you can mark your closure as `move` in
111/// order to move all the variables you capture into your closure. (Such as in the above
112/// example, where `count` is moved into the closure for modification)
113///
114/// [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
115#[repr(transparent)]
116#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
117pub struct PppCallback(pub(crate) u64);
118
119static CURRENT_CALLBACK_ID: AtomicU64 = AtomicU64::new(0);
120
121impl PppCallback {
122 /// Create a new callback slot which can then be used to install or modify
123 /// a given callback.
124 pub fn new() -> Self {
125 Self(CURRENT_CALLBACK_ID.fetch_add(1, Ordering::SeqCst))
126 }
127
128 /// Enable the callback assigned to the given slot, if any.
129 pub fn enable(&self) {
130 let mut callbacks = CALLBACKS.lock().unwrap();
131 if let Some(callback) = callbacks.get_mut(&self.0) {
132 if !callback.is_enabled {
133 unsafe {
134 (callback.enable)(callback.closure_ref);
135 }
136 callback.is_enabled = true;
137 }
138 }
139 }
140
141 /// Disable the callback assigned to the given slot, if any.
142 pub fn disable(&self) {
143 let mut callbacks = CALLBACKS.lock().unwrap();
144
145 if let Some(callback) = callbacks.get_mut(&self.0) {
146 if callback.is_enabled {
147 unsafe {
148 (callback.disable)(callback.closure_ref);
149 }
150 callback.is_enabled = false;
151 }
152 }
153 }
154}
155
156lazy_static::lazy_static! {
157 static ref CALLBACKS: Mutex<HashMap<u64, InternalPppClosureCallback>> = Mutex::new(HashMap::new());
158}
159
160#[doc(hidden)]
161pub struct InternalPppClosureCallback {
162 pub closure_ref: *mut c_void,
163 pub enable: unsafe fn(*mut c_void),
164 pub disable: unsafe fn(*mut c_void),
165 pub drop_fn: unsafe fn(*mut c_void),
166 pub is_enabled: bool,
167}
168
169unsafe impl Sync for InternalPppClosureCallback {}
170unsafe impl Send for InternalPppClosureCallback {}
171
172#[doc(hidden)]
173pub unsafe fn __internal_install_ppp_closure_callback(
174 PppCallback(id): PppCallback,
175 mut callback: InternalPppClosureCallback,
176) {
177 (callback.enable)(callback.closure_ref);
178 callback.is_enabled = true;
179
180 let mut callbacks = CALLBACKS.lock().unwrap();
181 callbacks.insert(id, callback);
182}