fix(userspace): clip display rects, bounds-check session app ids and fat16 handles, reject bad fat16 geometry, session kills via process cap

This commit is contained in:
2026-10-27 12:11:13 +01:00
parent e8a2963b24
commit 8bc5402a29
7 changed files with 426 additions and 109 deletions

View File

@@ -1,21 +1,25 @@
use crate::adapters::mock_block::MockBlockDevice;
use port_block_device::{BlockDevice, SectorId};
use crate::filesystem::{DirEntry, FileSystem};
use crate::fs::fat16::Fat16;
use port_block_device::{BlockDevice, SectorId};
fn format_fat16_disk(sectors: usize) -> MockBlockDevice {
let mut dev = MockBlockDevice::new(sectors);
let mut boot = [0u8; 512];
// BPB
boot[11] = 0x00; boot[12] = 0x02; // bytes_per_sector = 512
boot[13] = 1; // sectors_per_cluster = 1
boot[14] = 1; boot[15] = 0; // reserved_sectors = 1
boot[16] = 1; // num_fats = 1
boot[17] = 16; boot[18] = 0; // root_entry_count = 16
boot[19] = sectors as u8; // total_sectors
boot[11] = 0x00;
boot[12] = 0x02; // bytes_per_sector = 512
boot[13] = 1; // sectors_per_cluster = 1
boot[14] = 1;
boot[15] = 0; // reserved_sectors = 1
boot[16] = 1; // num_fats = 1
boot[17] = 16;
boot[18] = 0; // root_entry_count = 16
boot[19] = sectors as u8; // total_sectors
boot[20] = (sectors >> 8) as u8;
boot[22] = 1; boot[23] = 0; // sectors_per_fat = 1
boot[22] = 1;
boot[23] = 0; // sectors_per_fat = 1
// boot signature
boot[510] = 0x55;
@@ -110,3 +114,75 @@ fn create_duplicate_returns_none() {
fs.create(b"dup.txt").map(|h| fs.close(h));
assert!(fs.create(b"dup.txt").is_none());
}
// ---- hostile input: handles from clients, geometry and chains from disk ----
fn with_boot_byte(sectors: usize, index: usize, value: u8) -> MockBlockDevice {
let mut dev = format_fat16_disk(sectors);
let mut boot = [0u8; 512];
dev.read_sector(SectorId::new(0), &mut boot);
boot[index] = value;
dev.write_sector(SectorId::new(0), &boot);
dev
}
#[test]
fn mount_rejects_geometry_the_driver_cannot_use() {
// sectors_per_cluster 0 or 3, bytes_per_sector 513 or 0, no FATs, no
// FAT sectors
for (index, value) in [(13, 0), (13, 3), (11, 1), (12, 0), (16, 0), (22, 0)] {
let dev = with_boot_byte(32, index, value);
assert!(
Fat16::mount(dev).is_none(),
"byte {index} = {value} accepted"
);
}
}
#[test]
fn handles_past_the_table_are_rejected_not_indexed() {
use crate::filesystem::FileHandle;
let mut fs = mount_test_disk();
let mut buf = [0u8; 16];
for raw in [8, 9, u16::MAX] {
let h = FileHandle::new(raw);
assert_eq!(fs.read(h, &mut buf), None);
assert_eq!(fs.write(h, b"x"), None);
assert_eq!(fs.file_size(h), None);
fs.close(h);
}
}
/// Points a root-directory entry for `name` at `cluster` with `size`.
fn plant_entry(dev: &mut MockBlockDevice, name: &[u8; 11], cluster: u16, size: u32) {
// root dir starts after 1 reserved + 1 FAT sector
let mut dir = [0u8; 512];
dir[..11].copy_from_slice(name);
dir[11] = 0x20;
dir[26..28].copy_from_slice(&cluster.to_le_bytes());
dir[28..32].copy_from_slice(&size.to_le_bytes());
dev.write_sector(SectorId::new(2), &dir);
}
#[test]
fn a_file_pointing_at_cluster_zero_reads_nothing() {
let mut dev = format_fat16_disk(32);
plant_entry(&mut dev, b"BAD BIN", 0, 4096);
let mut fs = Fat16::mount(dev).unwrap();
let h = fs.open(b"bad.bin").expect("open");
let mut buf = [0u8; 64];
assert_eq!(fs.read(h, &mut buf), Some(0));
}
#[test]
fn deleting_a_file_with_a_cyclic_chain_terminates() {
let mut dev = format_fat16_disk(32);
// cluster 2 -> 3 -> 2 -> ...
let mut fat = [0u8; 512];
fat[4..6].copy_from_slice(&3u16.to_le_bytes());
fat[6..8].copy_from_slice(&2u16.to_le_bytes());
dev.write_sector(SectorId::new(1), &fat);
plant_entry(&mut dev, b"LOOP BIN", 2, 4096);
let mut fs = Fat16::mount(dev).unwrap();
assert!(fs.delete(b"loop.bin"));
}