kernel/arch-riscv: fix unsound usize->u32/u64 syscall register reinterpretation - #5057
kernel/arch-riscv: fix unsound usize->u32/u64 syscall register reinterpretation#5057ppannuto wants to merge 1 commit into
Conversation
|
This is the same rust-unsafe-is-broken-in-macros issue isn't it. Needs #allow(unsafe) so this goes in an unsafe block. Ugh. |
|
My protest is the macro seems to have nothing to do with this, ie, if I add fn get_dangling2() -> &'static mut u64 {
let mut x: usize = 5;
let r: &mut usize = &mut x;
let ptr = core::ptr::from_mut::<usize>(r);
let out: &mut u64 = unsafe { &mut *ptr.cast::<u64>() };
out
}
fn main() {
let r = get_dangling();
*r = 0xdeadbeef;
println!("{}", r); // prints the freed stack slot's leftover value
let s = get_dangling2();
*s = 0xdeadbefa;
println!("{}", s);
}to the playground link the output is still Maybe the function is a good fix, maybe zerocopy is a good fix, maybe leaving it alone is a good fix, maybe making the macro unsafe is a good fix. |
|
The problem with your example there (and any macro approach in general) is that this line: let out: &mut u64 = unsafe { &mut *ptr.cast::<u64>() };is located in the fn usize_as_native_mut_fn(val: &mut usize) -> &mut u64 { ...works is because now you have to pass the thing you wanted to convert into a function that consumes it mutably. Don't get caught up in where the Rust lifetime rules around blocks (i.e., braced chunks of code in a function) are not the same as the rules around function boundaries. You need the function boundary for the lifetime tracking to do what you want w.r.t. the unsafe reference that the conversion creates. |
|
What do you mean by the problem with my example? That is the code we already have. Adding a macro isn't making this any worse. And the exact same unsafe requirements that exist for the underlying Rust code can be added to the macro, because it is the same code. Function vs. block boundaries is orthogonal. The function might make for a better conversion tool, I'm not arguing that. But we can always leave the code exactly the way it is, which could exhibit this behavior. |
|
True, making is a macro doesn't make it any worse, it just doesn't fix the problem either. Your original PR did identify a real soundness issue in our current codebase, i.e. [-bash] Thu 13 Aug 09:59 [[master $] ~/code/helena-project/tock]
$ git diff
diff --git a/arch/riscv/src/syscall.rs b/arch/riscv/src/syscall.rs
index fe10dff71..097cb9a77 100644
--- a/arch/riscv/src/syscall.rs
+++ b/arch/riscv/src/syscall.rs
@@ -137,6 +137,8 @@ fn encode_syscall_return_helper(
(a0_u32, a1_u32, a2_u32, a3_u32)
};
+ *a0 = 1;
+
kernel::utilities::arch_helpers::encode_syscall_return_trd104(
&kernel::utilities::arch_helpers::TRD104SyscallReturn::from_syscall_return(return_value),
a0_u32,
$ # This shouldn't compile, but it does. The safety comment on line 129 is insufficient for the unsafe operation in the block it's documenting
$ make -C boards/qemu_rv32_virt/
Finished `release` profile [optimized + debuginfo] target(s) in 0.17s
text data bss dec hex filename
103980 16 109832 213828 34344 /Users/ppannuto/code/helena-project/tock/target/riscv32imac-unknown-none-elf/release/qemu_rv32_virt
c4177eeafba335662960c5f7898b7992bf614ac8d2ca9a8b1383ea19aec8ef0a /Users/ppannuto/code/helena-project/tock/target/riscv32imac-unknown-none-elf/release/qemu_rv32_virt.binI can update this PR to capture the soundness fixes with these functions and target master. |
…rpretation encode_syscall_return_helper/encode_upcall_helper reinterpreted &mut usize register slots as &mut u32/u64 via a raw-pointer cast inline in the function body. The conjured reference has no lifetime tied back to the original usize reference, so the compiler still allowed mutating/aliasing the original after the cast -- confirmed by inserting `*a0 = 1;` right after the conversion, which compiled cleanly. Extract the cast into kernel::utilities::helpers::usize_as_native_mut, a plain function. A function's lifetime elision ties the output reference to the input at every call site, which neither an inline block nor a macro can provide, since neither introduces a real function-call boundary. With this fix, the same `*a0 = 1;` repro fails to compile with E0506. Verified: `make -C boards/qemu_rv32_virt` and `cargo check --target riscv64imac-unknown-none-elf` (kernel, arch/riscv) build clean; the E0506 repro confirmed against both the unpatched and patched code. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
afa61f2 to
4752f30
Compare
|
Superseded by #5075 -- opening fresh since retargeting this PR's base confused GitHub's merge-base tracking. |
Pull Request Overview
Our current pointer casts are unsound. The conjured
&mut u32has no lifetime tied back toa0, so the compiler still allows usinga0again whilea0_u32is alive:This PR extracts the cast into
kernel::utilities::helpers::usize_as_native_mut, a plain function. A function's lifetime elision (fn usize_as_native_mut<'a>(val: &'a mut usize) -> &'a mut u32) ties the output to the input at every call site — neither an inline block nor a macro provides this, since neither introduces a real function-call boundary.With the fix, the
*a0 = 1;above (correctly) fails to compile with a lifetime violation.Testing Strategy
make -C boards/qemu_rv32_virt— full board build, riscv32 path.cargo check --target riscv64imac-unknown-none-elfagainstkernelandarch/riscvdirectly — riscv64 path (no riscv64 board in-tree yet).*a0 = 1;inserted after the conversion compiles on unpatched code, fails withE0506after this fix.TODO or Help Wanted
N/A
Checklist
make prepush.PR Contents
Documentation
AI Use
Prompts used across this PR (review, fix, and this update)
I have manually reviewed the diff.