#![cfg(windows)]
use std::os::windows::io::OwnedHandle;
use windows_guard_alloc::poison;
use windows_guard_alloc::witness::Witness;
use windows_ioring_sys::contract::RingContract;
use windows_ioring_sys::{
Batch, IoRing, PushOptions, RegisteredBuffers, RegisteredSpan, SharedFile, WriteCaching,
};
const POP_BOUND: std::time::Duration = std::time::Duration::from_secs(10);
#[global_allocator]
static ALLOC: windows_guard_alloc::GuardAlloc = windows_guard_alloc::GuardAlloc::new();
const SLOT_LEN: usize = 8192;
const WAIT_MS: u32 = 30_000;
mod common;
fn temp_file(tag: &str) -> common::TempPath {
common::TempPath::new("kernel-span", tag)
}
fn ordinal_for(slot: u32) -> u64 {
0x5000_0000 + u64::from(slot)
}
fn poison_slot(buffers: &mut RegisteredBuffers<Vec<u8>>, slot: u32, seed: u64) {
let bytes = buffers.get_mut(slot).expect("slot is quiet");
let len = bytes.len();
unsafe { poison::fill(bytes.as_mut_ptr(), len, seed, ordinal_for(slot)) };
}
fn registered_arena<T, X>(
ring: &mut IoRing<T, X>,
count: u32,
seed: u64,
) -> RegisteredBuffers<Vec<u8>> {
let buffers: Vec<Vec<u8>> = (0..count).map(|_| vec![0_u8; SLOT_LEN]).collect();
let mut batch = Batch::new(ring);
let pending = batch
.register_buffers(buffers)
.expect("queue buffer registration");
batch
.submit_and_wait(1, WAIT_MS)
.expect("submit the registration");
let (completion, _held) = ring
.pop_within(POP_BOUND)
.expect("pop the registration completion")
.expect("a completion arrives within the bound");
let mut registered = pending
.claim_if(&completion)
.expect("the pending registration claims its own completion")
.expect("buffer registration succeeded");
for slot in 0..count {
poison_slot(&mut registered, slot, seed);
}
registered
}
fn await_one<T, X>(ring: &mut IoRing<T, X>) -> windows_ioring_sys::HeldCompletion<T, X> {
ring.pop_within(std::time::Duration::from_millis(u64::from(WAIT_MS)))
.expect("pop a completion")
.expect("a completion arrived within the bound")
}
fn open_shared(path: &std::path::Path, write: bool) -> SharedFile {
let file = std::fs::OpenOptions::new()
.read(true)
.write(write)
.open(path)
.expect("open the fixture file");
SharedFile::new(OwnedHandle::from(file))
}
#[test]
fn the_seed_is_announced_so_a_failure_here_can_be_reproduced() {
ALLOC.announce_seed();
assert!(
ALLOC.total_allocations() > 0,
"the guard allocator is not installed for this binary"
);
}
#[test]
fn a_registered_write_leaves_its_source_slot_byte_identical() {
ALLOC.announce_seed();
let seed = ALLOC.seed();
let path = temp_file("write-source");
std::fs::write(&path, vec![0_u8; SLOT_LEN]).expect("create the fixture file");
let file = open_shared(&path, true);
let mut ring = IoRing::new(16, 16).expect("create a ring");
let mut buffers = registered_arena(&mut ring, 1, seed);
let before = buffers.get(0).expect("slot 0 is quiet").to_vec();
let span = RegisteredSpan {
buffer_index: 0,
offset: 0,
len: u32::try_from(SLOT_LEN).expect("slot length fits u32"),
};
let mut batch = Batch::new(&mut ring);
batch
.write_registered_owned(
&file,
&buffers,
span,
(),
0,
PushOptions::new(),
WriteCaching::Cached,
)
.expect("queue a registered write");
batch.submit().expect("submit the write");
let (completion, held) = await_one(&mut ring);
assert!(held.is_some(), "the ring was holding this operation");
let written = completion.result().expect("the write succeeded");
assert_eq!(written, SLOT_LEN, "the whole slot should have been written");
let after = buffers.get(0).expect("slot 0 is quiet again");
assert_eq!(
after,
before.as_slice(),
"a registered write must not modify its source slot; seed {seed:#x}"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn a_registered_read_writes_only_inside_the_span_it_was_given() {
ALLOC.announce_seed();
let seed = ALLOC.seed();
const OFFSET: u32 = 1024;
const LEN: u32 = 2048;
let path = temp_file("read-span");
let payload = vec![0xC7_u8; LEN as usize];
std::fs::write(&path, &payload).expect("create the fixture file");
let file = open_shared(&path, false);
let mut ring = IoRing::new(16, 16).expect("create a ring");
let mut buffers = registered_arena(&mut ring, 1, seed);
let span = RegisteredSpan {
buffer_index: 0,
offset: OFFSET,
len: LEN,
};
let mut batch = Batch::new(&mut ring);
batch
.read_registered_owned(&file, &buffers, span, (), 0, PushOptions::new())
.expect("queue a registered read");
batch.submit().expect("submit the read");
let (completion, held) = await_one(&mut ring);
assert!(held.is_some(), "the ring was holding this operation");
let read = completion.result().expect("the read succeeded");
assert_eq!(read, LEN as usize, "the whole span should have been read");
let slot = buffers.get(0).expect("slot 0 is quiet again");
let ordinal = ordinal_for(0);
assert_eq!(
poison::first_mismatch(seed, ordinal, 0, &slot[..OFFSET as usize]),
None,
"the kernel wrote before the span's offset; seed {seed:#x}"
);
assert_eq!(
&slot[OFFSET as usize..(OFFSET + LEN) as usize],
payload.as_slice(),
"the span should hold the file's contents"
);
let tail_start = (OFFSET + LEN) as usize;
assert_eq!(
poison::first_mismatch(seed, ordinal, tail_start, &slot[tail_start..]),
None,
"the kernel wrote past the span's end; seed {seed:#x}"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn a_short_read_leaves_the_unfilled_remainder_of_the_span_untouched() {
ALLOC.announce_seed();
let seed = ALLOC.seed();
const OFFSET: u32 = 512;
const SPAN_LEN: u32 = 2048;
const FILE_LEN: usize = 512;
let path = temp_file("short-read");
let payload = vec![0x9E_u8; FILE_LEN];
std::fs::write(&path, &payload).expect("create the fixture file");
let file = open_shared(&path, false);
let mut ring = IoRing::new(16, 16).expect("create a ring");
let mut buffers = registered_arena(&mut ring, 1, seed);
let span = RegisteredSpan {
buffer_index: 0,
offset: OFFSET,
len: SPAN_LEN,
};
let mut batch = Batch::new(&mut ring);
batch
.read_registered_owned(&file, &buffers, span, (), 0, PushOptions::new())
.expect("queue a registered read");
batch.submit().expect("submit the read");
let (completion, held) = await_one(&mut ring);
assert!(held.is_some(), "the ring was holding this operation");
let read = completion.result().expect("the read succeeded");
assert_eq!(read, FILE_LEN, "only the file's bytes should have arrived");
let slot = buffers.get(0).expect("slot 0 is quiet again");
let ordinal = ordinal_for(0);
let filled_end = OFFSET as usize + read;
assert_eq!(
&slot[OFFSET as usize..filled_end],
payload.as_slice(),
"the transferred bytes should be the file's"
);
assert_eq!(
poison::first_mismatch(seed, ordinal, filled_end, &slot[filled_end..]),
None,
"the kernel wrote beyond what it transferred; seed {seed:#x}"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn a_read_into_one_slot_leaves_its_neighbours_untouched() {
ALLOC.announce_seed();
let seed = ALLOC.seed();
const TARGET: u32 = 1;
const LEN: u32 = 1024;
let path = temp_file("neighbours");
std::fs::write(&path, vec![0x3B_u8; LEN as usize]).expect("create the fixture file");
let file = open_shared(&path, false);
let mut ring = IoRing::new(16, 16).expect("create a ring");
let mut buffers = registered_arena(&mut ring, 3, seed);
let span = RegisteredSpan {
buffer_index: TARGET,
offset: 0,
len: LEN,
};
let mut batch = Batch::new(&mut ring);
batch
.read_registered_owned(&file, &buffers, span, (), 0, PushOptions::new())
.expect("queue a registered read");
batch.submit().expect("submit the read");
let (completion, held) = await_one(&mut ring);
assert!(held.is_some(), "the ring was holding this operation");
completion.result().expect("the read succeeded");
for slot in [0_u32, 2] {
let bytes = buffers.get(slot).expect("neighbour slot is quiet");
assert_eq!(
poison::first_mismatch(seed, ordinal_for(slot), 0, bytes),
None,
"a read into slot {TARGET} disturbed slot {slot}; seed {seed:#x}"
);
}
let _ = std::fs::remove_file(&path);
}
struct Slot {
witness: Witness,
ever_written_into: bool,
}
#[test]
fn a_mixed_workload_leaves_every_unaccounted_byte_poisoned() {
ALLOC.announce_seed();
let seed = ALLOC.seed();
const SLOTS: u32 = 4;
let read_path = temp_file("mixed-read");
let write_path = temp_file("mixed-write");
let payload = vec![0x6D_u8; 4096];
std::fs::write(&read_path, &payload).expect("create the read fixture");
std::fs::write(&write_path, vec![0_u8; 8192]).expect("create the write fixture");
let read_file = open_shared(&read_path, false);
let write_file = open_shared(&write_path, true);
let mut ring = IoRing::<(), (u32, u32, bool)>::with_inventory(32, 64).expect("create a ring");
let mut buffers = registered_arena(&mut ring, SLOTS, seed);
let mut slots: Vec<Slot> = (0..SLOTS)
.map(|slot| Slot {
witness: Witness::new(ordinal_for(slot), SLOT_LEN),
ever_written_into: false,
})
.collect();
let reads: [(u32, u32, u32); 4] = [
(0, 0, 512), (1, 1024, 2048), (0, 4096, 1024), (2, 7168, 1024), ];
let writes: [(u32, u32, u32); 2] = [(3, 0, 4096), (3, 4096, 2048)];
let mut contract = RingContract::new();
let mut outstanding = 0_usize;
for (slot, offset, len) in reads {
let span = RegisteredSpan {
buffer_index: slot,
offset,
len,
};
let mut batch = Batch::new(&mut ring);
let id = batch
.read_registered_owned(
&read_file,
&buffers,
span,
(slot, offset, true),
0,
PushOptions::new(),
)
.expect("queue a registered read");
batch.submit().expect("submit the read");
contract.observe_push(id.user_data());
outstanding += 1;
}
for (slot, offset, len) in writes {
let span = RegisteredSpan {
buffer_index: slot,
offset,
len,
};
let mut batch = Batch::new(&mut ring);
let id = batch
.write_registered_owned(
&write_file,
&buffers,
span,
(slot, offset, false),
u64::from(offset),
PushOptions::new(),
WriteCaching::Cached,
)
.expect("queue a registered write");
batch.submit().expect("submit the write");
contract.observe_push(id.user_data());
outstanding += 1;
}
while outstanding > 0 {
let (completion, held) = await_one(&mut ring);
contract.observe_completion(completion.user_data());
let transferred = completion.result().expect("the operation succeeded");
let (_payload, (slot, offset, is_read)) =
held.expect("a completion must belong to a pushed operation");
outstanding -= 1;
if is_read {
let entry = &mut slots[slot as usize];
entry.ever_written_into = true;
entry.witness.permit(offset as usize, transferred);
}
}
for slot in 0..SLOTS {
contract.observe_buffer(
slot,
buffers
.outstanding(slot)
.expect("every slot index is in range"),
);
}
contract.assert_quiescent();
for (index, slot) in slots.iter().enumerate() {
let bytes = buffers
.get(index as u32)
.expect("every slot is quiet once all completions are claimed");
if let Err(breach) = slot.witness.verify(seed, bytes) {
panic!(
"slot {index} holds an unaccounted-for write: {breach}; seed {seed:#x}, \
{} byte(s) were legitimately written",
slot.witness.permitted_bytes()
);
}
}
assert_eq!(
slots[3].witness.permitted_bytes(),
0,
"a write-source slot must never have anything permitted"
);
assert!(
!slots[3].ever_written_into,
"slot 3 was used only as a write source"
);
let slot_zero = buffers.get(0).expect("slot 0 is quiet");
assert_eq!(
&slot_zero[..512],
&payload[..512],
"the first read should have landed its bytes"
);
assert!(
slots[0].witness.permitted_bytes() > 0,
"reads must have been accounted for, or the teardown check proves nothing"
);
let _ = std::fs::remove_file(&read_path);
let _ = std::fs::remove_file(&write_path);
}