-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
348: Add a Read-Only Allow API to `libtock_platform`. r=jrvanwhy a=jrvanwhy Co-authored-by: Johnathan Van Why <[email protected]>
- Loading branch information
Showing
7 changed files
with
306 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use crate::share::List; | ||
use crate::Syscalls; | ||
use core::marker::PhantomData; | ||
|
||
// ----------------------------------------------------------------------------- | ||
// `AllowRo` struct | ||
// ----------------------------------------------------------------------------- | ||
|
||
/// A `share::Handle<AllowRo>` instance allows safe code to call Tock's | ||
/// Read-Only Allow system call, by guaranteeing the buffer will be revoked | ||
/// before 'share ends. It is intended for use with the `share::scope` function, | ||
/// which offers a safe interface for constructing `share::Handle<AllowRo>` | ||
/// instances. | ||
pub struct AllowRo<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> { | ||
_syscalls: PhantomData<S>, | ||
|
||
// Make this struct invariant with respect to the 'share lifetime. | ||
// | ||
// If AllowRo were covariant with respect to 'share, then an | ||
// `AllowRo<'static, ...>` could be used to share a buffer that has a | ||
// shorter lifetime. The capsule would still have access to the memory after | ||
// the buffer is deallocated and the memory re-used (e.g. if the buffer is | ||
// on the stack), likely leaking data the process binary does not want to | ||
// share. Therefore, AllowRo cannot be covariant with respect to 'share. | ||
// Contravariance would not have this issue, but would still be confusing | ||
// and would be unexpected. | ||
// | ||
// Additionally, this makes AllowRo !Sync, which is probably desirable, as | ||
// Sync would allow for races between threads sharing buffers with the | ||
// kernel. | ||
_share: PhantomData<core::cell::Cell<&'share [u8]>>, | ||
} | ||
|
||
// We can't derive(Default) because S is not Default, and derive(Default) | ||
// generates a Default implementation that requires S to be Default. Instead, we | ||
// manually implement Default. | ||
impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> Default | ||
for AllowRo<'share, S, DRIVER_NUM, BUFFER_NUM> | ||
{ | ||
fn default() -> Self { | ||
Self { | ||
_syscalls: PhantomData, | ||
_share: PhantomData, | ||
} | ||
} | ||
} | ||
|
||
impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> Drop | ||
for AllowRo<'share, S, DRIVER_NUM, BUFFER_NUM> | ||
{ | ||
fn drop(&mut self) { | ||
S::unallow_ro(DRIVER_NUM, BUFFER_NUM); | ||
} | ||
} | ||
|
||
impl<'share, S: Syscalls, const DRIVER_NUM: u32, const BUFFER_NUM: u32> List | ||
for AllowRo<'share, S, DRIVER_NUM, BUFFER_NUM> | ||
{ | ||
} | ||
|
||
// ----------------------------------------------------------------------------- | ||
// `Config` trait | ||
// ----------------------------------------------------------------------------- | ||
|
||
/// `Config` configures the behavior of the Read-Only Allow system call. It | ||
/// should generally be passed through by drivers, to allow application code to | ||
/// configure error handling. | ||
pub trait Config { | ||
/// Called if a Read-Only Allow call succeeds and returns a non-zero buffer. | ||
/// In some applications, this may indicate unexpected reentrance. By | ||
/// default, the non-zero buffer is ignored. | ||
fn returned_nonzero_buffer(_driver_num: u32, _buffer_num: u32) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use libtock_platform::{allow_ro, share, CommandReturn, ErrorCode, Syscalls}; | ||
use libtock_unittest::{command_return, fake, RoAllowBuffer, SyscallLogEntry}; | ||
use std::cell::Cell; | ||
use std::rc::Rc; | ||
use std::thread_local; | ||
|
||
#[derive(Default)] | ||
struct TestDriver { | ||
buffer_0: Cell<RoAllowBuffer>, | ||
} | ||
|
||
impl fake::Driver for TestDriver { | ||
fn id(&self) -> u32 { | ||
42 | ||
} | ||
|
||
fn num_upcalls(&self) -> u32 { | ||
0 | ||
} | ||
|
||
fn command(&self, _command_num: u32, _argument0: u32, _argument1: u32) -> CommandReturn { | ||
command_return::failure(ErrorCode::NoSupport) | ||
} | ||
|
||
fn allow_readonly( | ||
&self, | ||
buffer_number: u32, | ||
buffer: RoAllowBuffer, | ||
) -> Result<RoAllowBuffer, (RoAllowBuffer, ErrorCode)> { | ||
if buffer_number != 0 { | ||
return Err((buffer, ErrorCode::NoSupport)); | ||
} | ||
Ok(self.buffer_0.replace(buffer)) | ||
} | ||
} | ||
|
||
struct TestConfig; | ||
|
||
// CALLED is set to true when returned_nonzero_buffer is called. | ||
thread_local! {static CALLED: Cell<bool> = Cell::new(false); } | ||
|
||
impl allow_ro::Config for TestConfig { | ||
fn returned_nonzero_buffer(driver_num: u32, buffer_num: u32) { | ||
assert_eq!(driver_num, 42); | ||
assert_eq!(buffer_num, 0); | ||
CALLED.with(|cell| cell.set(true)); | ||
} | ||
} | ||
|
||
#[test] | ||
fn allow_ro() { | ||
let kernel = fake::Kernel::new(); | ||
let driver = Rc::new(TestDriver::default()); | ||
kernel.add_driver(&driver); | ||
let buffer1 = [1, 2, 3, 4]; | ||
let buffer2 = [5, 6]; | ||
share::scope(|allow_ro| { | ||
// Tests a call that should fail because it has an incorrect buffer | ||
// number. | ||
let result = fake::Syscalls::allow_ro::<TestConfig, 42, 1>(allow_ro, &buffer1); | ||
assert!(!CALLED.with(|c| c.get())); | ||
assert_eq!(result, Err(ErrorCode::NoSupport)); | ||
assert_eq!( | ||
kernel.take_syscall_log(), | ||
[SyscallLogEntry::AllowRo { | ||
driver_number: 42, | ||
buffer_number: 1, | ||
len: 4, | ||
}] | ||
); | ||
}); | ||
|
||
// Verify that share::scope unallowed the buffer. | ||
assert_eq!( | ||
kernel.take_syscall_log(), | ||
[SyscallLogEntry::AllowRo { | ||
driver_number: 42, | ||
buffer_number: 1, | ||
len: 0, | ||
}] | ||
); | ||
|
||
share::scope(|allow_ro| { | ||
// Tests a call that should succeed and return a zero buffer. | ||
let result = fake::Syscalls::allow_ro::<TestConfig, 42, 0>(allow_ro, &buffer1); | ||
assert!(!CALLED.with(|c| c.get())); | ||
assert_eq!(result, Ok(())); | ||
assert_eq!( | ||
kernel.take_syscall_log(), | ||
[SyscallLogEntry::AllowRo { | ||
driver_number: 42, | ||
buffer_number: 0, | ||
len: 4, | ||
}] | ||
); | ||
|
||
// Tests a call that should succeed and return a nonzero buffer. | ||
let result = fake::Syscalls::allow_ro::<TestConfig, 42, 0>(allow_ro, &buffer2); | ||
assert!(CALLED.with(|c| c.get())); | ||
assert_eq!(result, Ok(())); | ||
assert_eq!( | ||
kernel.take_syscall_log(), | ||
[SyscallLogEntry::AllowRo { | ||
driver_number: 42, | ||
buffer_number: 0, | ||
len: 2, | ||
}] | ||
); | ||
}); | ||
|
||
// Verify that share::scope unallowed the buffer, but only once. | ||
assert_eq!( | ||
kernel.take_syscall_log(), | ||
[SyscallLogEntry::AllowRo { | ||
driver_number: 42, | ||
buffer_number: 0, | ||
len: 0, | ||
}] | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters