Skip to content

binder: Add support for transferring file descriptors. #371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2021

Conversation

wedsonaf
Copy link

@wedsonaf wedsonaf commented Jun 9, 2021

Signed-off-by: Wedson Almeida Filho [email protected]

@ksquirrel

This comment has been minimized.

Copy link
Member

@ojeda ojeda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Copy link
Collaborator

@TheSven73 TheSven73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish I understood Binder, so my review would be more useful than it is now. But I guess something is better than nothing, right?

@@ -19,6 +22,7 @@ pub(crate) struct Allocation<'a> {
pub(crate) process: &'a Process,
allocation_info: Option<AllocationInfo>,
free_on_drop: bool,
pub(crate) file_list: List<Box<FileInfo>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: when looking at the binder code, I often wonder why struct members are so often declared as pub(crate)? Would it be beneficial to replace with a method function that does what you plan to do with the struct member, without having to expose internal implementation details?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these modules started off in another module with access to all fields. Eventually I pushed some of these out to their own modules and made most fields private; for the ones where this wasn't trivial I just made them pub(crate) because I had (and still do) bigger changes to worry about. I didn't want to have pure getters/setters as in Java.

For this particular change, I already have take_file_list (added later), so I may as well add a file_list push and make file_list private. Will do.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got ya. It's a WIP.

pub(crate) struct Transaction {
inner: SpinLock<TransactionInner>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a real need for a SpinLock here, as opposed to a Mutex? Are you locking this from a non-sleep context?
(Not suggesting it should be a Mutex - I just genuinely don't know)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need some synchronisation. Given that the hold times are small (just to swap a pointer), I chose a spinlock because it will just spin on contention as opposed to sleep. This is preferable because schedule() itself would take longer than the hold time of the lock, that is, sleeping is counterproductive on contention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to swap a pointer

Would AtomicPtr work?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to swap a pointer

Would AtomicPtr work?

I considered it, but decided against it for two reasons: it doesn't work for pointers to ?Sized types, and I may want to add more fields to it inner eventually.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Could be one of the parameters to be verified using the ping benchmark somewhere in the future?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, absolutely.

One "advantage" we have with binder is that we have the C implementation to benchmark against. As long as we're not too far off, we're good.

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");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still trying to get my head around Pinning. Is there a guarantee that Self will never move out of the Arc?
I'm guessing you either need an explicit guarantee (in the form of an # Invariant, might be hard to mentally enforce here) or return Pin<Arc<Self>> ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not guaranteed. At the moment it's an invariant of the project: I don't move objects behind Arcs.

Some background: in the beginning I had a bunch of TODO: This needs to be pinned in a lot of places, and a bunch of Pins that resulted in a bunch of unsafe blocks (it's always unsafe to get a mutable reference to a pinned type because the compiler needs us to manually ensure that we don't move anything). I removed them until we get a better handle on how this will look like.


// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If using Ordering::Relaxed, is the following scenario possible?

  1. a thread calls do_work() and free_allocation is set to false
  2. another thread calls Drop, doesn't "see" that free_allocation is false yet, frees the allocation
  3. user frees the allocation again?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible because Arc prevents that. In particular, all ref count decrements have release semantics, which are paired with an acquire right before drop. See https://doc.rust-lang.org/src/alloc/sync.rs.html#1540.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That definitely sounds legit :) It may be hard to know for most people looking at the code? Maybe document this in the code at some point in the future? Idk, I'm sure you've got more important things on your plate in Binder.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. I'll add a comment that describes this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got me thinking. Currently in kernel C, there are lots of variables that convey information from one thread to another - like free_allocation here. But many of them don't need to be protected by a synchronization primitive. That's because many kernel calls issue explicit fences. E.g. when you're waiting on a flag with wait_event_interruptible(), that function is guaranteed to have issued a fence when it returns, so the int or bool flag you're waiting for can never be out of date.

But in Rust, we can't make that optimization? We always need the non-zero cost synchronization primitive, even if we can logically deduce things will work across threads?

Anyways, this is too optimize-ey to worry about at this point IMHO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load/store with Ordering::Relaxed doesn't have overhead though. There is a nice article about what's actually generated: https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html. load/store with Ordering::Relaxed are basically just normal load/store with additional tear-free guarantee (even though normal load/store are de facto tear-free).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting!! Is Relaxed equivalent to a normal access in a structure?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting!! Is Relaxed equivalent to a normal access in a structure?

If you only access it once :)

If you access an atomic integer multiple times with Relaxed ordering the compiler will need to generate multiple load instructions (since its value may change), while for a non-atomic integer Rust compiler is free to assume that value wouldn't change (even if there is a function-call in between two accesses).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's extremely interesting, thank you Gary !

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load/store with Ordering::Relaxed doesn't have overhead though.

While this is generally true, it isn't always true. Even on architectures that offer non-tearing word-sized ops, if the type is bigger than the word size, we may need more expensive instructions. For example, on 32-bit x86, if you try to atomically read/write a 64-bit integer, it won't be a regular load/store.

But to Sven's original point, I tend to agree that Rust imposes restrictions that may make the code less efficient than C. We need to look into those carefully as I think a lot of them can be exposed safely in Rust, however, some of them are inherently unsafe (i.e., they rely on the developer following rules that cannot be enforced by the compiler) -- for these I think we'll have to do as discussed in another bug/issue, provide a combination of: safe alternative with the extra cost as the price for safety, and/or an unsafe optimal alternative. If we offer both, we let the developer choose according to their needs.


let obj = view.read::<bindings::binder_fd_object>(offset)?;
// SAFETY: The type is `BINDER_TYPE_FD`, so `fd` is populated.
let fd = unsafe { obj.__bindgen_anon_1.fd };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To nitpick: While this is indeed safe, I don't think the reason is correct. The program using binder doesn't have to populate fd. It is not UB to read it however, as from the perspective of the kernel all userspace data is defined.

Copy link
Member

@ojeda ojeda Jun 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I am confused here.

From a quick look at the reference, it says Rust unions do not have an active member (unlike C++), i.e. bits are always re-interpreted. It also says reading uninitialized memory from unions is OK, so that is not a concern either. Thus, for u32 etc. it would seem it is always safe to read from it since there are no invalid values possible.

So two questions:

