Skip to content
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

(RFC) rust: mark extern "C" (ffi) functions as unsafe #4666

Closed
wants to merge 3 commits into from

Conversation

jasonish
Copy link
Member

Currently the macros build_slice and cast_pointer wrap their code in unsafe not
requiring the containing function to be unsafe, but clippy recommends that any
public function working with raw pointers be marked unsafe. To comply with this
idiom, remove unsafe from the macros, requiring any function that uses these
macros to be unsafe itself.

Reference:
https://rust-lang.github.io/rust-clippy/master/index.html#not_unsaf

The next step would be to find the ffi funcations that use an unsafe block
and mark them unsafe, removing the unsafe blocks.

PRScript output (if applicable):

Currently the macros build_slice and cast_pointer wrap their
code in unsafe not requiring the containing function to be unsafe,
but clippy recommends that any public function working with
raw pointers be marked unsafe. To comply with this idiom,
remove unsafe from the macros, requiring any function that
uses these macros to be unsafe itself.

Reference:
https://rust-lang.github.io/rust-clippy/master/index.html#not_unsaf
Now that many of the ffi functions are marked unsafe, remove
unnecessary unsafe blocks within these functions.
Suppresses some clippy lints that have more to do with style
than anything else, to reduce the amount of noise in the
clippy output.
@jasonish
Copy link
Member Author

CC: @chifflier @dbcfd

@victorjulien
Copy link
Member

This seems a bit counter intuitive to me. Should it lead to the functions just be as minimal as possible to pass the objects created from the raw pointers into 'safe' functions? Ultimately we pass around data based on unsafe input, so it seems a bit strange to classify more code as unsafe while fundamentally nothing changes. All rust needs to know that we vouch for the validity of the raw pointers, right?

@victorjulien
Copy link
Member

Btw I don't object to refactoring to make code checkers happy, but I would like to see if we can avoid having more of the logic inside unsafe blocks.

@chifflier
Copy link
Contributor

This seems a bit counter intuitive to me. Should it lead to the functions just be as minimal as possible to pass the objects created from the raw pointers into 'safe' functions?

Yes, that's the usual recommendation for FFI code: write unsafe wrappers (mostly to wrap pointers and cast types), and call safe (classic) functions as soon as possible.

Ultimately we pass around data based on unsafe input, so it seems a bit strange to classify more code as unsafe while fundamentally nothing changes. All rust needs to know that we vouch for the validity of the raw pointers, right?

There are usually two kinds ofunsafe:

  • code that must access objects outside Rust's memory checks: syscalls, calling C, wrapping arguments
  • voodoo code that casts objects or performs lifetime changes etc.

The former is quite expected at FFI borders, the latter should be avoided unless really justified.

The idiomatic Rust way is what you and @ish described (and I think in fact everyone agrees): FFI functions are usually marked unsafe (if they dereference pointers in particular), and must perform as little actions as possible (immediately call a Rust function, and only handle wrapping to/from C).

So, my suggestion would be to indeed mark unsafe these functions, but in the same time to ensure they perform only the unsafe part, and call safe code as soon as possible. This may require splitting functions, but should not be bad since Rust tends to try inlining functions as much as possible.

@dbcfd
Copy link

dbcfd commented Mar 12, 2020

I agree with Pierre's recommendation and approach.

@jasonish
Copy link
Member Author

So I think its a bit more than making code checkers safe, but keeping up with generally accepted practices, and this is one lint I'd like to get rid of. I've also felt that when seeing these unsafe blocks, you really have to consider the code after them as well as being unsafe. So I think moving to a practice where the ffi functions are unsafe, but do as little as possible makes sense for us.

@victorjulien
Copy link
Member

So I think its a bit more than making code checkers safe, but keeping up with generally accepted practices, and this is one lint I'd like to get rid of. I've also felt that when seeing these unsafe blocks, you really have to consider the code after them as well as being unsafe. So I think moving to a practice where the ffi functions are unsafe, but do as little as possible makes sense for us.

But isn't the consequence of this that we should consider all our Rust code unsafe? It's all about dealing with data from unsafe origin.

@jasonish
Copy link
Member Author

jasonish commented Mar 12, 2020

So I think its a bit more than making code checkers safe, but keeping up with generally accepted practices, and this is one lint I'd like to get rid of. I've also felt that when seeing these unsafe blocks, you really have to consider the code after them as well as being unsafe. So I think moving to a practice where the ffi functions are unsafe, but do as little as possible makes sense for us.

But isn't the consequence of this that we should consider all our Rust code unsafe? It's all about dealing with data from unsafe origin.

Thats somewhat true, and is why you should consider any extern "C"
function unsafe, as the caller (our C) needs to ensure its passing valid
data as well.

@dbcfd
Copy link

dbcfd commented Mar 12, 2020

So I think its a bit more than making code checkers safe, but keeping up with generally accepted practices, and this is one lint I'd like to get rid of. I've also felt that when seeing these unsafe blocks, you really have to consider the code after them as well as being unsafe. So I think moving to a practice where the ffi functions are unsafe, but do as little as possible makes sense for us.

But isn't the consequence of this that we should consider all our Rust code unsafe? It's all about dealing with data from unsafe origin.

Oh, I see now more what you're saying. extern "C" functions called from C code should not be marked unsafe, as they have to be marked in this fashion for interop. We should not be calling extern "C" functions from Rust code that have been defined in Rust code. We should be exposing a normal rust interface that is invoked, and the extern "C" version should only be invoked by C code.

@chifflier
Copy link
Contributor

So I think its a bit more than making code checkers safe, but keeping up with generally accepted practices, and this is one lint I'd like to get rid of. I've also felt that when seeing these unsafe blocks, you really have to consider the code after them as well as being unsafe. So I think moving to a practice where the ffi functions are unsafe, but do as little as possible makes sense for us.

But isn't the consequence of this that we should consider all our Rust code unsafe? It's all about dealing with data from unsafe origin.

I would more explain that as "if the input arguments of the Rust code are trusted/verified (resp. untrusted), then the Rust code is safe (resp. unsafe)".

One cannot expect miracles from Rust: for ex, if you pass a buffer and a wrong (too short) length as arguments, then the built slice will trigger something wrong inside Rust safe code.

pub type StateTxFreeFn = extern "C" fn (*mut c_void, u64);
pub type StateGetTxFn = extern "C" fn (*mut c_void, u64) -> *mut c_void;
pub type StateGetTxCntFn = extern "C" fn (*mut c_void) -> u64;
pub type StateTxFreeFn = unsafe extern "C" fn (*mut c_void, u64);
Copy link

Choose a reason for hiding this comment

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

I think the linter is specifically complaining about these extern "C" functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is no complaint here, these types just had to be updated after the offending functions were updated.

@@ -25,7 +25,7 @@ use std;
use x509_parser::parse_x509_der;

#[no_mangle]
pub extern "C" fn rs_rdp_to_json(tx: *mut std::os::raw::c_void) -> *mut JsonT {
pub unsafe extern "C" fn rs_rdp_to_json(tx: *mut std::os::raw::c_void) -> *mut JsonT {
Copy link

Choose a reason for hiding this comment

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

The linter is probably not complaining about these, since they're exposing an interface for C to call.

Copy link
Member Author

Choose a reason for hiding this comment

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

The linter was complaining about this function as it dereferences a raw pointer, and suggest the function should be unsafe, not just block.

@chifflier
Copy link
Contributor

extern "C" functions called from C code should not be marked unsafe, as they have to be marked in this fashion for interop.

This is indeed not required, but the good practice (enforced by clippy) is to add an unsafe keyword if it dereferences an input pointer (because it cannot guarantee safety).

We should not be calling extern "C" functions from Rust code that have been defined in Rust code. We should be exposing a normal rust interface that is invoked, and the extern "C" version should only be invoked by C code.

I agree with that

@victorjulien
Copy link
Member

victorjulien commented Mar 12, 2020

We should not be calling extern "C" functions from Rust code that have been defined in Rust code. We should be exposing a normal rust interface that is invoked, and the extern "C" version should only be invoked by C code.

I agree with that

Can we enforce this somehow?

I agree with it as well. While I do not see a way to enforce it, with a naming convention we could easily spot it.. For example, if all FFI functions are prefixed with "rs_", or follow our C style of SomeLongFunctionName, it would be easy to spot during review.

@chifflier
Copy link
Contributor

I think I wasn't clear at some point: extern "C" functions are not required to be unsafe. Imagine a function adding two integers, it would be extern "C" but not unsafe.

The unsafe keyword has the usual meaning: this function is doing something that could be dangerous. The only difference is that, while the usual approach is to have an unsafe block, there is an exception for the specific case when you dereference a pointer. In that case, clippy suggest to mark the whole function as unsafe (not the block), so it appears in the documentation.

Marking a block or a function as unsafe is always possible, it's just the usual rules on when to choose which approach to use.

@dbcfd
Copy link

dbcfd commented Mar 12, 2020

extern "C" functions called from C code should not be marked unsafe, as they have to be marked in this fashion for interop.

This is indeed not required, but the good practice (enforced by clippy) is to add an unsafe keyword if it dereferences an input pointer (because it cannot guarantee safety).

The keyword is definitely nice for identifying code which has to deal with raw input, but I don't have strong opinions about whether the keyword is applied at the function level, or at the code level, since it is more of an indication to the rust developer. Both methods still require examining the code to make sure it is handling the raw input appropriately.

Definitely would like to see less logic in functions dealing with raw input, and more in a safe function that is called from the extern "C" function.

@dbcfd
Copy link

dbcfd commented Mar 12, 2020

In that case, clippy suggest to mark the whole function as unsafe (not the block), so it appears in the documentation.

Ah. In that case, all of our rust functions exposed to C would likely be unsafe. Which is fine, especially if it forces us to split out safe code from unsafe code, so the exposed interfaces only deal with the untrusted input, and the remainder of the logic is in safe functions called from the exposed interface.

@jasonish jasonish mentioned this pull request Jun 23, 2020
@catenacyber catenacyber marked this pull request as draft July 23, 2020 14:53
@liurunhao

This comment has been minimized.

@jasonish
Copy link
Member Author

Continued at #6280 which also removes transmute.

@jasonish jasonish closed this Jul 27, 2021
@jasonish jasonish deleted the rust/unsafe-ffi/v1 branch August 23, 2021 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

5 participants