cloudfox-coreshift-core 2.8.1

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/

//! Server-side binder primitives: serve transactions on a binder you own,
//! capture the caller's identity, push one-way updates to client observers,
//! and observe peer death.
//!
//! This is the transport primitives for the CoreShift direct-bind protocol
//! (the other side of the client `transact_write` helpers in `sys`). A
//! [`ServingBinder`] owns a class + binder whose `on_transact` dispatches to
//! a [`ServeHandler`] closure with a [`ServeCall`]: the transaction code, the
//! caller's uid/pid (captured synchronously inside `on_transact`, where the
//! thread-local binder context is still valid), and borrowed read/write
//! cursors over the framework-owned request and reply parcels.
//!
//! Caller identity is deliberately a *capture-at-entry* primitive: the uid
//! and pid are read once, at the top of `on_transact`, before any handler
//! work, and passed in the call. They are NOT valid after the transaction
//! returns, so a handler that needs them later (e.g. to gate a watcher)
//! must carry them onward as data.

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

// ── Serving binder ────────────────────────────────────────────────────────

/// One inbound transaction on a served binder.
pub struct ServeCall<'a> {
    /// The transaction code.
    pub code: u32,
    /// Caller UID, captured at `on_transact` entry.
    pub calling_uid: u32,
    /// Caller PID, captured at `on_transact` entry.
    pub calling_pid: i32,
    /// Read cursor over the framework-owned request parcel.
    pub request: ParcelReader<'a>,
    /// Write cursor over the framework-owned reply parcel. `Some` for a
    /// two-way transaction, `None` for a one-way (fire-and-forget) call.
    pub reply: Option<ParcelWriter<'a>>,
}

/// Handler for inbound transactions on a [`ServingBinder`]. Runs on a binder
/// thread-pool thread. Returning `Ok` replies `STATUS_OK`; returning `Err`
/// fails the transaction with `STATUS_UNKNOWN_TRANSACTION` (the caller's
/// `AIBinder_transact` sees a non-OK status).
pub type ServeHandler = Box<dyn for<'a> FnMut(ServeCall<'a>) -> Result<(), CoreError> + Send>;

/// The context boxed as the binder's userdata. `on_transact` resolves it via
/// `AIBinder_getUserData`, so the serving path has the same vtable snapshot
/// as the client side without any process-global state.
struct ServeCtx {
    vt: Vtable,
    handler: ServeHandler,
}

unsafe extern "C" fn serve_on_create(args: *mut c_void) -> *mut c_void {
    // onCreate must return the args passed to AIBinder_new so
    // AIBinder_getUserData returns the ServeCtx box.
    args
}
unsafe extern "C" fn serve_on_destroy(userdata: *mut c_void) {
    if !userdata.is_null() {
        // Reclaim the ServeCtx box handed to AIBinder_new. Only reached if
        // the framework destroys the binder; the ServingBinder never
        // releases its local strong ref, so in practice this fires at
        // process teardown, mirroring the other service modules.
        unsafe { drop(Box::from_raw(userdata as *mut ServeCtx)) };
    }
}
unsafe extern "C" fn serve_on_transact(
    binder: *mut AIBinder,
    code: u32,
    in_parcel: *const AParcel,
    out_parcel: *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 ctx = unsafe { &mut *(userdata as *mut ServeCtx) };
            // Capture identity at entry, while the thread-local binder
            // context is valid; carried onward as data by the handler.
            let calling_uid = unsafe { (ctx.vt.get_calling_uid)() };
            let calling_pid = unsafe { (ctx.vt.get_calling_pid)() };
            let request = ParcelReader::borrowed(&ctx.vt, in_parcel);
            let reply = if out_parcel.is_null() {
                None
            } else {
                Some(ParcelWriter::borrowed(&ctx.vt, out_parcel))
            };
            let call = ServeCall {
                code,
                calling_uid,
                calling_pid,
                request,
                reply,
            };
            return match (ctx.handler)(call) {
                Ok(()) => STATUS_OK,
                Err(_) => STATUS_UNKNOWN_TRANSACTION,
            };
        }
    }
    STATUS_UNKNOWN_TRANSACTION
}

/// A binder this process serves. Transactions addressed to it are
/// dispatched to the [`ServeHandler`] supplied at open.
///
/// The binder is deliberately NOT exported by name: it is handed to a client
/// through the calling process's direct-bind transport (e.g. a
/// `ContentProvider.call("sendBinder")` handoff), never via
/// `AServiceManager`. Exposing it to the service manager would let any
/// holder transact against it without the daemon's caller gate.
pub struct ServingBinder {
    _lib: DlHandle,
    vt: Vtable,
    binder: *mut AIBinder,
    _class: *mut AIBinder_Class,
}
unsafe impl Send for ServingBinder {}

impl ServingBinder {
    /// Define a class for `descriptor`, create the local binder, and start
    /// the binder thread pool so `on_transact` can fire. The binder's strong
    /// ref is held for the process lifetime; see the module docs.
    pub fn open(descriptor: &[u8], handler: ServeHandler) -> Result<Self, 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 class = unsafe {
            (vt.class_define)(
                descriptor.as_ptr() as *const c_char,
                serve_on_create,
                serve_on_destroy,
                serve_on_transact,
            )
        };
        if class.is_null() {
            return Err(CoreError::binder(-1, "AIBinder_Class_define:serve"));
        }

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