  1. Why does Rust not simply mark the operation as safe? Is it because they did not want to make the operation conditionally-safe depending on the type?
  2. If it is always safe, what do you mean when you say the reason is "as from the perspective of the kernel all userspace data is defined"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does Rust not simply mark the operation as safe? Is it because they did not want to make the operation conditionally-safe depending on the type?

Imagine the following union

union TransmuteToBox {
    num: usize,
    the_box: Box<u8>,
}

If reading an union variant was safe TransmuteToBox { num: 0 }.the_box would give a dangling box, which is UB.

What the reference is saying is that if the byte representation of the union is valid for an union variant, then reading it as such union variant is safe. This is unlike C where writing as one variant and then reading it as another variant is UB even if both variants have the same set of allowed byte representations. For example the following would be UB in C:

union TransmuteU32ToFourU8 {
    int: u32,
    array: [u8; 4],
}

TransmuteU32ToFourU8 { int: 0 }.array

despite the fact that all 0s is a valid byte representation for [u8; 4].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is always safe, what do you mean when you say the reason is "as from the perspective of the kernel all userspace data is defined"?

Even if the data is undefined inside the program that called the ioctl, the compiler of the kernel has no way to exploit this fact as it doesn't know.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that Rust had an active member. But given that isn't the case, it seems like the justifications should just be about the representation of the field being used. From the reference:

It is the programmer's responsibility to make sure that the data is valid at the field's type. Failing to do so results in undefined behavior. For example, reading the value 3 through of a field of the boolean type is undefined behavior.

Would something like this be reasonable to you all?

// SAFETY: `fd` is a `u32`; any bit pattern is a valid representation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copy_from_userspace is written in assembly AFAICT, which is opaque, so a C or Rust compiler has to assume that it initializes the full copy target. If the compiler was able to see that the userspace leaves it unitialized, that would make it impossible to write a kernel that can't be made to invoke UB by a malicious program.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it's shared memory rather than copying from userspace, but still there is no possible way to let the "uninitialized" property across process or kernel/userspace boundary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is shared writable memory, you will have to use atomics, as otherwise you could have a data race, which is UB. The compiler could reload the value and expect it to still be the same if atomics are not used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view.read already copies it :) it's just that this is copied directly in kernel space rather than from userspace.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory is indeed shared with userspace, but it (userspace) can only read from it. It is populated by copying from another userspace process to the kernel mapping (which is writable); while it is being copied, the receiving end (the one that has it mapped readonly) treats it as unallocated memory, so they don't touch it.

fn prepare_file_list(&self) -> Result<List<Box<FileInfo>>> {
// Get list of files that are being transfered as part of the transaction.
let mut file_list = List::new();
core::mem::swap(&mut file_list, &mut self.inner.lock().file_list);
Copy link
Member

@bjorn3 bjorn3 Jun 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use mem::replace here too. Something like let mut file_list = core::mem::replace(&mut ..., List::new());.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change this too.

@ksquirrel
Copy link
Member

Review of 42230f159d07:

  • ✔️ Commit 42230f1: Looks fine!

@wedsonaf
Copy link
Author

wedsonaf commented Jun 9, 2021

I wish I understood Binder, so my review would be more useful than it is now. But I guess something is better than nothing, right?

Indeed, thanks for reviewing! And please keep at it :)

@wedsonaf wedsonaf merged commit d8c6b9c into Rust-for-Linux:rust Jun 11, 2021
@wedsonaf wedsonaf deleted the file-descriptor branch June 11, 2021 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants