Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 67 additions & 52 deletions src/en/07_ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ the C standard library.

### Non-robust types: references, function pointers, enums

A *trap representation* of a particular type is a representation (pattern of
A _trap representation_ of a particular type is a representation (pattern of
bits) that respects the type's representation constraints (such as size and
alignment) but does not represent a valid value of this type and leads to
undefined behavior.
Expand All @@ -253,15 +253,15 @@ the C-compatible types:
- compound types that contain a field of a non-robust type.

On the other hand, integer types (`u*`/`i*`), packed compound types that contain
no non-robust fields, for instance are *robust types*.
no non-robust fields, for instance are _robust types_.

Non-robust types are a difficulty when interfacing two languages. It revolves
into deciding **which language of the two is responsible in asserting the
validity of boundary-crossing values** and how to do it.

> ### Rule {{#check FFI-CKNONROBUST | Do not use unchecked non-robust foreign values}}
>
> In a secure Rust development, there must not be any use of *unchecked* foreign
> In a secure Rust development, there must not be any use of _unchecked_ foreign
> values of non-robust types.
>
> In other words, either Rust translates robust types to non-robust types
Expand All @@ -288,10 +288,10 @@ function references, and enums, and are discussed below.
>
> Rust's `bool` has been made equivalent to C99's `_Bool` (aliased as `bool`
> in `<stdbool.h>`) and C++'s `bool`. However, loading a value other than 0 and
> 1 as a `_Bool`/`bool` is an undefined behavior *on both sides*.
> 1 as a `_Bool`/`bool` is an undefined behavior _on both sides_.
> Safe Rust ensures that. Standard-compliant C and C++ compilers ensure that no
> value but 0 and 1 can be *stored* in a `_Bool`/`bool` value but cannot
> guarantee the absence of an *incorrect reinterpretation* (e.g., union types,
> value but 0 and 1 can be _stored_ in a `_Bool`/`bool` value but cannot
> guarantee the absence of an _incorrect reinterpretation_ (e.g., union types,
> pointer cast). To detect such a bad reinterpretation, sanitizers such as
> LLVM's `-fsanitize=bool` may be used.

Expand All @@ -317,7 +317,7 @@ Rust references may be used reasonably with other C-compatible languages
including C variants allowing for non-null type checking, e.g. Microsoft SAL
annotated code.

On the other hand, Rust's *pointer types* may also lead to undefined behaviors
On the other hand, Rust's _pointer types_ may also lead to undefined behaviors
but are more verifiable, mostly against `std/core::ptr::null()` (C's `(void*)0`)
but also in some context against a known valid memory range (particularly in
embedded systems or kernel-level programming). Another advantage of using Rust
Expand Down Expand Up @@ -345,7 +345,7 @@ an `unsafe` block or function.
> In a secure Rust development, every foreign references that is transmitted to
> Rust through FFI must be **checked on the foreign side** either automatically
> (for instance, by a compiler) or manually.
>
>
> Exceptions include Rust references in an opaque wrapping that is created
> and manipulated only from the Rust side and `Option`-wrapped references
> (see Note below).
Expand All @@ -358,7 +358,7 @@ an `unsafe` block or function.
> pointer must check their validity beforehand.
> In particular, pointers must be checked to be non-null before any use.
>
> Stronger approaches are advisable when possible. They includes checking
> Stronger approaches are advisable when possible. They include checking
> pointers against known valid memory range or tagging (or signing) pointers
> (particularly applicable if the pointed value is only manipulated from Rust).

Expand All @@ -378,7 +378,7 @@ pub unsafe extern fn add_in_place(a: *mut u32, b: u32) {
```

Note that the methods `as_ref` and `as_mut` (for mutable pointers) allows easy
access to a reference while ensuring a null check in a very *Rusty* way.
access to a reference while ensuring a null check in a very _Rusty_ way.
On the other side in C, it can be used as follows:

```c
Expand Down Expand Up @@ -463,7 +463,7 @@ severe consequences on software security. Unfortunately, checking an `enum`
value at the FFI boundary is not simple on both sides.

On the Rust side, it consists to actually use an integer type in the `extern`
block declaration, a *robust* type, and then to perform a checked conversion
block declaration, a _robust_ type, and then to perform a checked conversion
to the enum type.

On the foreign side, it is possible only if the other language allows for
Expand Down Expand Up @@ -560,13 +560,13 @@ The same is true for other kind of resources such as sockets or files.
Rust tracks variable ownership and lifetime to determine at compilation time if
and when memory should be deallocated. Thanks to the `Drop` trait, one can
exploit this system to reclaim other kind of resources such as file or network
access. *Moving* some piece of data from Rust to a foreign language means also
access. _Moving_ some piece of data from Rust to a foreign language means also
abandoning the possible reclamations associated with it.

> ### Rule {{#check FFI-MEM-NODROP | Do not use types that implement `Drop` at FFI boundary}}
>
> In a secure Rust development, Rust code must not implement `Drop` for any
> types that are directly transmitted to foreign code (i.e. not through a
> types that are directly transmitted to foreign code (i.e. not through a
> pointer or reference).

In fact, it is advisable to only use `Copy` types. Note that `*const T` is
Expand Down Expand Up @@ -594,10 +594,10 @@ wrapper around the foreign type:

> ### Recommendation {{#check FFI-MEM-WRAPPING | Wrap foreign data in memory releasing wrapper}}
>
> In a secure Rust development, any non-sensitive foreign piece of data that are
> In a secure Rust development, any non-sensitive foreign piece of data that is
> allocated and deallocated in the foreign language should be encapsulated in a
> `Drop` type in such a way as to provide automatic deallocation in Rust,
> through an automatic call to the foreing language deallocation routine.
> through an automatic call to the foreign language deallocation routine.

A simple example of Rust wrapping over an external opaque type:

Expand Down Expand Up @@ -653,7 +653,7 @@ impl Drop for Foo {

> ### Warning
>
> Because panics may lead to not running the `Drop::drop` method this solution
> Because panics may lead to not running the `Drop::drop` method, this solution
> is not sufficient for sensitive deallocation (such as wiping sensitive data)
> except if the code is guaranteed to never panic.
>
Expand All @@ -667,21 +667,23 @@ In C for instance there is no easy way to check that the appropriate destructor
is checked. A possible approach is to exploit callbacks to ensure that the
reclamation is done.

The following Rust code is a **thread-unsafe** example of a C-compatible API
that provide callback to ensure safe resource
reclamation:
The following Rust code is an example of a C-compatible API that exploits
callbacks to ensure safe resource reclamation:

```rust,noplaypen
```rust
# use std::ops::Drop;
#
pub struct XtraResource {/*fields */}
pub struct XtraResource { /* fields */ }

impl XtraResource {
pub fn new() -> Self {
XtraResource { /* ... */}
}
pub fn dosthg(&mut self) {
/*...*/
pub fn dosthg(&mut self, arg: u32) {
/*... things that may panic ... */
# if arg == 0xDEAD_C0DE {
# panic!("oops XtraResource.dosthg panics!");
# }
}
}

Expand All @@ -693,57 +695,55 @@ impl Drop for XtraResource {

pub mod c_api {
use super::XtraResource;
use std::panic::catch_unwind;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU32, Ordering};

const INVALID_TAG: u32 = 0;
const VALID_TAG: u32 = 0xDEAD_BEEF;
const ERR_TAG: u32 = 0xDEAF_CAFE;

static mut COUNTER: u32 = 0;

pub struct CXtraResource {
tag: u32, // to detect accidental reuse
id: u32,
tag: AtomicU32, // to detect accidental reuse
inner: XtraResource,
}

#[no_mangle]
pub unsafe extern "C" fn xtra_with(cb: extern "C" fn(*mut CXtraResource) -> ()) {
let inner = if let Ok(res) = catch_unwind(XtraResource::new) {
pub unsafe extern "C" fn xtra_with(cb: unsafe extern "C" fn(*mut CXtraResource) -> ()) {
let inner = if let Ok(res) = catch_unwind(AssertUnwindSafe(XtraResource::new)) {
res
} else {
# println!("cannot allocate resource");
return;
};
let id = COUNTER;
let tag = VALID_TAG;

COUNTER = COUNTER.wrapping_add(1);
// Use heap memory and do not provide pointer to stack to C code!
let mut boxed = Box::new(CXtraResource { tag, id, inner });
let mut wrapped = CXtraResource {
tag: AtomicU32::new(tag),
inner
};

# println!("running the callback on {:p}", boxed.as_ref());
cb(boxed.as_mut() as *mut CXtraResource);
# println!("running the callback on {:p}", &wrapped);
cb(&mut wrapped as *mut CXtraResource);

if boxed.id == id && (boxed.tag == VALID_TAG || boxed.tag == ERR_TAG) {
# println!("freeing {:p}", boxed.as_ref());
boxed.tag = INVALID_TAG; // prevent accidental reuse
// implicit boxed drop
// prevent accidental reuse
let new_tag = wrapped.tag.swap(INVALID_TAG, Ordering::SeqCst);
if new_tag == VALID_TAG || new_tag == ERR_TAG {
# println!("freeing {:p}", &wrapped);
// implicit drop of wrappped
} else {
# println!("forgetting {:p}", boxed.as_ref());
# println!("forgetting {:p}", &wrapped);
// (...) error handling (should be fatal)
boxed.tag = INVALID_TAG; // prevent reuse
std::mem::forget(boxed); // boxed is corrupted it should not be
std::mem::forget(wrapped);
}
}

#[no_mangle]
pub unsafe extern "C" fn xtra_dosthg(cxtra: *mut CXtraResource) {
let do_it = || {
pub unsafe extern "C" fn xtra_dosthg(cxtra: *mut CXtraResource, arg: u32) {
let do_it = move || {
if let Some(cxtra) = cxtra.as_mut() {
if cxtra.tag == VALID_TAG {
if cxtra.tag.load(Ordering::SeqCst) == VALID_TAG {
# println!("doing something with {:p}", cxtra);
cxtra.inner.dosthg();
cxtra.inner.dosthg(arg);
return;
}
}
Expand All @@ -752,25 +752,40 @@ pub mod c_api {
if catch_unwind(do_it).is_err() {
if let Some(cxtra) = cxtra.as_mut() {
# println!("panicking with {:p}", cxtra);
cxtra.tag = ERR_TAG;
cxtra.tag.store(ERR_TAG, Ordering::SeqCst);
}
};
}
}
#
# fn main() {}
# fn main() {
# // do not use, only for testing purposes
# use c_api::*;
# unsafe {
# unsafe extern "C" fn cb_ok(p: *mut CXtraResource) {
# xtra_dosthg(p, 0);
# }
# unsafe extern "C" fn cb_panic(p: *mut CXtraResource) {
# xtra_dosthg(p, 0xDEADC0DE);
# }
# xtra_with(cb_ok);
# xtra_with(cb_panic);
# }
# }
```

A compatible C call:

```c
#include <stdint.h>

struct XtraResource;
void xtra_with(void (*cb)(XtraResource* xtra));
void xtra_with(void (*cb)(XtraResource* xtra), uint32_t arg);
void xtra_sthg(XtraResource* xtra);

void cb(XtraResource* xtra) {
// ()...) do anything with the proposed C API for XtraResource
xtra_sthg(xtra);
xtra_sthg(xtra, 0);
}

int main() {
Expand Down Expand Up @@ -834,7 +849,7 @@ trick: the linker fails if a non-trivially-dead branch leads to `panic!`.
> Interfacing a library written in another language in Rust should be done in
> two parts:
>
> - a low-level, possibly *hidden*, module that closely translates the original
> - a low-level, possibly _hidden_, module that closely translates the original
> C API into `extern` blocks,
> - a safe wrapping module that ensures memory safety and security invariants at
> the Rust level.
Expand Down
Loading