store: refuse a store that cannot be read — and the proof that the refusal is not the whole defect
The guard is `store_readable()`: four bytes at offset zero, asked before either walk op answers anything. Proven against the reproduction, which is the reproduction from the record — `cube_store=/dev/null cubelinux.enum=1`: cubelinux: cube_store= resolved to /dev/null (the token was honoured) cubelinux: the store is not readable; refusing to answer (the kernel refuses) and the client is *still* handed `spaces returned 0, len=0, cursor=1`, so it copies an unfilled space and walks 199,000 invented records. That is the whole defect in one transcript: the driver refuses and the syscall boundary reports success anyway. So this commit fixes and proves half of it. What remains is `cube_syscall.c`'s CUBE_OP_SPACES arm, where the driver's -EINVAL becomes a 0 with a cursor advanced by one — either its `ret < 0` test is not on the path the client takes, or the answer is overwritten before the copy-out. Nothing above or below that needs touching. Three earlier guesses are recorded as wrong rather than deleted: the packed fallback and the magic guard written for it are never reached (probed, proven), and `read_exact_at` already rejects a short read. The guard here is the first one that fires. The device-path print in cube_syscall.c stays on purpose: it is what turned four builds of inference into one line of fact. Built as #93, deliberately NOT installed — installing it alone would leave the walk still looping. The machine runs #87.
This commit is contained in:
@@ -95,6 +95,7 @@ static char store_device_path[256] = "/dev/vda";
|
|||||||
static int __init cube_store_setup(char *str)
|
static int __init cube_store_setup(char *str)
|
||||||
{
|
{
|
||||||
strscpy(store_device_path, str, sizeof(store_device_path));
|
strscpy(store_device_path, str, sizeof(store_device_path));
|
||||||
|
pr_info("cubelinux: cube_store= resolved to %s\n", store_device_path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
__setup("cube_store=", cube_store_setup);
|
__setup("cube_store=", cube_store_setup);
|
||||||
|
|||||||
@@ -4408,6 +4408,10 @@ pub unsafe extern "C" fn cubelinux_kernel_enum(
|
|||||||
out_len: *mut u64,
|
out_len: *mut u64,
|
||||||
out_cursor: *mut u64,
|
out_cursor: *mut u64,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
|
if let Err(e) = store_readable() {
|
||||||
|
pr_warn!("cubelinux: the store is not readable; refusing to answer\n");
|
||||||
|
return -(e.to_errno() as i32);
|
||||||
|
}
|
||||||
let mut wanted = [0u8; SPACE_ID_LEN];
|
let mut wanted = [0u8; SPACE_ID_LEN];
|
||||||
// SAFETY: the caller guarantees 32 readable bytes at `space`.
|
// SAFETY: the caller guarantees 32 readable bytes at `space`.
|
||||||
unsafe { core::ptr::copy_nonoverlapping(space, wanted.as_mut_ptr(), SPACE_ID_LEN) };
|
unsafe { core::ptr::copy_nonoverlapping(space, wanted.as_mut_ptr(), SPACE_ID_LEN) };
|
||||||
@@ -4690,8 +4694,34 @@ pub unsafe extern "C" fn cubelinux_kernel_flag_scan(
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `space_out` must point to 32 writable bytes.
|
/// `space_out` must point to 32 writable bytes.
|
||||||
|
/// Whether the store can be read at all, asked before an operation answers anything.
|
||||||
|
///
|
||||||
|
/// A walk pointed at something that is not a store served invented records — a garbage space, a
|
||||||
|
/// zero-length value, a cursor that advanced on its own, forever — and a boot that hung instead of
|
||||||
|
/// saying what was wrong. Probing every branch of `Addressed::open()` showed why the earlier guesses
|
||||||
|
/// were wrong: it is entered, the file opens, and execution never gets past the control-block read, so
|
||||||
|
/// neither the packed fallback nor the guard written for it was ever on that path. Whatever answered
|
||||||
|
/// downstream answered over a device that cannot be read.
|
||||||
|
///
|
||||||
|
/// So the question is asked here, where the answer leaves no room: four bytes at offset zero.
|
||||||
|
fn store_readable() -> Result<()> {
|
||||||
|
let file = store_file()?;
|
||||||
|
let mut head = KVVec::<u8>::with_capacity(8, GFP_KERNEL)?;
|
||||||
|
// The scratch is what `read_exact_at` reads *into*, and it asks for `min(left, scratch.len())`
|
||||||
|
// bytes — so a scratch with capacity but no length asks for zero bytes, gets zero, and the
|
||||||
|
// helper correctly reports a short read for every store, good or bad. Length, not capacity.
|
||||||
|
let mut scratch = KVVec::<u8>::with_capacity(4096, GFP_KERNEL)?;
|
||||||
|
scratch.resize(4096, 0, GFP_KERNEL)?;
|
||||||
|
read_exact_at(file, 0, IMAGE_MAGIC.len(), &mut head, &mut scratch)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub unsafe extern "C" fn cubelinux_kernel_spaces(cursor: u64, space_out: *mut u8) -> i32 {
|
pub unsafe extern "C" fn cubelinux_kernel_spaces(cursor: u64, space_out: *mut u8) -> i32 {
|
||||||
|
if let Err(e) = store_readable() {
|
||||||
|
pr_warn!("cubelinux: the store is not readable; refusing to answer\n");
|
||||||
|
return -(e.to_errno() as i32);
|
||||||
|
}
|
||||||
match Addressed::open() {
|
match Addressed::open() {
|
||||||
Ok(Some(image)) => return unsafe { v3_spaces(image, cursor, space_out) },
|
Ok(Some(image)) => return unsafe { v3_spaces(image, cursor, space_out) },
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
|
|||||||
Reference in New Issue
Block a user