cloudfox-coreshift-core 2.10.0

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/

//! Push-based task-stack change listener registered with `IActivityTaskManager`.

use super::sys::*;
use crate::CoreError;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::raw::{c_char, c_void};

// ── TaskStackListener (task-stack change wake-up) ────────────────────────

const TASK_SERVICE: &[u8] = b"activity_task\0";
const ATM_DESCRIPTOR: &[u8] = b"android.app.IActivityTaskManager\0";
const TASK_STACK_DESCRIPTOR: &[u8] = b"android.app.ITaskStackListener\0";

// No-op callbacks for the client-only IActivityTaskManager class (we never
// serve transactions on the `activity_task` binder — the class exists only
// to satisfy AIBinder_prepareTransaction's remote-transaction contract).
unsafe extern "C" fn atm_on_create(_: *mut c_void) -> *mut c_void {
    std::ptr::null_mut()
}
unsafe extern "C" fn atm_on_destroy(_: *mut c_void) {}
unsafe extern "C" fn atm_on_transact(
    _: *mut AIBinder,
    _: u32,
    _: *const AParcel,
    _: *mut AParcel,
) -> BinderStatus {
    STATUS_OK
}

// Pure wake-up handler: any ITaskStackListener callback
// (onTaskStackChanged, onTaskMovedToFront, …) just signals the eventfd.
// The callback arguments are deliberately NOT parsed — the authoritative
// (taskId, pkg) comes from re-querying getFocusedRootTaskInfo (txn 31) on
// the event, so we never depend on a parcel layout (RunningTaskInfo places
// taskId near the parcel tail).
//
// The wake eventfd is per-instance: the daemon hosts two listeners (the fg
// task source and the fps channel), each with its own eventfd. It is handed
// to AIBinder_new as the binder's userdata, so on_destroy must reclaim it.
// No process-wide static — a shared fd would deliver every instance's
// wake to whichever listener opened last.
//

unsafe extern "C" fn task_stack_on_create(userdata: *mut c_void) -> *mut c_void {
    userdata
}
unsafe extern "C" fn task_stack_on_destroy(userdata: *mut c_void) {
    if !userdata.is_null() {
        unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
    }
}
unsafe extern "C" fn task_stack_on_transact(
    binder: *mut AIBinder,
    _code: u32,
    _in_parcel: *const AParcel,
    _reply: *mut AParcel,
) -> BinderStatus {
    let get_user_data = GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner());
    if let Some(get_user_data) = *get_user_data {
        let userdata = unsafe { get_user_data(binder) };
        if !userdata.is_null() {
            let efd = userdata as *mut OwnedFd;
            let val: u64 = 1;
            unsafe { libc::write((*efd).as_raw_fd(), &val as *const u64 as *const c_void, 8) };
        }
    }
    STATUS_OK
}

/// Push-based task-stack change listener registered with
/// `IActivityTaskManager`.
///
/// Uses `IActivityTaskManager.registerTaskStackListener(listener)`; the
/// daemon hosts the `ITaskStackListener` server object. The callback is a
/// pure wake-up: on any task-stack change it signals the eventfd and
/// parses nothing. Consumers re-query `getFocusedRootTaskInfo` (txn 31) on
/// the event for the authoritative `(taskId, pkg)`.
///
/// This target's ROM exposes the legacy `ITaskStackListener` /
/// `registerTaskStackListener` pair; the newer `ITaskChangeListener` /
/// `registerTaskChangeListener` interface is absent.
///
/// The registering UID must hold `MANAGE_ACTIVITY_TASKS` /
/// `MANAGE_ACTIVITY_STACKS` — the same gate as txn 31, which root passes
/// empirically on the target ROM.
///
/// Returns `(Self, OwnedFd)` where the eventfd is a dup of the core's
/// callback fd. It becomes readable on any task-stack change.
pub struct TaskStackListener {
    _lib: DlHandle,
    vt: Vtable,
    service: OwnedBinder,
    cb_binder: *mut AIBinder,
    _atm_class: *mut AIBinder_Class,
    register_code: u32,
    unregister_code: u32,
}
unsafe impl Send for TaskStackListener {}

