cloudfox-coreshift-core 2.9.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/

//! 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};
use std::sync::{Arc, Mutex, OnceLock};

// ── 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 (several of which may dispatch concurrently). 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.
///
/// The handler lives behind an `Arc<Mutex<..>>` (the same discipline the
/// death-recipient slab uses): the binder pool spawns several dispatch
/// threads, so two inbound transactions can run concurrently and each needs
/// exclusive `&mut` access to the handler box.
struct ServeCtx {
    vt: Vtable,
    handler: Arc<Mutex<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 { &*(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,
            };
            // Serialize handler invocation: a transaction borrows the served
            // parcel cursors for its duration, and several pool threads can
            // dispatch concurrently. Holding the guard for the call gives the
            // `FnMut` its exclusive borrow; a poisoned lock is tolerated.
            let mut handler = ctx.handler.lock().unwrap_or_else(|p| p.into_inner());
            return match (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: Arc::new(Mutex::new(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 ───────────────────────────────────────────────────────

/// Process-wide callback slab. The framework cookie handed to
/// `AIBinder_DeathRecipient_new` is a *stable slot index* (the `Vec` never
/// shrinks and an index is never reused), so the trampoline's lookup is never a
/// use-after-free even when the recipient is dropped concurrently with a
/// delivery — the NDK `unlink` is asynchronous and does not wait for a delivery
/// already in flight. Each slot's `Arc` keeps the callback box alive while a
/// delivery holds a clone; the box is freed only when the last `Arc` is
/// released. A stale delivery (one whose recipient was dropped before the pool
/// thread ran its trampoline) finds an empty slot and no-ops instead of
/// invoking a newer recipient's callback.
struct CallbackSlab {
    slots: Vec<Option<Arc<Mutex<Box<dyn FnMut() + Send>>>>>,
}

fn callback_slab() -> &'static Mutex<CallbackSlab> {
    static SLAB: OnceLock<Mutex<CallbackSlab>> = OnceLock::new();
    SLAB.get_or_init(|| Mutex::new(CallbackSlab { slots: Vec::new() }))
}

unsafe extern "C" fn death_on_died(cookie: *mut c_void) {
    // Cookies are 1-based slab indices: slot 0 encodes as cookie 1, so the
    // very first recipient is never handed a null cookie (the framework treats
    // a null cookie as "no recipient" and would silently swallow its delivery).
    let index = cookie as usize;
    if index == 0 {
        return;
    }
    // The cookie is a stable slab index. Clone the callback's `Arc` under
    // the slab lock so the box outlives this delivery even if the
    // recipient is dropped concurrently; a slot released by that drop
    // (stale delivery) yields `None` and the delivery is a no-op.
    let cb = {
        let slab = callback_slab();
        let slab = slab.lock().unwrap_or_else(|e| e.into_inner());
        slab.slots.get(index - 1).and_then(|s| s.as_ref()).cloned()
    };
    if let Some(cb) = cb {
        let mut cb = cb.lock().unwrap_or_else(|e| e.into_inner());
        cb();
    }
}

/// Observe a binder peer's death. `on_died` runs on an arbitrary binder
/// thread when the linked binder dies.
///
/// The callback box lives in a process-wide slab behind an `Arc`, so dropping
/// the recipient is memory-safe even while a delivery is in flight: the
/// trampoline clones the `Arc` under the slab lock before calling, and the box
/// is freed only when the last `Arc` is released. `unlink` before drop remains
/// good hygiene — it deregisters the recipient so no future `on_died` fires —
/// but it is no longer required for soundness.
pub struct DeathRecipient {
    recipient: *mut AIBinder_DeathRecipient,
    /// Stable slab index of this recipient's callback slot; never reused.
    slot: usize,
    delete: unsafe extern "C" fn(*mut AIBinder_DeathRecipient),
    link_to_death:
        unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_DeathRecipient, *mut c_void) -> BinderStatus,
    unlink_to_death:
        unsafe extern "C" fn(*mut AIBinder, *mut AIBinder_DeathRecipient, *mut c_void) -> BinderStatus,
}
unsafe impl Send for DeathRecipient {}

impl DeathRecipient {
    fn new(vt: Vtable, on_died: Box<dyn FnMut() + Send>) -> Self {
        // Allocate the callback slot before creating the framework recipient so
        // the cookie (a stable slab index) is valid from the moment the
        // framework could schedule a delivery.
        let slot = {
            let mut slab = callback_slab().lock().unwrap_or_else(|e| e.into_inner());
            slab.slots.push(Some(Arc::new(Mutex::new(on_died))));
            slab.slots.len() - 1
        };
        let cookie = (slot + 1) as *mut c_void;
        let recipient = unsafe { (vt.death_recipient_new)(death_on_died, cookie) };
        Self {
            recipient,
            slot,
            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 status = unsafe { (self.link_to_death)(target.ptr, self.recipient, std::ptr::null_mut()) };
        if status == super::sys::STATUS_OK {
            Ok(())
        } else {
            Err(CoreError::binder(status, "AIBinder_linkToDeath"))
        }
    }

    /// Deregister this recipient from `target`. Call before dropping so no
    /// future `on_died` fires for this recipient.
    pub fn unlink(&self, target: &OwnedBinder) -> Result<(), CoreError> {
        let status =
            unsafe { (self.unlink_to_death)(target.ptr, self.recipient, std::ptr::null_mut()) };
        if status == super::sys::STATUS_OK {
            Ok(())
        } else {
            Err(CoreError::binder(status, "AIBinder_unlinkToDeath"))
        }
    }
}

impl Drop for DeathRecipient {
    fn drop(&mut self) {
        if !self.recipient.is_null() {
            unsafe { (self.delete)(self.recipient) };
        }
        // Release the callback slot. A delivery already in flight holds its own
        // `Arc` clone, so the callback box outlives this drop until that
        // delivery completes — the box is freed only when both the slot's `Arc`
        // and every in-flight clone are released. The slot index itself is
        // never reused, so a stale delivery that races this drop finds an empty
        // slot and no-ops instead of invoking a newer recipient's callback.
        let mut slab = callback_slab().lock().unwrap_or_else(|e| e.into_inner());
        if let Some(slot) = slab.slots.get_mut(self.slot) {
            slot.take();
        }
    }
}