feat: kernel W^X (per-section perms, firmware unmapped, stack guard, boot check), halt on kernel faults, region W^X across processes
This commit is contained in:
@@ -385,6 +385,18 @@ impl<P: Platform> Kernel<P> {
|
||||
if region.mappings().any(|m| m.process == pid) {
|
||||
return Err(KernelError::AddressInUse);
|
||||
}
|
||||
// W^X holds across processes too: nobody may write what someone
|
||||
// else can execute.
|
||||
let clashes = |other: PagePerms| {
|
||||
matches!(
|
||||
(perms, other),
|
||||
(PagePerms::ReadWrite, PagePerms::ReadExecute)
|
||||
| (PagePerms::ReadExecute, PagePerms::ReadWrite)
|
||||
)
|
||||
};
|
||||
if region.mappings().any(|m| clashes(m.perms)) {
|
||||
return Err(KernelError::WritableAndExecutable);
|
||||
}
|
||||
if !Self::in_user_window(va, pages) {
|
||||
return Err(KernelError::InvalidAddress);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ pub fn error_code(error: &KernelError) -> ErrorCode {
|
||||
InvalidSlot(_) | RevokedCapability(_) | NoSuchObject | InvalidEndpoint(_) => {
|
||||
ErrorCode::InvalidCap
|
||||
}
|
||||
PermissionDenied { .. } | ProcessCreateDenied => ErrorCode::PermissionDenied,
|
||||
PermissionDenied { .. } | ProcessCreateDenied | WritableAndExecutable => {
|
||||
ErrorCode::PermissionDenied
|
||||
}
|
||||
WrongObject => ErrorCode::WrongObject,
|
||||
NoFreeSlot => ErrorCode::NoFreeSlot,
|
||||
OutOfMemory => ErrorCode::OutOfMemory,
|
||||
|
||||
@@ -355,6 +355,43 @@ fn a_region_can_be_remapped_after_its_mapping_was_revoked() {
|
||||
assert!(map(&mut t, tb, again, 0x50000, PagePerms::ReadWrite));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_region_is_never_writable_in_one_process_and_executable_in_another() {
|
||||
let mut t = Tree::new();
|
||||
let (b, ta, tb, a_to_b) = (t.b, t.ta, t.tb, t.a_to_b);
|
||||
let r = region(&mut t, ta, 1);
|
||||
let in_b = t.give(ta, r, a_to_b, b);
|
||||
assert!(map(&mut t, ta, r, 0x30000, PagePerms::ReadWrite));
|
||||
assert!(!map(&mut t, tb, in_b, 0x50000, PagePerms::ReadExecute));
|
||||
assert!(t.k.platform_mut().mapping(b, 0x50000).is_none());
|
||||
// Read-only alongside either is fine.
|
||||
assert!(map(&mut t, tb, in_b, 0x50000, PagePerms::ReadOnly));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_can_be_written_then_made_executable_once_the_writer_unmaps() {
|
||||
let mut t = Tree::new();
|
||||
let (b, ta, tb, a_to_b) = (t.b, t.ta, t.tb, t.a_to_b);
|
||||
let r = region(&mut t, ta, 1);
|
||||
let writer = slot_of(t.run(
|
||||
ta,
|
||||
Syscall::Mint {
|
||||
slot: r,
|
||||
badge: Badge::NONE,
|
||||
permissions: all(),
|
||||
},
|
||||
));
|
||||
assert!(map(&mut t, ta, writer, 0x30000, PagePerms::ReadWrite));
|
||||
let in_b = t.give(ta, r, a_to_b, b);
|
||||
assert!(!map(&mut t, tb, in_b, 0x50000, PagePerms::ReadExecute));
|
||||
assert!(ok(&t.run(ta, Syscall::DropCap { slot: writer })));
|
||||
assert!(map(&mut t, tb, in_b, 0x50000, PagePerms::ReadExecute));
|
||||
assert!(
|
||||
!map(&mut t, ta, r, 0x30000, PagePerms::ReadWrite),
|
||||
"and nobody may write it while it is executable"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_region_is_charged_to_the_caller() {
|
||||
let mut t = Tree::new();
|
||||
|
||||
@@ -15,6 +15,7 @@ use platform::Platform;
|
||||
use types::address::DataRef;
|
||||
use types::bounded::CapSlot;
|
||||
use types::id::ThreadId;
|
||||
use types::mapping::PagePerms;
|
||||
use types::syscall_abi::*;
|
||||
|
||||
impl Kernel<MockPlatform> {
|
||||
@@ -96,6 +97,10 @@ impl Kernel<MockPlatform> {
|
||||
}
|
||||
}
|
||||
for (_, region) in self.regions.iter() {
|
||||
let has = |want| region.mappings().any(|m| m.perms == want);
|
||||
if has(PagePerms::ReadWrite) && has(PagePerms::ReadExecute) {
|
||||
return Err(String::from("region mapped both writable and executable"));
|
||||
}
|
||||
for m in region.mappings() {
|
||||
if let Some(via) = m.via
|
||||
&& (!self.resolves(via) || via.process != m.process)
|
||||
@@ -308,6 +313,19 @@ fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadI
|
||||
}
|
||||
}
|
||||
|
||||
/// The slots of every region capability `pid` holds.
|
||||
fn region_slots(k: &Kernel<MockPlatform>, pid: Option<ProcessId>) -> std::vec::Vec<CapSlot> {
|
||||
let Some(process) = pid.and_then(|p| k.processes.get(p).ok()) else {
|
||||
return std::vec::Vec::new();
|
||||
};
|
||||
process
|
||||
.capabilities()
|
||||
.iter()
|
||||
.filter(|(_, c)| matches!(c.resource(), domain::resource::Resource::Memory(_)))
|
||||
.map(|(s, _)| s)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_kernel_survives_a_syscall_storm() {
|
||||
let mut total = [0usize; 36 + 1];
|
||||
@@ -357,7 +375,20 @@ fn the_kernel_survives_a_syscall_storm() {
|
||||
if is_init && (nr == SYS_EXIT || nr == SYS_EXIT_THREAD) {
|
||||
continue;
|
||||
}
|
||||
let args: [usize; 6] = core::array::from_fn(|_| rng.arg());
|
||||
let mut args: [usize; 6] = core::array::from_fn(|_| rng.arg());
|
||||
// Random arguments almost never name a region cap, a free
|
||||
// address and valid perms at once; half the time, pick one of
|
||||
// the caller's region caps (often the hub's shared region) so
|
||||
// regions really are mapped into several processes.
|
||||
if nr == SYS_MAP_REGION && rng.below(2) == 0 {
|
||||
let regions = region_slots(&k, k.thread_process(tid));
|
||||
if !regions.is_empty() {
|
||||
args[0] = regions[rng.below(regions.len())].value() as usize;
|
||||
args[1] = 0x10000 + rng.below(32) * 4096;
|
||||
// Read-write or read-execute: the pairs W^X forbids.
|
||||
args[2] = [PERMS_READ_WRITE, PERMS_READ_EXECUTE][rng.below(2)];
|
||||
}
|
||||
}
|
||||
// init keeps the capability it creates processes with.
|
||||
if is_init && nr == SYS_DROP_CAP && args[0] == 0 {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user