Skip to content

Commit

Permalink
Merge pull request torvalds#371 from wedsonaf/file-descriptor
Browse files Browse the repository at this point in the history
binder: Add support for transferring file descriptors.
  • Loading branch information
wedsonaf authored Jun 11, 2021
2 parents 62bc7aa + 42230f1 commit d8c6b9c
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 31 deletions.
19 changes: 16 additions & 3 deletions drivers/android/allocation.rs
Original file line number Diff line number Diff line change
@@ -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> {
Expand All @@ -19,6 +22,7 @@ pub(crate) struct Allocation<'a> {
pub(crate) process: &'a Process,
allocation_info: Option<AllocationInfo>,
free_on_drop: bool,
file_list: List<Box<FileInfo>>,
}

impl<'a> Allocation<'a> {
Expand All @@ -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<Box<FileInfo>> {
replace(&mut self.file_list, List::new())
}

pub(crate) fn add_file_info(&mut self, file: Box<FileInfo>) {
self.file_list.push_back(file);
}

fn iterate<T>(&self, mut offset: usize, mut size: usize, mut cb: T) -> Result
where
T: FnMut(&Pages<0>, usize, usize) -> Result,
Expand Down
30 changes: 20 additions & 10 deletions drivers/android/thread.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -19,7 +19,7 @@ use crate::{
defs::*,
process::{AllocationInfo, Process},
ptr_align,
transaction::Transaction,
transaction::{FileInfo, Transaction},
DeliverCode, DeliverToRead, DeliverToReadListAdapter, Either,
};

Expand Down Expand Up @@ -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)?;
Expand All @@ -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 _;
Expand All @@ -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)
})?;
Expand All @@ -407,6 +408,15 @@ impl Thread {
if !allow_fds {
return Err(BinderError::new_failed());
}

let obj = view.read::<bindings::binder_fd_object>(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_),
}
Expand All @@ -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::<usize>()) {
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);
}
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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()?;
Expand All @@ -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.
Expand Down
155 changes: 137 additions & 18 deletions drivers/android/transaction.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -16,7 +26,12 @@ use crate::{
DeliverToRead, Either,
};

struct TransactionInner {
file_list: List<Box<FileInfo>>,
}

pub(crate) struct Transaction {
inner: SpinLock<TransactionInner>,
// TODO: Node should be released when the buffer is released.
node_ref: Option<NodeRef>,
stack_next: Option<Arc<Transaction>>,
Expand All @@ -37,13 +52,16 @@ impl Transaction {
stack_next: Option<Arc<Transaction>>,
from: &Arc<Thread>,
tr: &BinderTransactionData,
) -> BinderResult<Self> {
) -> BinderResult<Arc<Self>> {
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(),
Expand All @@ -55,19 +73,28 @@ 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(
from: &Arc<Thread>,
to: Ref<Process>,
tr: &BinderTransactionData,
allow_fds: bool,
) -> BinderResult<Self> {
let alloc = from.copy_transaction_data(&to, tr, allow_fds)?;
) -> BinderResult<Arc<Self>> {
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(),
Expand All @@ -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.
Expand Down Expand Up @@ -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<List<Box<FileInfo>>> {
// 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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -209,3 +296,35 @@ impl Drop for Transaction {
}
}
}

pub(crate) struct FileInfo {
links: Links<FileInfo>,

/// The file for which a descriptor will be created in the recipient process.
file: Option<File>,

/// The file descriptor reservation on the recipient process.
reservation: Option<FileDescriptorReservation>,

/// 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<Self::EntryType> {
&data.links
}
}

0 comments on commit d8c6b9c

Please sign in to comment.