        *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,
            binder,
            _class: class,
        })
    }

    /// The served `AIBinder*`, for a direct-bind handoff to the calling
    /// process (e.g. as the argument of a `ContentProvider.call`).
    pub fn as_raw(&self) -> *mut c_void {
        self.binder as *mut c_void
    }

    /// Fire a one-way (fire-and-forget) transaction at a client observer
    /// binder, typically one previously read from a request parcel via
    /// [`ParcelReader::read_strong_binder`]. Runs on the caller's thread;
    /// failures (including a dead peer) are returned, never panicked.
    pub fn push_oneway(
        &self,
        target: &OwnedBinder,
        code: u32,
        writes: impl FnOnce(&ParcelWriter<'_>) -> Result<(), CoreError>,
    ) -> Result<(), CoreError> {
        transact_oneway(&self.vt, target.ptr, code, writes)
    }

    /// Create a death recipient bound to this binder's vtable. Link it to a
    /// client observer binder to be notified when the peer dies; the daemon
    /// routes that through the same ingestion queue as `WatcherDied`.
    pub fn death_recipient(&self, on_died: Box<dyn FnMut() + Send>) -> DeathRecipient {
        DeathRecipient::new(self.vt, on_died)
    }

    /// A copyable, borrow-free handle that can fire one-way transactions with
    /// this binder's vtable. `push_oneway` needs only the vtable snapshot, so
    /// a `'static` watcher sink can capture it instead of the (non-`Sync`)
    /// serving object.
    pub fn oneway_sender(&self) -> OnewaySender {
        OnewaySender { vt: self.vt }
    }
}

// ── One-way sender ─────────────────────────────────────────────────────────

/// A self-contained one-way transaction sender. Snapshots the serving binder's
/// vtable; it owns no binder and borrows nothing, so a `'static` watcher sink
/// (the daemon's Binder backend) can hold a copy and push updates to a client
/// observer without the serving object's lifetime.
#[derive(Clone, Copy)]
pub struct OnewaySender {
    vt: Vtable,
}

impl OnewaySender {
    /// Fire a one-way transaction at `target`. See
    /// [`ServingBinder::push_oneway`].
    pub fn push_oneway(
        &self,
        target: &OwnedBinder,
        code: u32,
        writes: impl FnOnce(&ParcelWriter<'_>) -> Result<(), CoreError>,
    ) -> Result<(), CoreError> {
        transact_oneway(&self.vt, target.ptr, code, writes)
    }
}

// ── Death recipient ───────────────────────────────────────────────────────

unsafe extern "C" fn death_on_died(cookie: *mut c_void) {
    if !cookie.is_null() {
        let cb = unsafe { &mut *(cookie as *mut Box<dyn FnMut() + Send>) };
        cb();
    }
}

/// Observe a binder peer's death. `on_died` runs on an arbitrary binder
/// thread when the linked binder dies.
///
/// The callback box is owned by the recipient, so the daemon can drop it
/// after unlinking; link/unlink order mirrors the `TaskStackListener` /
/// `FpsListener` deregistration discipline.
pub struct DeathRecipient {
    recipient: *mut AIBinder_DeathRecipient,
    // Outer box owns the inner box; the inner box's heap address is the
    // stable cookie handed to the framework's onDied callback.
    _cb: Box<Box<dyn FnMut() + Send>>,
    delete: unsafe extern "C" fn(*mut AIBinder_DeathRecipient),
    link_to_death:
        unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_DeathRecipient, *mut c_void) -> bool,
    unlink_to_death:
        unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_DeathRecipient, *mut c_void) -> bool,
}
unsafe impl Send for DeathRecipient {}

impl DeathRecipient {
    fn new(vt: Vtable, on_died: Box<dyn FnMut() + Send>) -> Self {
        let outer: Box<Box<dyn FnMut() + Send>> = Box::new(on_died);
        let cookie = &*outer as *const Box<dyn FnMut() + Send> as *mut c_void;
        let recipient = unsafe { (vt.death_recipient_new)(death_on_died, cookie) };
        Self {
            recipient,
            _cb: outer,
            delete: vt.death_recipient_delete,
            link_to_death: vt.link_to_death,
            unlink_to_death: vt.unlink_to_death,
        }
    }

    /// Register this recipient on `target`; `on_died` fires when the peer
    /// dies. Returns an error if the framework refused the link.
    pub fn link(&self, target: &OwnedBinder) -> Result<(), CoreError> {
        let ok = unsafe { (self.link_to_death)(target.ptr, self.recipient, std::ptr::null_mut()) };
        if ok {
            Ok(())
        } else {
            Err(CoreError::binder(-1, "AIBinder_linkToDeath"))
        }
    }

    /// Deregister this recipient from `target`. Call before dropping so no
    /// `on_died` fires after the callback box is reclaimed.
    pub fn unlink(&self, target: &OwnedBinder) -> Result<(), CoreError> {
        let ok =
            unsafe { (self.unlink_to_death)(target.ptr, self.recipient, std::ptr::null_mut()) };
        if ok {
            Ok(())
        } else {
            Err(CoreError::binder(-1, "AIBinder_unlinkToDeath"))
        }
    }
}

impl Drop for DeathRecipient {
    fn drop(&mut self) {
        if !self.recipient.is_null() {
            unsafe { (self.delete)(self.recipient) };
        }
    }
}