From 42230f159d072b3b39325d0fa8d33ef1f7624b6f Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Mon, 19 Apr 2021 15:53:02 +0100 Subject: [PATCH] binder: Add support for transferring file descriptors. Signed-off-by: Wedson Almeida Filho --- drivers/android/allocation.rs | 19 +++- drivers/android/thread.rs | 30 ++++--- drivers/android/transaction.rs | 155 +++++++++++++++++++++++++++++---- 3 files changed, 173 insertions(+), 31 deletions(-) diff --git a/drivers/android/allocation.rs b/drivers/android/allocation.rs index 3e10ae4be6687e..3239b96ce01bcb 100644 --- a/drivers/android/allocation.rs +++ b/drivers/android/allocation.rs @@ -1,14 +1,17 @@ // SPDX-License-Identifier: GPL-2.0 -use alloc::sync::Arc; -use core::mem::{size_of, MaybeUninit}; -use kernel::{bindings, pages::Pages, prelude::*, user_ptr::UserSlicePtrReader, Error}; +use alloc::{boxed::Box, sync::Arc}; +use core::mem::{replace, size_of, MaybeUninit}; +use kernel::{ + bindings, linked_list::List, pages::Pages, prelude::*, user_ptr::UserSlicePtrReader, Error, +}; use crate::{ defs::*, node::NodeRef, process::{AllocationInfo, Process}, thread::{BinderError, BinderResult}, + transaction::FileInfo, }; pub(crate) struct Allocation<'a> { @@ -19,6 +22,7 @@ pub(crate) struct Allocation<'a> { pub(crate) process: &'a Process, allocation_info: Option, free_on_drop: bool, + file_list: List>, } impl<'a> Allocation<'a> { @@ -37,9 +41,18 @@ impl<'a> Allocation<'a> { pages, allocation_info: None, free_on_drop: true, + file_list: List::new(), } } + pub(crate) fn take_file_list(&mut self) -> List> { + replace(&mut self.file_list, List::new()) + } + + pub(crate) fn add_file_info(&mut self, file: Box) { + self.file_list.push_back(file); + } + fn iterate(&self, mut offset: usize, mut size: usize, mut cb: T) -> Result where T: FnMut(&Pages<0>, usize, usize) -> Result, diff --git a/drivers/android/thread.rs b/drivers/android/thread.rs index 94c2d70fa33ba4..896ec86106ea84 100644 --- a/drivers/android/thread.rs +++ b/drivers/android/thread.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 -use alloc::sync::Arc; +use alloc::{boxed::Box, sync::Arc}; use core::{alloc::AllocError, mem::size_of, pin::Pin}; use kernel::{ bindings, @@ -19,7 +19,7 @@ use crate::{ defs::*, process::{AllocationInfo, Process}, ptr_align, - transaction::Transaction, + transaction::{FileInfo, Transaction}, DeliverCode, DeliverToRead, DeliverToReadListAdapter, Either, }; @@ -376,7 +376,7 @@ impl Thread { fn translate_object( &self, index_offset: usize, - view: &AllocationView, + view: &mut AllocationView, allow_fds: bool, ) -> BinderResult { let offset = view.alloc.read(index_offset)?; @@ -386,7 +386,8 @@ impl Thread { BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => { let strong = header.type_ == BINDER_TYPE_BINDER; view.transfer_binder_object(offset, strong, |obj| { - // SAFETY: The type is `BINDER_TYPE_{WEAK_}BINDER`, so `binder` is populated. + // SAFETY: `binder` is a `binder_uintptr_t`; any bit pattern is a valid + // representation. let ptr = unsafe { obj.__bindgen_anon_1.binder } as _; let cookie = obj.cookie as _; let flags = obj.flags as _; @@ -398,7 +399,7 @@ impl Thread { BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => { let strong = header.type_ == BINDER_TYPE_HANDLE; view.transfer_binder_object(offset, strong, |obj| { - // SAFETY: The type is `BINDER_TYPE_{WEAK_}HANDLE`, so `handle` is populated. + // SAFETY: `handle` is a `u32`; any bit pattern is a valid representation. let handle = unsafe { obj.__bindgen_anon_1.handle } as _; self.process.get_node_from_handle(handle, strong) })?; @@ -407,6 +408,15 @@ impl Thread { if !allow_fds { return Err(BinderError::new_failed()); } + + let obj = view.read::(offset)?; + // SAFETY: `fd` is a `u32`; any bit pattern is a valid representation. + let fd = unsafe { obj.__bindgen_anon_1.fd }; + let file = File::from_fd(fd)?; + let field_offset = + kernel::offset_of!(bindings::binder_fd_object, __bindgen_anon_1.fd) as usize; + let file_info = Box::try_new(FileInfo::new(file, offset + field_offset))?; + view.alloc.add_file_info(file_info); } _ => pr_warn!("Unsupported binder object type: {:x}\n", header.type_), } @@ -420,9 +430,9 @@ impl Thread { end: usize, allow_fds: bool, ) -> BinderResult { - let view = AllocationView::new(alloc, start); + let mut view = AllocationView::new(alloc, start); for i in (start..end).step_by(size_of::()) { - if let Err(err) = self.translate_object(i, &view, allow_fds) { + if let Err(err) = self.translate_object(i, &mut view, allow_fds) { alloc.set_info(AllocationInfo { offsets: start..i }); return Err(err); } @@ -558,7 +568,7 @@ impl Thread { let completion = Arc::try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; let process = orig.from.process.clone(); let allow_fds = orig.flags & TF_ACCEPT_FDS != 0; - let reply = Arc::try_new(Transaction::new_reply(self, process, tr, allow_fds)?)?; + let reply = Transaction::new_reply(self, process, tr, allow_fds)?; self.inner.lock().push_work(completion); orig.from.deliver_reply(Either::Left(reply), &orig); Ok(()) @@ -592,7 +602,7 @@ impl Thread { let handle = unsafe { tr.target.handle }; let node_ref = self.process.get_transaction_node(handle)?; let completion = Arc::try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; - let transaction = Arc::try_new(Transaction::new(node_ref, None, self, tr)?)?; + let transaction = Transaction::new(node_ref, None, self, tr)?; self.inner.lock().push_work(completion); // TODO: Remove the completion on error? transaction.submit()?; @@ -606,7 +616,7 @@ impl Thread { // could this happen? let top = self.top_of_transaction_stack()?; let completion = Arc::try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; - let transaction = Arc::try_new(Transaction::new(node_ref, top, self, tr)?)?; + let transaction = Transaction::new(node_ref, top, self, tr)?; // Check that the transaction stack hasn't changed while the lock was released, then update // it with the new transaction. diff --git a/drivers/android/transaction.rs b/drivers/android/transaction.rs index 94de80dc6f0383..4e7ab4f19f9170 100644 --- a/drivers/android/transaction.rs +++ b/drivers/android/transaction.rs @@ -1,10 +1,20 @@ // SPDX-License-Identifier: GPL-2.0 -use alloc::sync::Arc; -use core::sync::atomic::{AtomicBool, Ordering}; +use alloc::{boxed::Box, sync::Arc}; +use core::{ + pin::Pin, + sync::atomic::{AtomicBool, Ordering}, +}; use kernel::{ - io_buffer::IoBufferWriter, linked_list::Links, prelude::*, sync::Ref, - user_ptr::UserSlicePtrWriter, ScopeGuard, + bindings, + file::{File, FileDescriptorReservation}, + io_buffer::IoBufferWriter, + linked_list::List, + linked_list::{GetLinks, Links}, + prelude::*, + sync::{Ref, SpinLock}, + user_ptr::UserSlicePtrWriter, + Error, ScopeGuard, }; use crate::{ @@ -16,7 +26,12 @@ use crate::{ DeliverToRead, Either, }; +struct TransactionInner { + file_list: List>, +} + pub(crate) struct Transaction { + inner: SpinLock, // TODO: Node should be released when the buffer is released. node_ref: Option, stack_next: Option>, @@ -37,13 +52,16 @@ impl Transaction { stack_next: Option>, from: &Arc, tr: &BinderTransactionData, - ) -> BinderResult { + ) -> BinderResult> { let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0; let to = node_ref.node.owner.clone(); - let alloc = from.copy_transaction_data(&to, tr, allow_fds)?; + let mut alloc = from.copy_transaction_data(&to, tr, allow_fds)?; let data_address = alloc.ptr; + let file_list = alloc.take_file_list(); alloc.keep_alive(); - Ok(Self { + let mut tr = Arc::try_new(Self { + // SAFETY: `spinlock_init` is called below. + inner: unsafe { SpinLock::new(TransactionInner { file_list }) }, node_ref: Some(node_ref), stack_next, from: from.clone(), @@ -55,7 +73,13 @@ impl Transaction { offsets_size: tr.offsets_size as _, links: Links::new(), free_allocation: AtomicBool::new(true), - }) + })?; + + let mut_tr = Arc::get_mut(&mut tr).ok_or(Error::EINVAL)?; + + // SAFETY: `inner` is pinned behind `Arc`. + kernel::spinlock_init!(Pin::new_unchecked(&mut_tr.inner), "Transaction::inner"); + Ok(tr) } pub(crate) fn new_reply( @@ -63,11 +87,14 @@ impl Transaction { to: Ref, tr: &BinderTransactionData, allow_fds: bool, - ) -> BinderResult { - let alloc = from.copy_transaction_data(&to, tr, allow_fds)?; + ) -> BinderResult> { + let mut alloc = from.copy_transaction_data(&to, tr, allow_fds)?; let data_address = alloc.ptr; + let file_list = alloc.take_file_list(); alloc.keep_alive(); - Ok(Self { + let mut tr = Arc::try_new(Self { + // SAFETY: `spinlock_init` is called below. + inner: unsafe { SpinLock::new(TransactionInner { file_list }) }, node_ref: None, stack_next: None, from: from.clone(), @@ -79,7 +106,13 @@ impl Transaction { offsets_size: tr.offsets_size as _, links: Links::new(), free_allocation: AtomicBool::new(true), - }) + })?; + + let mut_tr = Arc::get_mut(&mut tr).ok_or(Error::EINVAL)?; + + // SAFETY: `inner` is pinned behind `Arc`. + kernel::spinlock_init!(Pin::new_unchecked(&mut_tr.inner), "Transaction::inner"); + Ok(tr) } /// Determines if the transaction is stacked on top of the given transaction. @@ -136,6 +169,33 @@ impl Transaction { process.push_work(self) } } + + /// Prepares the file list for delivery to the caller. + fn prepare_file_list(&self) -> Result>> { + // Get list of files that are being transferred as part of the transaction. + let mut file_list = core::mem::replace(&mut self.inner.lock().file_list, List::new()); + + // If the list is non-empty, prepare the buffer. + if !file_list.is_empty() { + let alloc = self.to.buffer_get(self.data_address).ok_or(Error::ESRCH)?; + let cleanup = ScopeGuard::new(|| { + self.free_allocation.store(false, Ordering::Relaxed); + }); + + let mut it = file_list.cursor_front_mut(); + while let Some(file_info) = it.current() { + let reservation = FileDescriptorReservation::new(bindings::O_CLOEXEC)?; + alloc.write(file_info.buffer_offset, &reservation.reserved_fd())?; + file_info.reservation = Some(reservation); + it.move_next(); + } + + alloc.keep_alive(); + cleanup.dismiss(); + } + + Ok(file_list) + } } impl DeliverToRead for Transaction { @@ -145,9 +205,19 @@ impl DeliverToRead for Transaction { pub sender_euid: uid_t, */ let send_failed_reply = ScopeGuard::new(|| { - let reply = Either::Right(BR_FAILED_REPLY); - self.from.deliver_reply(reply, &self); + if self.node_ref.is_some() && self.flags & TF_ONE_WAY == 0 { + let reply = Either::Right(BR_FAILED_REPLY); + self.from.deliver_reply(reply, &self); + } }); + let mut file_list = if let Ok(list) = self.prepare_file_list() { + list + } else { + // On failure to process the list, we send a reply back to the sender and ignore the + // transaction on the recipient. + return Ok(true); + }; + let mut tr = BinderTransactionData::default(); if let Some(nref) = &self.node_ref { @@ -165,10 +235,6 @@ impl DeliverToRead for Transaction { tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size)) as _; } - // When `drop` is called, we don't want the allocation to be freed because it is now the - // user's reponsibility to free it. - self.free_allocation.store(false, Ordering::Relaxed); - let code = if self.node_ref.is_none() { BR_REPLY } else { @@ -183,6 +249,27 @@ impl DeliverToRead for Transaction { // here on out. send_failed_reply.dismiss(); + // Commit all files. + { + let mut it = file_list.cursor_front_mut(); + while let Some(file_info) = it.current() { + if let Some(reservation) = file_info.reservation.take() { + if let Some(file) = file_info.file.take() { + reservation.commit(file); + } + } + + it.move_next(); + } + } + + // When `drop` is called, we don't want the allocation to be freed because it is now the + // user's reponsibility to free it. + // + // `drop` is guaranteed to see this relaxed store because `Arc` guarantess that everything + // that happens when an object is referenced happens-before the eventual `drop`. + self.free_allocation.store(false, Ordering::Relaxed); + // When this is not a reply and not an async transaction, update `current_transaction`. If // it's a reply, `current_transaction` has already been updated appropriately. if self.node_ref.is_some() && tr.flags & TF_ONE_WAY == 0 { @@ -209,3 +296,35 @@ impl Drop for Transaction { } } } + +pub(crate) struct FileInfo { + links: Links, + + /// The file for which a descriptor will be created in the recipient process. + file: Option, + + /// The file descriptor reservation on the recipient process. + reservation: Option, + + /// The offset in the buffer where the file descriptor is stored. + buffer_offset: usize, +} + +impl FileInfo { + pub(crate) fn new(file: File, buffer_offset: usize) -> Self { + Self { + file: Some(file), + reservation: None, + buffer_offset, + links: Links::new(), + } + } +} + +impl GetLinks for FileInfo { + type EntryType = Self; + + fn get_links(data: &Self::EntryType) -> &Links { + &data.links + } +}