fix(kernel): KRNBUG-IO-002 — vol-info class-3 returns 0x10000 alloc unit (canary NullDevice)
`nt_query_volume_information_file` class-3 (`FileFsSizeInformation`) was returning sectors_per_unit=1, bytes_per_sector=2048 (alloc unit 2048). Replaced with canary's NullDevice byte-identical values sectors=0x80, bps=0x200 (alloc unit 0x10000), with total / available allocation units lowered to 0x10 / 0x10 to match. Reference: xenia-canary/src/xenia/vfs/devices/null_device.h:38-46 (`NullDevice::sectors_per_allocation_unit()` and `bytes_per_sector()`); consumed by canary's `NtQueryVolumeInformationFile_entry` at xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_io_info.cc:355-365. Tests 591 → 592 (added `nt_query_volume_information_file_class3_returns_64k_alloc_unit`). Lockstep `instructions=100000010, swaps=2, draws=0` deterministic across two `--stable-digest -n 100M` reruns. sylpheed_n50m oracle still matches its existing golden — observably a no-op at -n 50M. The audit-006-predicted 7→0 cascade did NOT fire (canary-only exports still 7, identical set; XexCheckExecutablePrivilege still priv=0xA only; XamTaskSchedule still 0). All 16 NtQueryVolumeInformationFile calls in our 500M trace originate from a single LR 0x82611f38 and complete successfully — vol-info is therefore not the priv-11 gate. The fix value is correct (canary-byte-identical) but is not load-bearing for the gate; landing it anyway because it's the right value and unblocks no regression. Stop condition triggered per the IO-002 task brief — no second fix this session. Next-session: --pc-probe on sub_824A9710 entry to find the actual upstream gate. See `audit-findings.md` (KRNBUG-IO-002 entry) and `audit-runs/post-IO-002/` for the full diagnostic trail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1250,10 +1250,10 @@ fn nt_query_volume_information_file(ctx: &mut PpcContext, mem: &GuestMemory, _st
|
||||
// SectorsPerAllocationUnit(u32), BytesPerSector(u32)
|
||||
let written: u32 = match class {
|
||||
3 if length >= 24 => {
|
||||
mem.write_u64(info, 0x10_0000); // ~2GB at 2KB sectors
|
||||
mem.write_u64(info + 8, 0);
|
||||
mem.write_u32(info + 16, 1);
|
||||
mem.write_u32(info + 20, 2048);
|
||||
mem.write_u64(info, 0x10);
|
||||
mem.write_u64(info + 8, 0x10);
|
||||
mem.write_u32(info + 16, 0x80);
|
||||
mem.write_u32(info + 20, 0x200);
|
||||
24
|
||||
}
|
||||
_ => {
|
||||
@@ -4331,6 +4331,37 @@ mod tests {
|
||||
assert_eq!(mem.read_u8(info_buf + 21), 0, "normal file not directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nt_query_volume_information_file_class3_returns_64k_alloc_unit() {
|
||||
let (mut ctx, mut mem, mut state) = fresh();
|
||||
let h = state.alloc_handle_for(KernelObject::File {
|
||||
path: String::new(),
|
||||
size: 0,
|
||||
position: 0,
|
||||
data: std::sync::Arc::new(Vec::new()),
|
||||
dir_enum_pos: None,
|
||||
});
|
||||
let iosb = SCRATCH_BASE;
|
||||
let info_buf = SCRATCH_BASE + 0x100;
|
||||
ctx.gpr[3] = h as u64;
|
||||
ctx.gpr[4] = iosb as u64;
|
||||
ctx.gpr[5] = info_buf as u64;
|
||||
ctx.gpr[6] = 24;
|
||||
ctx.gpr[7] = 3; // FileFsSizeInformation
|
||||
nt_query_volume_information_file(&mut ctx, &mut mem, &mut state);
|
||||
|
||||
assert_eq!(ctx.gpr[3], STATUS_SUCCESS as u64);
|
||||
let sectors_per_unit = mem.read_u32(info_buf + 16);
|
||||
let bytes_per_sector = mem.read_u32(info_buf + 20);
|
||||
assert_eq!(sectors_per_unit, 0x80);
|
||||
assert_eq!(bytes_per_sector, 0x200);
|
||||
assert_eq!(
|
||||
sectors_per_unit * bytes_per_sector,
|
||||
0x10000,
|
||||
"alloc unit must be 64 KiB to match canary NullDevice",
|
||||
);
|
||||
}
|
||||
|
||||
// ===== PKEVENT shim =====
|
||||
|
||||
/// Write a DISPATCHER_HEADER at the given guest pointer.
|
||||
|
||||
Reference in New Issue
Block a user