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:
MechaCat02
2026-05-04 21:01:25 +02:00
parent 556a8c387a
commit 7675035082
2 changed files with 115 additions and 4 deletions

View File

@@ -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.