Files
wanderbooks/crates/ledger-store/src/refuse.rs
T
swaitsandClaude Opus 5.5 d5c9522ad1 P2: tests mutation found, three bugs fixed; plan P12 and P13
Mutation testing pauses until P12 (D-33, owner 2026-10-20), which reruns
the whole max-testing pass over everything since P1. Before the pause,
pass 1 on slices 3-6 and its triage gave tests in ledger-store,
ledger-package and ledger-mcp, and three real bugs, fixed:
- the command line took an admin answer longer than four MiB, or cut off
  before its newline, whenever the bytes that came still parsed;
- The CPA package put a twin's number on the last dot of the whole path,
  so in a folder with a dot the second file landed in another folder;
- a document or category named of dots alone ("..") could climb out of
  its folder when the CPA package is unpacked.
The plan gains P13, bookspec (D-34): a formal, sourced accounting
specification first (rules with symbolic forms and worked examples), then
its own formal validation and a conformance harness any engine can be
tested against, seeded by P3's regression tests. docs/testing.md records
slices 3-8 and what P12 must cover; plan/STATUS.md says where P2 stands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-20 11:14:22 -06:00

94 lines
3.3 KiB
Rust

//! Tests only: system calls refused, as a disk or a file system can
//! refuse them, in one thread of the test's own. A seccomp filter makes
//! each named call fail at once with the error chosen, before the kernel
//! looks at any path, so a refusal no real folder can be made to give
//! (a lookup that fails where making the folder would not; a link
//! refused where the name is already taken) is given every time. The
//! filter holds the calling thread only and ends with it: the rest of
//! the test process is untouched.
#![cfg(target_os = "linux")]
/// Run `f` in a thread of its own in which each call in `calls` fails
/// with `errno`, and give back what it returns; None where the system
/// won't put the filter on (the test then has nothing to show).
pub(crate) fn refusing<T: Send>(
calls: &[libc::c_long],
errno: i32,
f: impl FnOnce() -> T + Send,
) -> Option<T> {
std::thread::scope(|s| {
s.spawn(|| {
// Looked up once before the filter, so std knows statx is
// there and never mistakes a refused one for a missing one.
let _ = std::fs::symlink_metadata("/");
filter(calls, errno).then(f)
})
.join()
.unwrap()
})
}
/// Put the filter on this thread: the call's number checked against
/// each of `calls`, a match refused with `errno`, anything else let be.
fn filter(calls: &[libc::c_long], errno: i32) -> bool {
use libc::{
BPF_ABS, BPF_JEQ, BPF_JMP, BPF_K, BPF_LD, BPF_RET, BPF_W, SECCOMP_RET_ALLOW,
SECCOMP_RET_ERRNO, sock_filter,
};
let stmt = |code: u32, k: u32| sock_filter {
code: code as u16,
jt: 0,
jf: 0,
k,
};
let n = calls.len();
let mut prog = vec![stmt(
BPF_LD | BPF_W | BPF_ABS,
std::mem::offset_of!(libc::seccomp_data, nr) as u32,
)];
for (i, call) in calls.iter().enumerate() {
prog.push(sock_filter {
code: (BPF_JMP | BPF_JEQ | BPF_K) as u16,
// To the refusal, past the other checks and the allow.
jt: u8::try_from(n - i).unwrap(),
jf: 0,
k: u32::try_from(*call).unwrap(),
});
}
prog.push(stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW));
prog.push(stmt(
BPF_RET | BPF_K,
SECCOMP_RET_ERRNO | u32::try_from(errno).unwrap(),
));
let fprog = libc::sock_fprog {
len: u16::try_from(prog.len()).unwrap(),
filter: prog.as_mut_ptr(),
};
// SAFETY: prctl with a filter program that outlives the call (the
// kernel copies it); without TSYNC it binds this thread alone.
unsafe {
libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0
&& libc::prctl(
libc::PR_SET_SECCOMP,
libc::SECCOMP_MODE_FILTER,
&fprog as *const libc::sock_fprog,
) == 0
}
}
/// The calls that look a name up without following it (std's
/// `symlink_metadata`): statx, and what std and libc fall back to.
pub(crate) const LOOKUPS: &[libc::c_long] = &[
libc::SYS_statx,
libc::SYS_newfstatat,
#[cfg(target_arch = "x86_64")]
libc::SYS_lstat,
];
/// The calls that make a hard link (std's `hard_link`).
pub(crate) const LINKS: &[libc::c_long] = &[
libc::SYS_linkat,
#[cfg(target_arch = "x86_64")]
libc::SYS_link,
];