cloudfox-coreshift-core 2.13.0

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
// 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::atomic::{AtomicUsize, Ordering};
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>;

/// Default admission cap for [`ServingBinder::open`]. The process-wide binder
/// pool is 15 threads (the NDK default); the fps/observer/task-stack callbacks
/// share that pool, so served two-way transactions get a small share and any
/// over-capacity burst is rejected up front instead of queuing the whole pool.
const DEFAULT_MAX_INFLIGHT: usize = 4;

/// 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.
///
/// `admission` bounds how many transactions may be *in the handler at once*
/// (i.e. how many pool threads can be blocked waiting on `handler` or running
/// it). Without it a client sending a burst of slow two-way transactions
/// queues every pool thread on the mutex and starves the process-wide binder
/// pool (fps/observer/task-stack callbacks all share it) — see finding 7.
///
/// A counting semaphore (incremented on entry, decremented when the handler
/// returns) rather than `std::sync::Semaphore`, which is not available in the
/// Android target's std.
struct ServeCtx {
    vt: Vtable,
    handler: Arc<Mutex<ServeHandler>>,
    /// Current in-handler transaction count. `max_inflight` is stored beside
    /// it; `try_acquire` returns true when `count < max`.
    in_flight: AtomicUsize,
    max_inflight: usize,
}

impl ServeCtx {
    /// Attempt to admit one transaction; returns a guard that must be held for
    /// the transaction's duration and released (dropped) on completion.
    fn try_acquire(&self) -> Option<InFlightGuard<'_>> {
        loop {
            let cur = self.in_flight.load(Ordering::Acquire);
            if cur >= self.max_inflight {
                return None;
            }
            if self
                .in_flight
                .compare_exchange_weak(cur, cur + 1, Ordering::AcqRel, Ordering::Acquire)
                .is_ok()
            {
                return Some(InFlightGuard {
                    in_flight: &self.in_flight,
                });
            }
        }
    }
}

/// Releasing a transaction's admission slot. Drops atomically.
struct InFlightGuard<'a> {
    in_flight: &'a AtomicUsize,
}

impl Drop for InFlightGuard<'_> {
    fn drop(&mut self) {
        self.in_flight.fetch_sub(1, Ordering::Release);
    }
}

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,
            };
            // Admission gate BEFORE the handler mutex: cap how many pool
            // threads may be inside the handler (or queued on its lock) at
            // once. The rendezvous inside a handler can block for seconds; a
            // burst of concurrent two-way transactions would otherwise queue
            // every thread of the process-wide 15-thread pool on the lock and
            // starve the fps/observer/task-stack callbacks that share it
            // (finding 7). Rejecting over-capacity transactions up front frees
            // the pool thread immediately with a distinguishable status. The
            // guard is held until the handler returns (the drop decrements).
            let Some(_permit) = ctx.try_acquire() else {
                return STATUS_OUT_OF_RESOURCES;
            };
            // 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());
            // A panic inside the handler must never unwind across the
            // `extern "C"` frame — that is UB with `panic=unwind` (in practice
            // an abort) in the root daemon. Catch it and fail the transaction
            // instead.
            return match std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
                (handler)(call)
            })) {
                Ok(Ok(())) => STATUS_OK,
                Ok(Err(_)) => STATUS_UNKNOWN_TRANSACTION,
                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.
    ///
    /// `max_inflight` bounds how many transactions may be running (or queued)
    /// inside the handler at once — see [`ServeCtx::admission`]. Use
    /// [`Self::open`] for the default limit.
    pub fn open_bounded(
        descriptor: &[u8],
        handler: ServeHandler,
        max_inflight: usize,
    ) -> 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)),
            in_flight: AtomicUsize::new(0),
            max_inflight,
        })) 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,
        })
    }

    /// Define a class for `descriptor` with the default in-flight admission
    /// limit. Equivalent to [`Self::open_bounded`] with a conservative cap.
    pub fn open(descriptor: &[u8], handler: ServeHandler) -> Result<Self, CoreError> {
        Self::open_bounded(descriptor, handler, DEFAULT_MAX_INFLIGHT)
    }

    /// 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)
    }

    /// Associate this service's class with a client observer binder read from
    /// a request parcel. `AIBinder_prepareTransaction` requires every target
    /// of a transaction to have a class (it writes the interface header), and
    /// a proxy read via `read_strong_binder` has none yet. Both sides of the
    /// watch channel share the same descriptor, so the association always
    /// matches.
    pub fn associate(&self, target: &OwnedBinder) -> Result<(), CoreError> {
        if target.ptr.is_null() {
            return Err(CoreError::binder(-1, "AIBinder_associateClass:null"));
        }
        let ok = unsafe { (self.vt.associate_class)(target.ptr, self._class) };
        if !ok {
            return Err(CoreError::binder(-1, "AIBinder_associateClass"));
        }
        Ok(())
    }

    /// 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,
    /// The framework cookie delivered to `death_on_died`. The NDK's
    /// `AIBinder_DeathRecipient_new` takes only the callback; the cookie is
    /// instead bound at `link_to_death` time (its 3rd argument) and handed
    /// back to the callback when the peer dies.
    cookie: *mut c_void,
    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) };
        Self {
            recipient,
            slot,
            cookie,
            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, self.cookie) };
        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, self.cookie) };
        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();
        }
    }
}