From 72e72fe7a807df89bb793bd4bc50be63a9ab11f1 Mon Sep 17 00:00:00 2001 From: surface-camera-build Date: Tue, 22 Sep 2026 01:02:39 -0400 Subject: [PATCH] cubelinux: an addressed image is readable without a log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit merged_digest answered "no log" before it looked at the layout, so a bare v3/v4 image fell through to the packed walk — which starts at the v2 header length and reads index entries as record frames. A bare addressed image with no log beside it therefore read as a one-record store with an empty value. An addressed image is a complete store on its own: its index holds every record and says where each one is, so a log is an addition rather than a requirement. A packed image is not, which is why "no log" still means what it meant for v1/v2. Found by verify-image-read the moment userspace started writing v4: while every userspace image was v2 the fallback happened to be the right walk. The kernel's own v4 images always had a log header beside them, so no other gate could have caught it. --- drivers/cube/cubelinux_store.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/cube/cubelinux_store.rs b/drivers/cube/cubelinux_store.rs index 88507b528..b5fea2867 100644 --- a/drivers/cube/cubelinux_store.rs +++ b/drivers/cube/cubelinux_store.rs @@ -1047,7 +1047,17 @@ fn build_merged(image: &[u8], log: &[u8], header: &Header) -> Result<(Merged, u6 /// to it. Returns `Err("no-log")` when there is none, so the caller can fall back to reading /// the image by itself. fn merged_digest(image: &[u8], log: &[u8], header: &Header) -> Result { - if log.len() < WAL_HEADER_LEN || &log[0..4] != WAL_MAGIC { + // An addressed image is a complete store on its own: its index holds every record and says + // where each one is, so a log is an *addition* rather than a requirement. A packed image is + // not — without a log there is nothing this path adds over the image's own order, which is why + // it answers "no log" and lets the caller walk the records it can see. + // + // Requiring a log here regardless of layout is what made a bare v3/v4 image read as a *packed* + // one: the caller fell through to a walk that starts at the v2 header length and reads index + // entries as record frames. It went unnoticed while every userspace-written image was v2, which + // is exactly the kind of bug that a migration finds and a unit test does not. + let addressed = header.version == VERSION_V3 || header.version == VERSION_V4; + if !addressed && (log.len() < WAL_HEADER_LEN || &log[0..4] != WAL_MAGIC) { return Err("no-log"); } let (mut merged, applied) = build_merged(image, log, header)?;