use std::sync::{Arc, Mutex, OnceLock, Weak};
use super::heap::MappedHeap;
use super::{IMemory, IMemoryHeap};
use crate::binder::{Interface, Remotable, SIBinder, TransactionCode, FIRST_CALL_TRANSACTION};
use crate::error::{Result, StatusCode};
use crate::native::Binder;
use crate::parcel::Parcel;
pub const IMEMORY_HEAP_DESCRIPTOR: &str = "android.utils.IMemoryHeap";
pub const IMEMORY_DESCRIPTOR: &str = "android.utils.IMemory";
pub const HEAP_ID: TransactionCode = FIRST_CALL_TRANSACTION;
pub const GET_MEMORY: TransactionCode = FIRST_CALL_TRANSACTION;
fn call(binder: &SIBinder, descriptor: &str, code: TransactionCode) -> Result<Parcel> {
if let Some(remote) = binder.as_remote() {
let actual = binder.descriptor();
if !actual.is_empty() && actual != descriptor {
log::error!("shared memory: not an {descriptor}: {actual}");
return Err(StatusCode::BadType);
}
#[cfg(feature = "rpc")]
if let Some(rp) = (**binder).as_any().downcast_ref::<crate::rpc::RpcProxy>() {
let data = rp.build_request(descriptor)?;
return rp
.transact(code, &data, crate::FLAG_CLEAR_BUF)?
.ok_or(StatusCode::UnexpectedNull);
}
let mut data = Parcel::new();
data.write_interface_token(descriptor)?;
return remote
.submit_transact(code, &data, crate::FLAG_CLEAR_BUF)?
.ok_or(StatusCode::UnexpectedNull);
}
let local = binder.as_transactable().ok_or(StatusCode::BadType)?;
if !crate::process_state::ProcessState::is_initialized() {
return Err(StatusCode::InvalidOperation);
}
let mut data = Parcel::new();
data.write_interface_token(descriptor)?;
let mut reply = Parcel::new();
local.transact(code, &mut data, &mut reply)?;
reply.set_data_position(0);
Ok(reply)
}
pub struct BnMemoryHeap<H: IMemoryHeap + 'static>(pub Arc<H>);
impl<H: IMemoryHeap + 'static> std::fmt::Debug for BnMemoryHeap<H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BnMemoryHeap")
.field("heap_id", &self.0.heap_id())
.field("size", &self.0.size())
.finish()
}
}
impl<H: IMemoryHeap + 'static> Remotable for BnMemoryHeap<H> {
fn descriptor() -> &'static str {
IMEMORY_HEAP_DESCRIPTOR
}
fn on_transact(
&self,
code: TransactionCode,
_reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
match code {
HEAP_ID => {
let heap = &*self.0;
let fd = heap.heap_fd().ok_or(StatusCode::BadValue)?;
crate::file_descriptor::write_raw_fd(reply, fd)?;
reply.write_u64(heap.size() as u64)?;
reply.write_i64(heap.offset() as i64)?;
reply.write_u32(heap.flags())
}
_ => Err(StatusCode::UnknownTransaction),
}
}
fn on_dump(&self, _writer: &mut dyn std::io::Write, _args: &[String]) -> Result<()> {
Ok(())
}
}
pub fn export_heap<H: IMemoryHeap + 'static>(heap: Arc<H>) -> SIBinder {
Interface::as_binder(&Binder::new(BnMemoryHeap(heap)))
}
pub struct BpMemoryHeap {
binder: SIBinder,
mapped: OnceLock<Arc<MappedHeap>>,
}
impl std::fmt::Debug for BpMemoryHeap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BpMemoryHeap")
.field("mapped", &self.mapped.get())
.finish()
}
}
impl BpMemoryHeap {
pub fn new(binder: SIBinder) -> Self {
Self {
binder,
mapped: OnceLock::new(),
}
}
pub fn as_binder(&self) -> &SIBinder {
&self.binder
}
pub fn map(&self) -> Result<Arc<MappedHeap>> {
if let Some(m) = self.mapped.get() {
return Ok(m.clone());
}
let mut reply = call(&self.binder, IMEMORY_HEAP_DESCRIPTOR, HEAP_ID)?;
let fd = crate::file_descriptor::read_raw_fd(&mut reply)?;
let size64 = reply.read_u64()?;
let offset64 = reply.read_i64()?;
let flags = reply.read_u32()?;
let size = usize::try_from(size64).map_err(|_| StatusCode::BadValue)?;
let offset = usize::try_from(offset64).map_err(|_| StatusCode::BadValue)?;
let heap = Arc::new(MappedHeap::from_fd(fd, size, offset, flags)?);
Ok(self.mapped.get_or_init(|| heap).clone())
}
pub fn mapped(&self) -> Option<&Arc<MappedHeap>> {
self.mapped.get()
}
}
impl IMemoryHeap for BpMemoryHeap {
fn heap_fd(&self) -> Option<std::os::fd::BorrowedFd<'_>> {
self.mapped.get().and_then(|m| m.heap_fd())
}
fn size(&self) -> usize {
self.mapped.get().map_or(0, |m| m.size())
}
fn flags(&self) -> u32 {
self.mapped.get().map_or(0, |m| m.flags())
}
fn offset(&self) -> usize {
self.mapped.get().map_or(0, |m| m.offset())
}
fn base(&self) -> Option<super::SharedBytes<'_>> {
self.mapped.get().and_then(|m| m.base())
}
}
#[derive(Default)]
pub struct HeapCache {
entries: Mutex<Vec<(SIBinder, Weak<BpMemoryHeap>)>>,
}
impl std::fmt::Debug for HeapCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HeapCache")
.field("live", &self.live().len())
.finish()
}
}
impl HeapCache {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
fn live(&self) -> std::sync::MutexGuard<'_, Vec<(SIBinder, Weak<BpMemoryHeap>)>> {
let mut entries = self.entries.lock().unwrap_or_else(|e| e.into_inner());
entries.retain(|(_, w)| w.strong_count() > 0);
entries
}
pub fn get_or_insert(&self, heap_binder: &SIBinder) -> Arc<BpMemoryHeap> {
let mut entries = self.live();
if let Some(h) = entries
.iter()
.find(|(b, _)| b == heap_binder)
.and_then(|(_, w)| w.upgrade())
{
return h;
}
let h = Arc::new(BpMemoryHeap::new(heap_binder.clone()));
entries.push((heap_binder.clone(), Arc::downgrade(&h)));
h
}
pub fn len(&self) -> usize {
self.live().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct MemoryBase {
heap: Arc<dyn IMemoryHeap>,
heap_binder: SIBinder,
offset: usize,
size: usize,
}
impl std::fmt::Debug for MemoryBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryBase")
.field("heap_id", &self.heap.heap_id())
.field("offset", &self.offset)
.field("size", &self.size)
.finish()
}
}
impl MemoryBase {
pub fn new(
heap: Arc<dyn IMemoryHeap>,
heap_binder: SIBinder,
offset: usize,
size: usize,
) -> Result<Self> {
check_window(heap.size(), offset, size)?;
Ok(Self {
heap,
heap_binder,
offset,
size,
})
}
pub fn export(self: Arc<Self>) -> SIBinder {
Interface::as_binder(&Binder::new(BnMemory(self)))
}
pub fn heap_binder(&self) -> &SIBinder {
&self.heap_binder
}
}
impl IMemory for MemoryBase {
fn memory(&self) -> &dyn IMemoryHeap {
&*self.heap
}
fn offset(&self) -> usize {
self.offset
}
fn size(&self) -> usize {
self.size
}
}
fn check_window(heap_size: usize, offset: usize, size: usize) -> Result<()> {
if size <= heap_size && offset <= heap_size - size {
Ok(())
} else {
Err(StatusCode::BadValue)
}
}
#[derive(Debug)]
pub struct BnMemory(pub Arc<MemoryBase>);
impl Remotable for BnMemory {
fn descriptor() -> &'static str {
IMEMORY_DESCRIPTOR
}
fn on_transact(
&self,
code: TransactionCode,
_reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
match code {
GET_MEMORY => {
reply.write(&self.0.heap_binder)?;
reply.write_i64(self.0.offset as i64)?;
reply.write_u64(self.0.size as u64)
}
_ => Err(StatusCode::UnknownTransaction),
}
}
fn on_dump(&self, _writer: &mut dyn std::io::Write, _args: &[String]) -> Result<()> {
Ok(())
}
}
struct Resolved {
heap: Arc<BpMemoryHeap>,
offset: usize,
size: usize,
}
pub struct BpMemory {
binder: SIBinder,
resolved: OnceLock<Resolved>,
cache: Option<Arc<HeapCache>>,
}
impl BpMemory {
pub fn new(binder: SIBinder) -> Self {
Self {
binder,
resolved: OnceLock::new(),
cache: None,
}
}
pub fn new_with_cache(binder: SIBinder, cache: Arc<HeapCache>) -> Self {
Self {
binder,
resolved: OnceLock::new(),
cache: Some(cache),
}
}
pub fn as_binder(&self) -> &SIBinder {
&self.binder
}
pub fn resolve(&self) -> Result<Arc<BpMemoryHeap>> {
if let Some(r) = self.resolved.get() {
return Ok(r.heap.clone());
}
let mut reply = call(&self.binder, IMEMORY_DESCRIPTOR, GET_MEMORY)?;
let heap_binder: SIBinder = reply.read()?;
let offset64 = reply.read_i64()?;
let size64 = reply.read_u64()?;
let heap = match &self.cache {
Some(c) => c.get_or_insert(&heap_binder),
None => Arc::new(BpMemoryHeap::new(heap_binder)),
};
let mapped = heap.map()?;
let (offset, size) = clamp_window(mapped.size(), offset64, size64);
let r = self
.resolved
.get_or_init(|| Resolved { heap, offset, size });
Ok(r.heap.clone())
}
pub fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<()> {
let (heap, base) = self.window(off, dst.len())?;
heap.read_at(base, dst)
}
pub fn write_at(&self, off: usize, src: &[u8]) -> Result<()> {
let (heap, base) = self.window(off, src.len())?;
heap.write_at(base, src)
}
fn window(&self, off: usize, len: usize) -> Result<(Arc<MappedHeap>, usize)> {
let heap = self.resolve()?;
let r = self.resolved.get().ok_or(StatusCode::Unknown)?;
let end = off.checked_add(len).ok_or(StatusCode::BadValue)?;
if end > r.size {
return Err(StatusCode::BadValue);
}
let mapped = heap.map()?;
Ok((mapped, r.offset + off))
}
}
fn clamp_window(heap_size: usize, offset64: i64, size64: u64) -> (usize, usize) {
let ok = (|| {
let size = usize::try_from(size64).ok()?;
let offset = usize::try_from(offset64).ok()?;
check_window(heap_size, offset, size).ok()?;
Some((offset, size))
})();
ok.unwrap_or((0, 0))
}
impl IMemory for BpMemory {
fn memory(&self) -> &dyn IMemoryHeap {
match self.resolved.get() {
Some(r) => &*r.heap,
None => &UNRESOLVED,
}
}
fn offset(&self) -> usize {
self.resolved.get().map_or(0, |r| r.offset)
}
fn size(&self) -> usize {
self.resolved.get().map_or(0, |r| r.size)
}
}
struct UnresolvedHeap;
static UNRESOLVED: UnresolvedHeap = UnresolvedHeap;
impl IMemoryHeap for UnresolvedHeap {
fn heap_fd(&self) -> Option<std::os::fd::BorrowedFd<'_>> {
None
}
fn size(&self) -> usize {
0
}
fn flags(&self) -> u32 {
0
}
fn offset(&self) -> usize {
0
}
fn base(&self) -> Option<super::SharedBytes<'_>> {
None
}
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "android", target_os = "macos")
))]
mod tests {
use super::*;
use crate::shared_memory::{MemoryHeapBase, FLAG_READ_ONLY};
fn page() -> usize {
rustix::param::page_size()
}
#[test]
fn heap_id_reply_layout_matches_aosp() {
let heap = Arc::new(MemoryHeapBase::new(page() * 3, FLAG_READ_ONLY).unwrap());
let bn = BnMemoryHeap(heap.clone());
let mut reply = Parcel::new();
bn.on_transact(HEAP_ID, &mut Parcel::new(), &mut reply)
.unwrap();
reply.set_data_position(0);
let fd = crate::file_descriptor::read_raw_fd(&mut reply).unwrap();
assert_eq!(reply.read_u64().unwrap(), (page() * 3) as u64);
assert_eq!(reply.read_i64().unwrap(), 0);
assert_eq!(reply.read_u32().unwrap(), FLAG_READ_ONLY);
assert!(reply.read_u32().is_err(), "no trailing data");
use std::os::fd::AsRawFd;
assert_ne!(fd.as_raw_fd(), heap.heap_id());
let rx = MappedHeap::from_fd(fd, page() * 3, 0, FLAG_READ_ONLY).unwrap();
heap.write_at(page(), b"layout").unwrap();
let mut b = [0u8; 6];
rx.read_at(page(), &mut b).unwrap();
assert_eq!(&b, b"layout");
}
#[test]
fn heap_id_reply_is_built_from_heap_fd_only() {
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
struct Lying {
heap: MemoryHeapBase,
other: std::fs::File,
}
impl IMemoryHeap for Lying {
fn heap_fd(&self) -> Option<BorrowedFd<'_>> {
self.heap.heap_fd()
}
fn heap_id(&self) -> i32 {
self.other.as_raw_fd()
}
fn size(&self) -> usize {
self.heap.size()
}
fn flags(&self) -> u32 {
0
}
fn offset(&self) -> usize {
0
}
fn base(&self) -> Option<crate::shared_memory::SharedBytes<'_>> {
None
}
}
let ino = |fd: BorrowedFd<'_>| rustix::fs::fstat(fd).unwrap().st_ino;
let lying = Arc::new(Lying {
heap: MemoryHeapBase::new(page(), 0).unwrap(),
other: std::fs::File::open("/dev/null").unwrap(),
});
let mut reply = Parcel::new();
BnMemoryHeap(lying.clone())
.on_transact(HEAP_ID, &mut Parcel::new(), &mut reply)
.unwrap();
reply.set_data_position(0);
let fd = crate::file_descriptor::read_raw_fd(&mut reply).unwrap();
assert_eq!(
ino(fd.as_fd()),
ino(lying.heap.as_fd()),
"not the heap's fd"
);
let unmapped = BnMemoryHeap(Arc::new(BpMemoryHeap::new(export_heap(lying))));
assert_eq!(
unmapped
.on_transact(HEAP_ID, &mut Parcel::new(), &mut Parcel::new())
.unwrap_err(),
StatusCode::BadValue
);
}
#[test]
fn unknown_code_is_rejected() {
let bn = BnMemoryHeap(Arc::new(MemoryHeapBase::new(1, 0).unwrap()));
assert_eq!(
bn.on_transact(HEAP_ID + 1, &mut Parcel::new(), &mut Parcel::new())
.unwrap_err(),
StatusCode::UnknownTransaction
);
assert_eq!(
<BnMemoryHeap<MemoryHeapBase> as Remotable>::descriptor(),
IMEMORY_HEAP_DESCRIPTOR
);
assert_eq!(<BnMemory as Remotable>::descriptor(), IMEMORY_DESCRIPTOR);
assert_eq!(
export_heap(Arc::new(MemoryHeapBase::new(1, 0).unwrap())).descriptor(),
IMEMORY_HEAP_DESCRIPTOR
);
}
#[test]
fn memory_base_window_is_bounds_checked() {
let owner: Arc<dyn IMemoryHeap> = Arc::new(MemoryHeapBase::new(page(), 0).unwrap());
let hb = export_heap(Arc::new(MemoryHeapBase::new(page(), 0).unwrap()));
assert!(MemoryBase::new(owner.clone(), hb.clone(), 0, page()).is_ok());
assert!(MemoryBase::new(owner.clone(), hb.clone(), page() - 8, 8).is_ok());
assert_eq!(
MemoryBase::new(owner.clone(), hb.clone(), page() - 7, 8).unwrap_err(),
StatusCode::BadValue
);
assert_eq!(
MemoryBase::new(owner, hb, 0, page() + 1).unwrap_err(),
StatusCode::BadValue
);
}
#[test]
fn clamp_window_matches_aosp_rules() {
assert_eq!(clamp_window(4096, 0, 4096), (0, 4096));
assert_eq!(clamp_window(4096, 4000, 96), (4000, 96));
assert_eq!(clamp_window(4096, 0, 4097), (0, 0)); assert_eq!(clamp_window(4096, -1, 16), (0, 0)); assert_eq!(clamp_window(4096, 4000, 97), (0, 0)); assert_eq!(clamp_window(4096, 0, u64::MAX), (0, 0)); }
#[cfg(feature = "rpc")]
#[test]
fn call_refuses_a_binder_of_another_interface() {
use crate::rpc::transport::MemTransport;
use crate::rpc::{AddressSpace, RpcSession};
use std::sync::atomic::{AtomicBool, Ordering};
struct Foo(Arc<AtomicBool>);
impl Remotable for Foo {
fn descriptor() -> &'static str {
"x.y.IFoo"
}
fn on_transact(
&self,
code: TransactionCode,
_: &mut Parcel,
_: &mut Parcel,
) -> Result<()> {
if code == FIRST_CALL_TRANSACTION {
self.0.store(true, Ordering::SeqCst);
}
Ok(())
}
fn on_dump(&self, _: &mut dyn std::io::Write, _: &[String]) -> Result<()> {
Ok(())
}
}
let with_root = |case: &dyn Fn(SIBinder)| {
let hit = Arc::new(AtomicBool::new(false));
let (a, b) = MemTransport::pair();
let server = RpcSession::new(Box::new(a), AddressSpace::Acceptor).unwrap();
server
.set_root(Interface::as_binder(&Binder::new(Foo(hit.clone()))))
.unwrap();
let handle = std::thread::spawn(move || {
let _ = server.serve_blocking();
});
{
let client = RpcSession::new(Box::new(b), AddressSpace::Initiator).unwrap();
case(client.get_root().unwrap());
}
handle.join().unwrap();
assert!(!hit.load(Ordering::SeqCst), "x.y.IFoo code 1 ran");
};
with_root(&|root| {
crate::binder::__rpc_stamp_descriptor(&root, "x.y.IFoo");
assert_eq!(
BpMemoryHeap::new(root.clone()).map().unwrap_err(),
StatusCode::BadType
);
assert_eq!(
BpMemory::new(root).resolve().unwrap_err(),
StatusCode::BadType
);
});
with_root(&|root| {
assert!(BpMemoryHeap::new(root.clone()).map().is_err());
assert_eq!(root.descriptor(), "");
});
let (a, b) = MemTransport::pair();
let server = RpcSession::new(Box::new(a), AddressSpace::Acceptor).unwrap();
let heap = export_heap(Arc::new(MemoryHeapBase::new(page(), 0).unwrap()));
server.set_root(heap).unwrap();
let handle = std::thread::spawn(move || {
let _ = server.serve_blocking();
});
{
let client = RpcSession::new(Box::new(b), AddressSpace::Initiator).unwrap();
let root = client.get_root().unwrap();
crate::binder::__rpc_stamp_descriptor(&root, "x.y.IFoo");
assert_eq!(
BpMemoryHeap::new(root).map().unwrap_err(),
StatusCode::BadType
);
}
handle.join().unwrap();
}
#[test]
fn unresolved_bp_memory_reports_empty_window() {
let bp = BpMemory::new(export_heap(Arc::new(MemoryHeapBase::new(1, 0).unwrap())));
assert_eq!(bp.size(), 0);
assert_eq!(bp.offset(), 0);
assert_eq!(bp.memory().heap_id(), -1);
assert!(bp.memory().base().is_none());
let err = bp.read_at(0, &mut [0u8; 1]).unwrap_err();
assert!(
matches!(err, StatusCode::InvalidOperation | StatusCode::BadType),
"{err:?}"
);
}
}