impl TaskStackListener {
    /// Open `activity_task`, define the `ITaskStackListener` server object,
    /// and start the binder thread pool. Does **not** register yet — call
    /// [`TaskStackListener::register`] once consumers are active.
    pub fn open() -> Result<(Self, OwnedFd), CoreError> {
        let handle = unsafe {
            libc::dlopen(
                LIBBINDER_PATH.as_ptr() as *const c_char,
                libc::RTLD_NOW | libc::RTLD_LOCAL,
            )
        };
        if handle.is_null() {
            return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
        }
        let lib = DlHandle;
        let vt = load_vtable(handle)?;

        let (register_code, unregister_code) = crate::android::dex::resolve_task_stack_codes()
            .ok_or_else(|| {
                CoreError::binder(-1, "dex:TRANSACTION_registerTaskStackListener not found")
            })?;

        let service = {
            let raw = unsafe { (vt.get_service)(TASK_SERVICE.as_ptr() as *const c_char) };
            if raw.is_null() {
                return Err(CoreError::binder(
                    -1,
                    "AServiceManager_getService:activity_task",
                ));
            }
            OwnedBinder {
                ptr: raw,
                dec_strong: vt.dec_strong,
            }
        };

        // Remote transactions require a class on the binder (same
        // AIBinder_prepareTransaction contract as the other services).
        let atm_class = unsafe {
            (vt.class_define)(
                ATM_DESCRIPTOR.as_ptr() as *const c_char,
                atm_on_create,
                atm_on_destroy,
                atm_on_transact,
            )
        };
        if atm_class.is_null() {
            return Err(CoreError::binder(
                -1,
                "AIBinder_Class_define:IActivityTaskManager",
            ));
        }
        unsafe { (vt.associate_class)(service.ptr, atm_class) };

        let cb_class = unsafe {
            (vt.class_define)(
                TASK_STACK_DESCRIPTOR.as_ptr() as *const c_char,
                task_stack_on_create,
                task_stack_on_destroy,
                task_stack_on_transact,
            )
        };
        if cb_class.is_null() {
            return Err(CoreError::binder(
                -1,
                "AIBinder_Class_define:ITaskStackListener",
            ));
        }

        // Blocking eventfd — callback writes, consumer waits/reads. Each
        // instance owns its own fd; it is handed to the callback binder as
        // userdata (per-instance routing, no process-wide static) and the
        // consumer receives a dup below (C2).
        let owned = unsafe {
            let raw = libc::eventfd(0, libc::EFD_CLOEXEC);
            if raw < 0 {
                return Err(CoreError::sys(*libc::__errno(), "eventfd"));
            }
            OwnedFd::from_raw_fd(raw)
        };

        let consumer = owned
            .try_clone()
            .map_err(|e| CoreError::sys(e.raw_os_error().unwrap_or(-1), "dup:task_stack"))?;

        let userdata = Box::into_raw(Box::new(owned)) as *mut c_void;
        let cb_binder = unsafe { (vt.new_binder)(cb_class, userdata) };
        if cb_binder.is_null() {
            // Reclaim the userdata box handed to AIBinder_new before bailing.
            unsafe { drop(Box::from_raw(userdata as *mut OwnedFd)) };
            return Err(CoreError::binder(-1, "AIBinder_new:ITaskStackListener"));
        }
        unsafe { (vt.associate_class)(cb_binder, cb_class) };

        // The callback resolves AIBinder_getUserData from this vtable; the
        // symbol address is process-wide, so a cached static is safe.
        *GET_USER_DATA.lock().unwrap_or_else(|p| p.into_inner()) = Some(vt.get_user_data);

        unsafe { (vt.set_thread_pool_max)(0) };
        let join_fn = vt.join_thread_pool;
        std::thread::spawn(move || unsafe { join_fn() });

        Ok((
            Self {
                _lib: lib,
                vt,
                service,
                cb_binder,
                _atm_class: atm_class,
                register_code,
                unregister_code,
            },
            consumer,
        ))
    }

    /// Register the task-stack listener with `activity_task` (one listener
    /// receives all task-stack events). Idempotent at the framework level;
    /// callers should register once and keep the object alive.
    pub fn register(&self) -> Result<(), CoreError> {
        let _ = transact_write(&self.vt, self.service.ptr, self.register_code, |w| {
            w.write_strong_binder(self.cb_binder)
        })?;
        Ok(())
    }

    /// Unregister the task-stack listener from `activity_task`. No-op at
    /// the framework level if not registered; callers should unregister
    /// before dropping the object.
    pub fn unregister(&self) -> Result<(), CoreError> {
        let _ = transact_write(&self.vt, self.service.ptr, self.unregister_code, |w| {
            w.write_strong_binder(self.cb_binder)
        })?;
        Ok(())
    }
}

impl Drop for TaskStackListener {
    /// Best-effort deregistration from `activity_task`. Same deliberate
    /// non-release of the local strong ref as [`FpsListener`] — the
    /// userdata OwnedFd stays valid for any in-flight wake, and the
    /// framework-side registration is gone after the unregister.
    fn drop(&mut self) {
        let _ = self.unregister();
    }
}