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/watcher_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
33
34    // ── NDK binder status codes ───────────────────────────────────────────────
35
36    const STATUS_OK: i32 = 0;
37    // Returned by the kernel when the tx code is unknown to the stub.
38    const STATUS_UNKNOWN_TRANSACTION: i32 = -2;
39
40    // ── Parcel exception codes written by Java stubs ──────────────────────────
41
42    // writeNoException() writes 0; any non-zero value is a Java exception code.
43    const EX_NONE: i32 = 0;
44
45    // ── Interface descriptor ──────────────────────────────────────────────────
46
47    const ACTIVITY_MANAGER_DESCRIPTOR: &[u8] = b"android.app.IActivityManager\0";
48    const ACTIVITY_SERVICE_NAME: &[u8] = b"activity\0";
49    const LIBBINDER_NDK_PATH: &[u8] = b"/system/lib64/libbinder_ndk.so\0";
50
51    // ── Tx code cache path ────────────────────────────────────────────────────
52
53    const TX_CACHE_PATH: &str = "/data/local/tmp/watcher_tx_code.txt";
54
55    // ── SDK-version tx code table ─────────────────────────────────────────────
56    //
57    // Codes shift each AOSP release. Derived from AOSP git history for stock
58    // Android. Custom ROMs may differ; the probe fallback handles those.
59    //
60    // Android 10 (29): no getFocusedRootTaskInfo; use getFocusedStackInfo ≈ 157.
61    // Android 11 (30): getFocusedRootTaskInfo first appears ≈ 168.
62    // Android 12 (31): ≈ 176.  Android 12L (32): ≈ 178.
63    // Android 13 (33): ≈ 181.  Android 14 (34): ≈ 183.
64    // Android 15 (35): ≈ 187.
65
66    #[derive(Clone, Copy)]
67    struct TxEntry {
68        api: u32,
69        code: i32,
70        legacy: bool,
71    }
72
73    static TX_TABLE: &[TxEntry] = &[
74        TxEntry { api: 29, code: 157, legacy: true  },
75        TxEntry { api: 30, code: 168, legacy: false },
76        TxEntry { api: 31, code: 176, legacy: false },
77        TxEntry { api: 32, code: 178, legacy: false },
78        TxEntry { api: 33, code: 181, legacy: false },
79        TxEntry { api: 34, code: 183, legacy: false },
80        TxEntry { api: 35, code: 187, legacy: false },
81    ];
82
83    const PROBE_WINDOW: i32 = 12;
84    use std::os::raw::{c_char, c_void};
85
86    // ── Raw NDK binder type aliases ───────────────────────────────────────────
87
88    type AIBinder = c_void;
89    #[allow(non_camel_case_types)]
90    type AIBinder_Class = c_void;
91    type AParcel = c_void;
92
93    type BinderStatus = i32;
94
95    // AParcel_stringAllocator: called by AParcel_readString to allocate the
96    // output buffer. We use a Box<String> as the cookie.
97    type StringAllocator =
98        unsafe extern "C" fn(string_data: *mut c_void, length: i32, buffer: *mut *mut c_char)
99            -> bool;
100
101    // AIBinder_Class callbacks (no-ops for client-side use).
102    unsafe extern "C" fn on_create(_args: *mut c_void) -> *mut c_void {
103        std::ptr::null_mut()
104    }
105    unsafe extern "C" fn on_destroy(_user_data: *mut c_void) {}
106    unsafe extern "C" fn on_transact(
107        _binder: *mut AIBinder,
108        _code: u32,
109        _in_parcel: *const AParcel,
110        _out_parcel: *mut AParcel,
111    ) -> BinderStatus {
112        STATUS_UNKNOWN_TRANSACTION
113    }
114
115    // ── String allocator callback ─────────────────────────────────────────────
116
117    unsafe extern "C" fn string_alloc(
118        string_data: *mut c_void,
119        length: i32,
120        buffer: *mut *mut c_char,
121    ) -> bool {
122        if length < 0 {
123            // null string
124            return true;
125        }
126        let s = unsafe { &mut *(string_data as *mut StringBuf) };
127        s.buf.resize((length as usize) + 1, 0u8);
128        unsafe { *buffer = s.buf.as_mut_ptr() as *mut c_char };
129        true
130    }
131
132    struct StringBuf {
133        buf: Vec<u8>,
134    }
135
136    impl StringBuf {
137        fn new() -> Self {
138            Self { buf: Vec::new() }
139        }
140
141        fn as_str(&self) -> Option<&str> {
142            if self.buf.is_empty() {
143                return None;
144            }
145            let len = self.buf.iter().position(|&b| b == 0).unwrap_or(self.buf.len());
146            std::str::from_utf8(&self.buf[..len]).ok()
147        }
148    }
149
150    // ── NDK vtable ────────────────────────────────────────────────────────────
151
152    struct Vtable {
153        // ServiceManager
154        get_service: unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
155        #[allow(dead_code)]
156        wait_for_service: unsafe extern "C" fn(*const c_char) -> *mut AIBinder,
157        // AIBinder_Class
158        class_define: unsafe extern "C" fn(
159            descriptor: *const c_char,
160            on_create: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
161            on_destroy: unsafe extern "C" fn(*mut c_void),
162            on_transact: unsafe extern "C" fn(
163                *mut AIBinder,
164                u32,
165                *const AParcel,
166                *mut AParcel,
167            ) -> BinderStatus,
168        ) -> *mut AIBinder_Class,
169        associate_class:
170            unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool,
171        // AIBinder
172        prepare_transaction:
173            unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus,
174        transact: unsafe extern "C" fn(
175            *mut AIBinder,
176            u32,
177            *mut *mut AParcel,
178            *mut *mut AParcel,
179            u32,
180        ) -> BinderStatus,
181        dec_strong: unsafe extern "C" fn(*mut AIBinder),
182        // AParcel
183        parcel_delete: unsafe extern "C" fn(*mut AParcel),
184        read_int32: unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus,
185        #[allow(dead_code)]
186        read_bool: unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus,
187        read_string: unsafe extern "C" fn(
188            *const AParcel,
189            *mut c_void,
190            StringAllocator,
191        ) -> BinderStatus,
192    }
193
194    struct DlHandle(*mut c_void);
195    unsafe impl Send for DlHandle {}
196    impl Drop for DlHandle {
197        fn drop(&mut self) {
198            if !self.0.is_null() {
199                unsafe { libc::dlclose(self.0) };
200            }
201        }
202    }
203
204    macro_rules! dlsym_fn {
205        ($handle:expr, $name:literal, $ty:ty) => {{
206            let sym = unsafe {
207                libc::dlsym($handle, concat!($name, "\0").as_ptr() as *const c_char)
208            };
209            if sym.is_null() {
210                return Err(CoreError::binder(-1, concat!("dlsym:", $name)));
211            }
212            unsafe { std::mem::transmute::<*mut c_void, $ty>(sym) }
213        }};
214    }
215
216    fn load_vtable(handle: *mut c_void) -> Result<Vtable, CoreError> {
217        Ok(Vtable {
218            get_service: dlsym_fn!(
219                handle,
220                "AServiceManager_getService",
221                unsafe extern "C" fn(*const c_char) -> *mut AIBinder
222            ),
223            wait_for_service: dlsym_fn!(
224                handle,
225                "AServiceManager_waitForService",
226                unsafe extern "C" fn(*const c_char) -> *mut AIBinder
227            ),
228            class_define: dlsym_fn!(
229                handle,
230                "AIBinder_Class_define",
231                unsafe extern "C" fn(
232                    *const c_char,
233                    unsafe extern "C" fn(*mut c_void) -> *mut c_void,
234                    unsafe extern "C" fn(*mut c_void),
235                    unsafe extern "C" fn(*mut AIBinder, u32, *const AParcel, *mut AParcel)
236                        -> BinderStatus,
237                ) -> *mut AIBinder_Class
238            ),
239            associate_class: dlsym_fn!(
240                handle,
241                "AIBinder_associateClass",
242                unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_Class) -> bool
243            ),
244            prepare_transaction: dlsym_fn!(
245                handle,
246                "AIBinder_prepareTransaction",
247                unsafe extern "C" fn(*mut AIBinder, *mut *mut AParcel) -> BinderStatus
248            ),
249            transact: dlsym_fn!(
250                handle,
251                "AIBinder_transact",
252                unsafe extern "C" fn(
253                    *mut AIBinder,
254                    u32,
255                    *mut *mut AParcel,
256                    *mut *mut AParcel,
257                    u32,
258                ) -> BinderStatus
259            ),
260            dec_strong: dlsym_fn!(
261                handle,
262                "AIBinder_decStrong",
263                unsafe extern "C" fn(*mut AIBinder)
264            ),
265            parcel_delete: dlsym_fn!(
266                handle,
267                "AParcel_delete",
268                unsafe extern "C" fn(*mut AParcel)
269            ),
270            read_int32: dlsym_fn!(
271                handle,
272                "AParcel_readInt32",
273                unsafe extern "C" fn(*const AParcel, *mut i32) -> BinderStatus
274            ),
275            read_bool: dlsym_fn!(
276                handle,
277                "AParcel_readBool",
278                unsafe extern "C" fn(*const AParcel, *mut bool) -> BinderStatus
279            ),
280            read_string: dlsym_fn!(
281                handle,
282                "AParcel_readString",
283                unsafe extern "C" fn(*const AParcel, *mut c_void, StringAllocator) -> BinderStatus
284            ),
285        })
286    }
287
288    // ── Parcel helpers ────────────────────────────────────────────────────────
289
290    struct ParcelReader<'a> {
291        vt: &'a Vtable,
292        parcel: *const AParcel,
293    }
294
295    impl<'a> ParcelReader<'a> {
296        fn read_i32(&self) -> Result<i32, CoreError> {
297            let mut v = 0i32;
298            let s = unsafe { (self.vt.read_int32)(self.parcel, &mut v) };
299            if s != STATUS_OK {
300                return Err(CoreError::binder(s, "AParcel_readInt32"));
301            }
302            Ok(v)
303        }
304
305        #[allow(dead_code)]
306        fn read_bool(&self) -> Result<bool, CoreError> {
307            let mut v = false;
308            let s = unsafe { (self.vt.read_bool)(self.parcel, &mut v) };
309            if s != STATUS_OK {
310                return Err(CoreError::binder(s, "AParcel_readBool"));
311            }
312            Ok(v)
313        }
314
315        fn read_string(&self) -> Result<Option<String>, CoreError> {
316            let mut buf = StringBuf::new();
317            let s = unsafe {
318                (self.vt.read_string)(
319                    self.parcel,
320                    &mut buf as *mut StringBuf as *mut c_void,
321                    string_alloc,
322                )
323            };
324            if s != STATUS_OK {
325                return Err(CoreError::binder(s, "AParcel_readString"));
326            }
327            Ok(buf.as_str().map(str::to_owned))
328        }
329
330        // Skip a nullable Parcelable (int32 presence flag + 4 int32s if present).
331        fn skip_nullable_rect(&self) -> Result<(), CoreError> {
332            let present = self.read_i32()?;
333            if present != 0 {
334                for _ in 0..4 {
335                    self.read_i32()?;
336                }
337            }
338            Ok(())
339        }
340
341        // Skip an int32 array (count + count × int32).
342        fn skip_int_array(&self) -> Result<(), CoreError> {
343            let count = self.read_i32()?;
344            for _ in 0..count.max(0) {
345                self.read_i32()?;
346            }
347            Ok(())
348        }
349
350        // Skip a string array (count + count × string).
351        fn skip_string_array(&self) -> Result<(), CoreError> {
352            let count = self.read_i32()?;
353            for _ in 0..count.max(0) {
354                self.read_string()?;
355            }
356            Ok(())
357        }
358
359        // Skip a typed array of Rects (count + count × nullable_rect).
360        fn skip_rect_array(&self) -> Result<(), CoreError> {
361            let count = self.read_i32()?;
362            for _ in 0..count.max(0) {
363                // Each element written via writeTypedObject: presence flag + 4 ints.
364                self.skip_nullable_rect()?;
365            }
366            Ok(())
367        }
368
369        // Read a nullable ComponentName → (package, class) or None.
370        fn read_component_name(&self) -> Result<Option<(String, String)>, CoreError> {
371            let present = self.read_i32()?;
372            if present == 0 {
373                return Ok(None);
374            }
375            let pkg = self.read_string()?.unwrap_or_default();
376            let cls = self.read_string()?.unwrap_or_default();
377            if pkg.is_empty() {
378                return Ok(None);
379            }
380            Ok(Some((pkg, cls)))
381        }
382    }
383
384    // ── Response parsing ──────────────────────────────────────────────────────
385
386    // Android 11+ response: getFocusedRootTaskInfo
387    //   exception (int32=0)
388    //   non-null flag for RootTaskInfo (int32: 0=null, 1=present)
389    //   topActivity nullable ComponentName (int32 flag + string + string)
390    fn parse_root_task_info(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
391        let ex = r.read_i32()?;
392        if ex != EX_NONE {
393            return Err(CoreError::binder(ex, "getFocusedRootTaskInfo:exception"));
394        }
395        let has_info = r.read_i32()?;
396        if has_info == 0 {
397            return Ok(None);
398        }
399        Ok(r.read_component_name()?.map(|(pkg, _)| pkg))
400    }
401
402    // Android 10 response: getFocusedStackInfo
403    //   exception (int32=0)
404    //   stackId (int32)
405    //   bounds nullable Rect (int32 flag + 4 int32s)
406    //   taskIds int32[]
407    //   taskNames String[]
408    //   taskBounds Rect[] (count + count × nullable_rect)
409    //   taskUserIds int32[]
410    //   topActivity nullable ComponentName
411    fn parse_stack_info(r: &ParcelReader<'_>) -> Result<Option<String>, CoreError> {
412        let ex = r.read_i32()?;
413        if ex != EX_NONE {
414            return Err(CoreError::binder(ex, "getFocusedStackInfo:exception"));
415        }
416        r.read_i32()?;              // stackId
417        r.skip_nullable_rect()?;    // bounds
418        r.skip_int_array()?;        // taskIds
419        r.skip_string_array()?;     // taskNames
420        r.skip_rect_array()?;       // taskBounds
421        r.skip_int_array()?;        // taskUserIds
422        Ok(r.read_component_name()?.map(|(pkg, _)| pkg))
423    }
424
425    // ── Tx code resolution ────────────────────────────────────────────────────
426
427    fn read_tx_cache() -> Option<(i32, bool)> {
428        let text = std::fs::read_to_string(TX_CACHE_PATH).ok()?;
429        let mut parts = text.split_whitespace();
430        let root: i32 = parts.next()?.parse().ok()?;
431        let stack: i32 = parts.next()?.parse().ok()?;
432        // First value is getFocusedRootTaskInfo code; if zero/invalid fall back.
433        if root > 0 {
434            Some((root, false))
435        } else if stack > 0 {
436            Some((stack, true))
437        } else {
438            None
439        }
440    }
441
442    fn sdk_version() -> Option<u32> {
443        android_property_get("ro.build.version.sdk")
444            .and_then(|s| s.trim().parse().ok())
445    }
446
447    fn table_lookup(api: u32) -> Option<(i32, bool)> {
448        // Walk the table and return the entry whose api matches; if exact match
449        // not found use the nearest lower entry.
450        let mut best: Option<&TxEntry> = None;
451        for entry in TX_TABLE {
452            if entry.api <= api {
453                best = Some(entry);
454            }
455        }
456        best.map(|e| (e.code, e.legacy))
457    }
458
459    fn probe_window(api: u32) -> std::ops::Range<i32> {
460        // Centre the probe window on the table entry for this API level.
461        let centre = table_lookup(api).map(|(c, _)| c).unwrap_or(170);
462        (centre - PROBE_WINDOW)..(centre + PROBE_WINDOW)
463    }
464
465    // ── ActivityManagerBinder ─────────────────────────────────────────────────
466
467    pub struct ActivityManagerBinder {
468        _lib: DlHandle,
469        vt: Vtable,
470        _class: *mut AIBinder_Class,
471        service: *mut AIBinder,
472        tx_code: i32,
473        legacy: bool,
474    }
475
476    // SAFETY: AIBinder is internally reference-counted and thread-safe.
477    unsafe impl Send for ActivityManagerBinder {}
478
479    impl ActivityManagerBinder {
480        pub fn open() -> Result<Self, CoreError> {
481            // dlopen libbinder_ndk.so
482            let handle = unsafe {
483                libc::dlopen(
484                    LIBBINDER_NDK_PATH.as_ptr() as *const c_char,
485                    libc::RTLD_NOW | libc::RTLD_LOCAL,
486                )
487            };
488            if handle.is_null() {
489                return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
490            }
491            let lib = DlHandle(handle);
492            let vt = load_vtable(handle)?;
493
494            // Define the class (client-side, no real transaction handler).
495            let class = unsafe {
496                (vt.class_define)(
497                    ACTIVITY_MANAGER_DESCRIPTOR.as_ptr() as *const c_char,
498                    on_create,
499                    on_destroy,
500                    on_transact,
501                )
502            };
503            if class.is_null() {
504                return Err(CoreError::binder(-1, "AIBinder_Class_define"));
505            }
506
507            // Get ActivityManager binder handle.
508            let service = unsafe {
509                (vt.get_service)(ACTIVITY_SERVICE_NAME.as_ptr() as *const c_char)
510            };
511            if service.is_null() {
512                return Err(CoreError::binder(-1, "AServiceManager_getService:activity"));
513            }
514
515            // Associate class so prepareTransaction writes the interface descriptor.
516            unsafe { (vt.associate_class)(service, class) };
517
518            // Resolve the transaction code.
519            let (tx_code, legacy) = Self::resolve_tx(&vt, service, class)?;
520
521            Ok(Self { _lib: lib, vt, _class: class, service, tx_code, legacy })
522        }
523
524        fn resolve_tx(
525            vt: &Vtable,
526            service: *mut AIBinder,
527            _class: *mut AIBinder_Class,
528        ) -> Result<(i32, bool), CoreError> {
529            // 1. Cache file written by fgw.
530            if let Some(pair) = read_tx_cache() {
531                return Ok(pair);
532            }
533
534            // 2. SDK-version table.
535            let api = sdk_version().unwrap_or(30);
536            if let Some(pair) = table_lookup(api) {
537                // Validate the table entry before trusting it.
538                if Self::tx_code_valid(vt, service, pair.0, pair.1) {
539                    return Ok(pair);
540                }
541            }
542
543            // 3. Linear probe.
544            let window = probe_window(api);
545            for code in window {
546                let is_legacy = code < 165; // rough heuristic
547                if Self::tx_code_valid(vt, service, code, is_legacy) {
548                    return Ok((code, is_legacy));
549                }
550            }
551
552            Err(CoreError::binder(-1, "tx_code_resolution"))
553        }
554
555        fn tx_code_valid(
556            vt: &Vtable,
557            service: *mut AIBinder,
558            code: i32,
559            _legacy: bool,
560        ) -> bool {
561            let mut in_parcel: *mut AParcel = std::ptr::null_mut();
562            let s = unsafe { (vt.prepare_transaction)(service, &mut in_parcel) };
563            if s != STATUS_OK {
564                return false;
565            }
566
567            let mut out_parcel: *mut AParcel = std::ptr::null_mut();
568            let s = unsafe {
569                (vt.transact)(service, code as u32, &mut in_parcel, &mut out_parcel, 0)
570            };
571            if s != STATUS_OK {
572                if !out_parcel.is_null() {
573                    unsafe { (vt.parcel_delete)(out_parcel) };
574                }
575                return false;
576            }
577
578            let r = ParcelReader { vt, parcel: out_parcel };
579            let ex = r.read_i32().unwrap_or(-1);
580            unsafe { (vt.parcel_delete)(out_parcel) };
581
582            // A valid transaction returns exception code 0.
583            // UNKNOWN_TRANSACTION and other errors produce non-zero exception codes
584            // or a non-OK binder status that was caught above.
585            ex == EX_NONE
586        }
587
588        fn do_transact(&self) -> Result<*mut AParcel, CoreError> {
589            let mut in_parcel: *mut AParcel = std::ptr::null_mut();
590            let s = unsafe { (self.vt.prepare_transaction)(self.service, &mut in_parcel) };
591            if s != STATUS_OK {
592                return Err(CoreError::binder(s, "AIBinder_prepareTransaction"));
593            }
594
595            let mut out_parcel: *mut AParcel = std::ptr::null_mut();
596            let s = unsafe {
597                (self.vt.transact)(
598                    self.service,
599                    self.tx_code as u32,
600                    &mut in_parcel,
601                    &mut out_parcel,
602                    0,
603                )
604            };
605            if s != STATUS_OK {
606                if !out_parcel.is_null() {
607                    unsafe { (self.vt.parcel_delete)(out_parcel) };
608                }
609                return Err(CoreError::binder(s, "AIBinder_transact"));
610            }
611
612            Ok(out_parcel)
613        }
614
615        pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
616            let out = self.do_transact()?;
617            let r = ParcelReader { vt: &self.vt, parcel: out };
618            let result = if self.legacy {
619                parse_stack_info(&r)
620            } else {
621                parse_root_task_info(&r)
622            };
623            unsafe { (self.vt.parcel_delete)(out) };
624            result
625        }
626    }
627
628    impl Drop for ActivityManagerBinder {
629        fn drop(&mut self) {
630            unsafe { (self.vt.dec_strong)(self.service) };
631        }
632    }
633}
634
635// ── Public re-exports ─────────────────────────────────────────────────────────
636
637#[cfg(target_os = "android")]
638pub use imp::ActivityManagerBinder;
639
640// ── Non-Android stub ──────────────────────────────────────────────────────────
641
642#[cfg(not(target_os = "android"))]
643pub struct ActivityManagerBinder;
644
645#[cfg(not(target_os = "android"))]
646impl ActivityManagerBinder {
647    pub fn open() -> Result<Self, CoreError> {
648        Err(CoreError::binder(-1, "binder:unsupported platform"))
649    }
650
651    pub fn get_focused_package(&self) -> Result<Option<String>, CoreError> {
652        Err(CoreError::binder(-1, "binder:unsupported platform"))
653    }
654}