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
10 changes: 2 additions & 8 deletions crates/sandlock-core/src/checkpoint/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,14 +423,8 @@ pub(crate) fn capture(pid: i32, policy: &Sandbox) -> Result<Checkpoint, Sandlock
// while the tell-tale sentinel is still in `a0`. This saves the memory
// dump and surfaces the error at the actionable moment.
#[cfg(target_arch = "riscv64")]
if let Some(sentinel) = super::restore_blob::restart_sentinel_in_a0(&regs) {
return Err(SandlockError::Runtime(SandboxRuntimeError::Child(format!(
"checkpoint captured an interrupted restartable syscall \
(a0 = {sentinel}); riscv64 cannot recover its original argument, \
so restore would resume with a corrupt return value — retry \
while the workload is not blocked in a syscall"
))));
}
super::restore_blob::reject_restart_sentinel(&regs)
.map_err(|e| SandlockError::Runtime(SandboxRuntimeError::Child(e)))?;
// FP state is best-effort: an image without it still restores.
let fpregs = ptrace_getfpregs(pid).unwrap_or_default();
let maps =
Expand Down
4 changes: 4 additions & 0 deletions crates/sandlock-core/src/checkpoint/restore-stub.c
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ struct sigctx {
u8 fpregs[528]; /* sc_fpregs: union __riscv_fp_state (kernel 528 byte union) */
};

/* The sc_fpregs memcpy is bounded only by validate's FP_MAX check. */
_Static_assert(FP_MAX <= sizeof((struct sigctx){0}.fpregs),
"FP_MAX must not exceed sc_fpregs");

struct uctx {
u64 uc_flags; /* 0x00 */
u64 uc_link; /* 0x08 */
Expand Down
27 changes: 17 additions & 10 deletions crates/sandlock-core/src/checkpoint/restore_blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,14 +372,29 @@ fn is_restart_sentinel(v: i64) -> bool {
/// `user_regs_struct`, so neither capture nor restore can recover it. Callers
/// reject the checkpoint rather than resume with a corrupt return value.
#[cfg(target_arch = "riscv64")]
pub(crate) fn restart_sentinel_in_a0(regs: &[u64]) -> Option<i64> {
fn restart_sentinel_in_a0(regs: &[u64]) -> Option<i64> {
// riscv64 user_regs_struct order: pc, ra, sp, gp, tp, t0-t2, s0-s1,
// a0-a7, s2-s11, t3-t6 — so a0 is index 10.
const A0: usize = 10;
let a0 = *regs.get(A0)? as i64;
if is_restart_sentinel(a0) { Some(a0) } else { None }
}

/// Refuse a riscv64 register file whose `a0` holds a restart sentinel, with
/// the shared error text; both capture and restore reject through here.
#[cfg(target_arch = "riscv64")]
pub(crate) fn reject_restart_sentinel(regs: &[u64]) -> Result<(), String> {
match restart_sentinel_in_a0(regs) {
Some(sentinel) => Err(format!(
"checkpoint captured an interrupted restartable syscall \
(a0 = {sentinel}); riscv64 cannot recover its original argument, \
so restore would resume with a corrupt return value; retry the \
checkpoint while the workload is not blocked in a syscall"
)),
None => Ok(()),
}
}

/// Re-arm an interrupted, restartable syscall in the saved register file.
///
/// When the checkpoint was taken (via `PTRACE_INTERRUPT`) while the process sat
Expand Down Expand Up @@ -420,15 +435,7 @@ fn rearm_restartable_syscall(regs: &mut [u64]) -> Result<(), String> {
/// syscall result.
#[cfg(target_arch = "riscv64")]
fn rearm_restartable_syscall(regs: &mut [u64]) -> Result<(), String> {
if let Some(sentinel) = restart_sentinel_in_a0(regs) {
return Err(format!(
"checkpoint captured an interrupted restartable syscall \
(a0 = {sentinel}); riscv64 cannot recover its original argument, \
so restore would resume with a corrupt return value. Retry the \
checkpoint while the workload is not blocked in a syscall"
));
}
Ok(())
reject_restart_sentinel(regs)
}

/// Build the FP image the stub points the signal frame's `fpstate` at.
Expand Down
Loading