Skip to main content

coreshift_core/binder/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! NDK binder primitives for querying Android system services.
6//!
7//! Uses `dlopen` on `libbinder_ndk.so` to avoid hard-linking against a library
8//! absent from older NDK toolchains or non-Android targets.
9//!
10//! All public types are gated on `#[cfg(target_os = "android")]`. On other
11//! targets every entry point returns `CoreError::Binder`.
12//!
13//! ## Transaction code resolution
14//!
15//! `IActivityManager` transaction codes are AIDL-generated and shift with each
16//! AOSP release. Resolution order (no subprocess required):
17//!
18//! 1. Parse `/data/local/tmp/coreshift/tx_code.txt` if present (written by fgw).
19//! 2. Look up a known code from the `ro.build.version.sdk` property table.
20//! 3. Linear probe codes in a narrow SDK-version-dependent window.
21
22use crate::CoreError;
23
24// ─────────────────────────────────────────────────────────────────────────────
25// Android-only implementation
26// ─────────────────────────────────────────────────────────────────────────────
27
28#[cfg(target_os = "android")]
29mod imp {
30    use super::*;
31    use crate::android_property::android_property_get;
32    use std::os::raw::{c_char, c_void};
33
34    // ── NDK binder status codes ───────────────────────────────────────────────
35
36    const STATUS_OK: i32 = 0;
37    const STATUS_UNKNOWN_TRANSACTION: i32 = -2;
38
39    // writeNoException() writes 0; any non-zero is a Java exception code.
40    const EX_NONE: i32 = 0;
41
42    // ── Interface constants ───────────────────────────────────────────────────
43
44    const ACTIVITY_MANAGER_DESCRIPTOR: &[u8] = b"android.app.IActivityManager\0";
45    const ACTIVITY_SERVICE_NAME: &[u8] = b"activity\0";
46    const LIBBINDER_NDK_PATH: &[u8] = b"/system/lib64/libbinder_ndk.so\0";
47
48    // ── Tx code cache ─────────────────────────────────────────────────────────
49    //
50    // Format written by fgw (4 space-separated values):
51    //   observer_code  focused_task_code  api_mode  fg_activities_code
52    // api_mode: 1 = getFocusedRootTaskInfo (API 30+), 2 = getFocusedStackInfo (API 29)
53
54    const TX_CACHE_PATH: &str = "/data/local/tmp/coreshift/tx_code.txt";
55
56    // ── SDK-version tx code table ─────────────────────────────────────────────
57
58    #[derive(Clone, Copy)]
59    struct TxEntry { api: u32, code: i32, legacy: bool }
60
61    static TX_TABLE: &[TxEntry] = &[
62        TxEntry { api: 29, code: 157, legacy: true  },
63        TxEntry { api: 30, code: 168, legacy: false },
64        TxEntry { api: 31, code: 176, legacy: false },
65        TxEntry { api: 32, code: 178, legacy: false },
66        TxEntry { api: 33, code: 181, legacy: false },
67        TxEntry { api: 34, code: 183, legacy: false },
68        TxEntry { api: 35, code: 187, legacy: false },
69    ];
70
71    const PROBE_WINDOW: i32 = 12;
72
73    // ── Raw NDK type aliases ──────────────────────────────────────────────────
74
75    type AIBinder = c_void;
76    #[allow(non_camel_case_types)]
77    type AIBinder_Class = c_void;
78    type AParcel = c_void;
79    type BinderStatus = i32;
80    type StringAllocator =
81        unsafe extern "C" fn(*mut c_void, i32, *mut *mut c_char) -> bool;
82
83    // ── AIBinder_Class no-op callbacks (client-side only) ────────────────────
84
85    unsafe extern "C" fn on_create(_: *mut c_void) -> *mut c_void { std::ptr::null_mut() }
86    unsafe extern "C" fn on_destroy(_: *mut c_void) {}
87    unsafe extern "C" fn on_transact(
88        _: *mut AIBinder, _: u32, _: *const AParcel, _: *mut AParcel,
89    ) -> BinderStatus { STATUS_UNKNOWN_TRANSACTION }
90
91    // ── String allocator callback ─────────────────────────────────────────────
92
93    unsafe extern "C" fn string_alloc(
94        cookie: *mut c_void,
95        length: i32,
96        buffer: *mut *mut c_char,
97    ) -> bool {
98        if length < 0 { return true; } // null string
99        let s = unsafe { &mut *(cookie as *mut StringBuf) };
100        s.0.reserve_exact(length as usize + 1);
101        unsafe { s.0.as_mut_vec().resize(length as usize + 1, 0) };
102        unsafe { *buffer = s.0.as_mut_ptr() as *mut c_char };
103        true
104    }
105
106    struct StringBuf(String);
107
108    impl StringBuf {
109        fn new() -> Self { Self(String::new()) }
110
111        fn finish(mut self) -> Option<String> {
112            // Strip the trailing NUL written by the NDK allocator.
113            if let Some(pos) = self.0.as_bytes().iter().position(|&b| b == 0) {
114                unsafe { self.0.as_mut_vec().truncate(pos) };
115            }
116            if self.0.is_empty() { None } else { Some(self.0) }
117        }
118    }
119
120    // ── NDK vtable ────────────────────────────────────────────────────────────
121
122    struct Vtable {
123        get_service:         unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
124        #[allow(dead_code)]
125        wait_for_service:    unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
126        class_define:        unsafe extern "C" fn(
127                                 *const c_char,
128                                 unsafe extern "C" fn(*mut c_void) -> *mut c_void,
129                                 unsafe extern "C" fn(*mut c_void),
130                                 unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
131                             ) -> *mut AIBinder_Class,
132        associate_class:     unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
133        prepare_transaction: unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
134        transact:            unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus,
135        dec_strong:          unsafe extern "C" fn(*mut AIBinder),
136        parcel_delete:       unsafe extern "C" fn(*mut AParcel),
137        read_int32:          unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
138        #[allow(dead_code)]
139        read_bool:           unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus,
140        read_string:         unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus,
141    }
142
143    // ── RAII wrappers ─────────────────────────────────────────────────────────
144
145    struct DlHandle(*mut c_void);
146    unsafe impl Send for DlHandle {}
147    impl Drop for DlHandle {
148        fn drop(&mut self) {
149            if !self.0.is_null() { unsafe { libc::dlclose(self.0) }; }
150        }
151    }
152
153    struct OwnedParcel {
154        ptr: *mut AParcel,
155        delete: unsafe extern "C" fn(*mut AParcel),
156    }
157    impl Drop for OwnedParcel {
158        fn drop(&mut self) {
159            if !self.ptr.is_null() { unsafe { (self.delete)(self.ptr) }; }
160        }
161    }
162
163    struct OwnedBinder {
164        ptr: *mut AIBinder,
165        dec_strong: unsafe extern "C" fn(*mut AIBinder),
166    }
167    unsafe impl Send for OwnedBinder {}
168    impl Drop for OwnedBinder {
169        fn drop(&mut self) {
170            if !self.ptr.is_null() { unsafe { (self.dec_strong)(self.ptr) }; }
171        }
172    }
173
174    // ── dlsym helper ─────────────────────────────────────────────────────────
175
176    macro_rules! dlsym_fn {
177        ($handle:expr, $name:literal, $ty:ty) => {{
178            let sym = unsafe {
179                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
180            };
181            if sym.is_null() {
182                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
183            }
184            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
185        }};
186    }
187
188    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
189        Ok(Vtable {
190            get_service: dlsym_fn!(handle, "AServiceManager_getService",
191                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
192            wait_for_service: dlsym_fn!(handle, "AServiceManager_waitForService",
193                unsafe extern "C" fn(*const c_char) -> *mut AIBinder),
194            class_define: dlsym_fn!(handle, "AIBinder_Class_define",
195                unsafe extern "C" fn(
196                    *const c_char,
197                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
198                    unsafe extern "C" fn(*mut c_void),
199                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel) -> BinderStatus,
200                ) -> *mut AIBinder_Class),
201            associate_class: dlsym_fn!(handle, "AIBinder_associateClass",
202                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool),
203            prepare_transaction: dlsym_fn!(handle, "AIBinder_prepareTransaction",
204                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus),
205            transact: dlsym_fn!(handle, "AIBinder_transact",
206                unsafe extern "C" fn(*mut AIBinder, u32, *mut *mut AParcel, *mut *mut AParcel, u32) -> BinderStatus),
207            dec_strong: dlsym_fn!(handle, "AIBinder_decStrong",
208                unsafe extern "C" fn(*mut AIBinder)),
209            parcel_delete: dlsym_fn!(handle, "AParcel_delete",
210                unsafe extern "C" fn(*mut AParcel)),
211            read_int32: dlsym_fn!(handle, "AParcel_readInt32",
212                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus),
213            read_bool: dlsym_fn!(handle, "AParcel_readBool",
214                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus),
215            read_string: dlsym_fn!(handle, "AParcel_readString",
216                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus),
217        })
218    }
219
220    // ── ParcelReader — safe parcel API ────────────────────────────────────────
221
222    struct ParcelReader<'a> {
223        vt: &'a Vtable,
224        parcel: &'a OwnedParcel,
225    }
226
227    impl<'a> ParcelReader<'a> {
228        fn read_i32(&self) -> Result<i32, CoreError> {
229            let mut v = 0i32;
230            let s = unsafe { (self.vt.read_int32)(self.parcel.ptr, &mut v) };
231            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readInt32")); }
232            Ok(v)
233        }
234
235        fn read_string(&self) -> Result<Option<String>, CoreError> {
236            let mut buf = StringBuf::new();
237            let s = unsafe {
238                (self.vt.read_string)(
239                    self.parcel.ptr,
240                    &mut buf as *mut StringBuf as *mut c_void,
241                    string_alloc,
242                )
243            };
244            if s != STATUS_OK { return Err(CoreError::binder(s, "AParcel_readString")); }
245            Ok(buf.finish())
246        }
247
248        fn skip_i32s(&self, n: usize) -> Result<(), CoreError> {
249            for _ in 0..n { self.read_i32()?; }
250            Ok(())
251        }
252
253        fn skip_int_array(&self) -> Result<(), CoreError> {
254            let count = self.read_i32()?.max(0) as usize;
255            self.skip_i32s(count)
256        }
257
258        // Read component name strings (count + strings), return first package.
259        // Component format: "com.pkg/.Activity" — package is left of '/'.
260        fn read_first_package_from_names(&self) -> Result<Option<String>, CoreError> {
261            let count = self.read_i32()?.max(0) as usize;
262            let mut first: Option<String> = None;
263            for _ in 0..count {
264                let s = self.read_string()?;
265                if first.is_none() {
266                    first = s.and_then(|c| c.split('/').next().map(str::to_owned));
267                }
268            }
269            Ok(first)
270        }
271    }
272
273    // ── Response parsers (safe — no unsafe blocks) ────────────────────────────
274    //
275    // Called after exception (int32=0) and present (int32!=0) are consumed
276    // by get_focused_package. Mirrors watcher.c parseRootTaskInfoReply /
277    // parseStackInfoReply exactly.
278
279    // getFocusedRootTaskInfo (API 30+)
280    fn parse_root_task_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
281        // scratch flag; if non-zero, 4 more ints follow (bounds-like fields)
282        let scratch = r.read_i32()?;
283        if scratch != 0 { r.skip_i32s(4)?; }
284        r.skip_int_array()?;               // child task IDs
285        r.read_first_package_from_names()  // child task names → first package
286    }
287
288    // getFocusedStackInfo (API 29)
289    fn parse_stack_info_body(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
290        r.skip_i32s(5)?;                   // scratch fields
291        r.skip_int_array()?;               // task IDs
292        r.read_first_package_from_names()  // task names → first package
293    }
294
295    // ── Tx code resolution ────────────────────────────────────────────────────
296
297    fn read_tx_cache() -> Option<(i32, bool)> {
298        let text = std::fs::read_to_string(TX_CACHE_PATH).ok()?;
299        // observer_code  focused_task_code  api_mode  fg_activities_code
300        let mut parts = text.split_whitespace();
301        let _observer: i32  = parts.next()?.parse().ok()?;
302        let tx_code: i32    = parts.next()?.parse().ok()?;
303        let api_mode: i32   = parts.next()?.parse().ok()?;
304        if tx_code <= 0 { return None; }
305        Some((tx_code, api_mode == 2)) // api_mode 2 = getFocusedStackInfo (legacy)
306    }
307
308    fn sdk_version() -> Option<u32> {
309        android_property_get("ro.build.version.sdk")
310            .and_then(|s| s.trim().parse().ok())
311    }
312
313    fn table_lookup(api: u32) -> Option<(i32, bool)> {
314        TX_TABLE.iter()
315            .filter(|e| e.api <= api)
316            .last()
317            .map(|e| (e.code, e.legacy))
318    }
319
320    fn probe_window(api: u32) -> impl Iterator<Item = (i32, bool)> {
321        let centre = table_lookup(api).map(|(c, _)| c).unwrap_or(170);
322        let legacy_threshold = 165i32;
323        ((centre - PROBE_WINDOW)..(centre + PROBE_WINDOW))
324            .map(move |c| (c, c < legacy_threshold))
325    }
326
327    // ── ActivityManagerBinder ─────────────────────────────────────────────────
328
329    pub struct ActivityManagerBinder {
330        _lib:     DlHandle,
331        vt:       Vtable,
332        _class:   *mut AIBinder_Class,
333        service:  OwnedBinder,
334        tx_code:  i32,
335        legacy:   bool,
336    }
337
338    // SAFETY: AIBinder is internally reference-counted and thread-safe.
339    unsafe impl Send for ActivityManagerBinder {}
340
341    impl ActivityManagerBinder {
342        pub fn open() -> Result<Self, CoreError> {
343            let handle = unsafe {
344                libc::dlopen(
345                    LIBBINDER_NDK_PATH.as_ptr() as *const c_char,
346                    libc::RTLD_NOW | libc::RTLD_LOCAL,
347                )
348            };
349            if handle.is_null() {
350                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
351            }
352            let lib = DlHandle(handle);
353            let vt = load_vtable(handle)?;
354
355            let class = unsafe {
356                (vt.class_define)(
357                    ACTIVITY_MANAGER_DESCRIPTOR.as_ptr() as *const c_char,
358                    on_create, on_destroy, on_transact,
359                )
360            };
361            if class.is_null() {
362                return Err(CoreError::binder(-1, "AIBinder_Class_define"));
363            }
364
365            let raw_service = unsafe {
366                (vt.get_service)(ACTIVITY_SERVICE_NAME.as_ptr() as *const c_char)
367            };
368            if raw_service.is_null() {
369                return Err(CoreError::binder(-1, "AServiceManager_getService:activity"));
370            }
371            unsafe { (vt.associate_class)(raw_service, class) };
372
373            let service = OwnedBinder { ptr: raw_service, dec_strong: vt.dec_strong };
374            let (tx_code, legacy) = Self::resolve_tx(&vt, raw_service, class)?;
375
376            Ok(Self { _lib: lib, vt, _class: class, service, tx_code, legacy })
377        }
378
379        fn resolve_tx(
380            vt: &Vtable,
381            service: *mut AIBinder,
382            _class: *mut AIBinder_Class,
383        ) -> Result<(i32, bool), CoreError> {
384            if let Some(pair) = read_tx_cache() {
385                return Ok(pair);
386            }
387
388            let api = sdk_version().unwrap_or(30);
389
390            let pair = if let Some(pair) = table_lookup(api) {
391                if Self::tx_code_valid(vt, service, pair.0) {
392                    pair
393                } else {
394                    probe_window(api)
395                        .find(|&(code, _)| Self::tx_code_valid(vt, service, code))
396                        .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution"))?
397                }
398            } else {
399                probe_window(api)
400                    .find(|&(code, _)| Self::tx_code_valid(vt, service, code))
401                    .ok_or_else(|| CoreError::binder(-1, "tx_code_resolution"))?
402            };
403
404            // Persist so future daemon restarts skip probe entirely.
405            // Format: observer_code focused_task_code api_mode fg_code
406            // We only own focused_task_code; write 0 for fields fgw manages.
407            let api_mode = if pair.1 { 2i32 } else { 1i32 };
408            let _ = std::fs::create_dir_all("/data/local/tmp/coreshift");
409            let _ = std::fs::write(TX_CACHE_PATH, format!("0 {} {} 0\n", pair.0, api_mode));
410
411            Ok(pair)
412        }
413
414        fn tx_code_valid(vt: &Vtable, service: *mut AIBinder, code: i32) -> bool {
415            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
416            if unsafe { (vt.prepare_transaction)(service, &mut in_ptr) } != STATUS_OK {
417                return false;
418            }
419            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
420            let s = unsafe {
421                (vt.transact)(service, code as u32, &mut in_ptr, &mut out_ptr, 0)
422            };
423            let out = OwnedParcel { ptr: out_ptr, delete: vt.parcel_delete };
424            if s != STATUS_OK { return false; }
425            let r = ParcelReader { vt, parcel: &out };
426            r.read_i32().map(|ex| ex == EX_NONE).unwrap_or(false)
427            // out dropped here → parcel_delete called automatically
428        }
429
430        fn do_transact(&self) -> Result<OwnedParcel, CoreError> {
431            let mut in_ptr: *mut AParcel = std::ptr::null_mut();
432            let s = unsafe { (self.vt.prepare_transaction)(self.service.ptr, &mut in_ptr) };
433            if s != STATUS_OK {
434                return Err(CoreError::binder(s, "AIBinder_prepareTransaction"));
435            }
436            let mut out_ptr: *mut AParcel = std::ptr::null_mut();
437            let s = unsafe {
438                (self.vt.transact)(
439                    self.service.ptr, self.tx_code as u32,
440                    &mut in_ptr, &mut out_ptr, 0,
441                )
442            };
443            let out = OwnedParcel { ptr: out_ptr, delete: self.vt.parcel_delete };
444            if s != STATUS_OK {
445                return Err(CoreError::binder(s, "AIBinder_transact"));
446            }
447            Ok(out)
448        }
449
450        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
451            let out = self.do_transact()?;
452            let r = ParcelReader { vt: &self.vt, parcel: &out };
453
454            let ex = r.read_i32()?;
455            if ex != EX_NONE {
456                return Err(CoreError::binder(ex, "getFocusedTask:exception"));
457            }
458            let present = r.read_i32()?;
459            if present == 0 {
460                return Ok(None);
461            }
462            if self.legacy {
463                parse_stack_info_body(&r)
464            } else {
465                parse_root_task_info_body(&r)
466            }
467            // out dropped here → parcel_delete called automatically
468        }
469    }
470}
471
472// ── Public re-exports ─────────────────────────────────────────────────────────
473
474#[cfg(target_os = "android")]
475pub use imp::ActivityManagerBinder;
476
477// ── Non-Android stub ──────────────────────────────────────────────────────────
478
479#[cfg(not(target_os = "android"))]
480pub struct ActivityManagerBinder;
481
482#[cfg(not(target_os = "android"))]
483impl ActivityManagerBinder {
484    pub fn open() -> Result<Self, CoreError> {
485        Err(CoreError::binder(-1, "binder:unsupported platform"))
486    }
487
488    pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
489        Err(CoreError::binder(-1, "binder:unsupported platform"))
490    }
491}