`process_inner.mem_heap_size + delta` can wrap on RV32 if a
client requests a `delta` close to `usize::MAX`. A wrapped
value can land below `mem_heap_max` and pass the cap check,
letting the call proceed with a bogus heap size.
Use `checked_add` and return `Error::OutOfMemory` on overflow.
Adds a hosted-mode behavioral test that sends `usize::MAX` as
`delta` and asserts the syscall rejects it. The test doesn't
cleanly distinguish patched from unpatched code on 64-bit hosted
(both paths reject absurd deltas, just at different layers), but
it locks in the expected behavior as regression coverage.
Signed-off-by: tunnell <tunnell@users.noreply.github.com>
Assisted-by: Claude Code (Opus 4.7)
this is gated behind the feature "debug-proc" because this could
leak attacker-useful information, and also it adds a primitive that
causes the kernel to write the debug information into any page
that the userspace already has access to. There are some checks
to ensure that the page is actually in RAM (so you can't use this
to trigger weird peripheral states, etc.) so hypothetically this
should just cause corruption in the userspace process if it is
abused, but still - more attack surface, more problems.
however, the feature is useful enough and the risk is small enough
that I think I'm going to leave it enabled for the foreseeable future.
It's definitely helpful to be able to see how much free memory
you have, and other resource allocation trackers like connection
limits and interrupt handler allocations.
Based on this benchmark of elapsed_ms():
```rust
pub fn elapsed_ms(&self) -> u64 {
let response = send_message(
self.conn,
xous::Message::new_blocking_scalar(api::Opcode::ElapsedMs.to_usize().unwrap(), 0, 0, 0, 0),
)
.expect("Ticktimer: failure to send message to Ticktimer");
if let xous::Result::Scalar2(upper, lower) = response {
upper as u64 | ((lower as u64) << 32)
} else {
panic!("Ticktimer elapsed_ms(): unexpected return value.");
}
}
```
We find the following context switches:
1. send_message -> kernel/src/syscall.rs @ 166 - ss.remember_server_message()
- *context switch round trip* to enqueue the message into the target thread's space, then switch back.
- takes 546ns just in the target context, out of 4430ns round-trip
(measured as leave userspace of caller to enter userspace of callee) (12% of total time)
- emplaces the message in the target queue
- generates "sender_idx" which is the index in the message queue to find the message, which is put into Envelope
2. send_message -> kernel/src/syscall.rs @ 222 - ss.activate_process_thread()
- activates the thread to run
- Total penalty is closer to 1us - 22% of total time - once pre/amble/post-amble overhead considered
This set of patches removes the context-switch round trip to emplace messages in the target's thread
space, as well as to set thread results, by passing them as arguments to `activate_process_thread`.
What then happens is that these items are emplaced after the activation privot to the target
process space occurs, saving a set of TLB-flushes and page table walks.
More testing is required, but at least on a reasonable set of cases the system seems to be stable
with these patcehs, and we get a 24% speedup on blocking scalar messages and 8% speedup on
mutable lends in the case that the target is waiting to receive messages.
in practice the hard-OOM only seems to happen, and oom-doom
takes up precious memory that could be used for preventing
OOMs. Just leave one path in, but make it a bit smarter.
The smarts are still in userland, so the kernel is minimal
dependencies.
The Hard OOM re-entrant syscall can actually originate in
any process space, but only from inside the kernel. Modify the
checks so that the PID==2 check is applied only to all the other
calls; and that a supervisor mode check is applied to the hard OOM
call.
turns out that
- hard OOM has a clearly defined threshold: 0. we shouldn't
make it higher, because then it forces us to count free memory
on every allocation.
- it's better to have the tuning in userspace (I think) for
OOM, because that's where all the algorithms are
some thought needs to go into when and how we call oom_doom
but let's test more core functionality of the swapper before we
worry about that. I can think of several places where we could
use that call, the main tricky bit is that oom_doom diverges
and so wherever we patch it has to be able to resume. If we put
it into e.g. somewhere in the main memory alloc loop, the trouble
is we would have to track all of the multiple places that we
could have arrived there...ideally we can find a single spot
to patch this, with a clean entry and exit path.
instead of sending scalar messages on every page alloc/free, which
is a lot of traffic, introduce an API where the swapper can request
a snapshot of the alloc status by mapping the kernel's alloc
table as a read-only structure into the swapper's memory space,
and then invoking the swapper in its interrupt context to read that out.
The structure is unmapped on return from the context.
additionally, a running tally of free space is now maintained
and this can be queried with a fast syscall.
Thus, the normal workflow is the swapper would have a timer that
periodically queries the kernel's free space level, and when it
falls below a critical amount, it will invoke the expensive snapshot.
this can also be extended so that the kernel can call into
the swapper's interrupt context with a "help, OOM imminent"
message down the road.
The unmap syscall is a little bit tricky, because it can de-allocate
several pages at once, which can trigger an advisory de-allocate
message to the swapper in the middle of the loop. The entire structure
has to be re-written to handle this case, so we patch this right at
the top level syscall point and handle it entirely with custom
logic in the swapper.
With Rust 1.76.0, we no longer support building with `#![no_std]` or
`panic = "abort"`, both of which are required within the kernel. As a
workaround, go back to building the kernel under
`riscv32imac-unknown-none-elf`.
Signed-off-by: Sean Cross <sean@xobs.io>
Fix memory zeroing when requesting a physical pointer. Previously, only
the first 25% of a page was zeroed due to a bug when casting between
*mut u8 and *mut usize.
This now uses `write_volatile` to ensure the memory clear isn't
optimized out, and uses `usize` pointers everywhere.
Signed-off-by: Sean Cross <sean@xobs.io>
Even when the print-debug feature isn't turned on, println! macro is still able to output some critical non-debug info to the console such as panics, process crashes and violations, etc. Debugging messages are printed with klog! macro that's disabled when debug-print feature is not set.
The previous patch didn't properly restore thread IDs when switching
contexts -- there were spots where we missed things, and spots where we
were double-borrowing.
Fix these corner cases.
Signed-off-by: Sean Cross <sean@xobs.io>
Note the current thread in the original process when its quantum
expires. This will allow us to clean up after ourselves when a context
switch occurs, and will help to prevent thread starvation.
Signed-off-by: Sean Cross <sean@xobs.io>