feat(kernel): KRNBUG-AUDIT-005 — --pc-probe extension + canary diff identifies XexCheckExecutablePrivilege stub cascade
Extends `--ctor-probe` machinery into `--pc-probe` (clap alias) with
the optional `PC@DISPATCHER:OFFSET` token form: on a hit, the helper
additionally logs `[disp+off]` — what the producer's
`lwz r3, OFFSET(r3)` is about to read. Reuses `parse_hex_u32`; both
flags share parser + storage.
Read-only diagnostic. Lockstep digest preserved (`run digest matches
golden` at -n 50M `--stable-digest`). 588 tests green.
Decisive findings (full deliverable in `audit-findings.md` /
`audit-runs/audit-005/`):
- Failure mode α confirmed for KRNBUG-AUDIT-004: all 9 producer call
sites for handles 0x100c (5 sites) and 0x15e0 (4 sites) fire 0x at
-n 500M. The producer code path is not reached.
- Set-diff of kernel-call sequences (canary.log oracle vs ours.log
at -n 500M) identifies 11 exports canary calls and we don't:
XGetAVPack, XeCryptSha, XeKeysConsolePrivateKeySign,
ObCreateSymbolicLink, NtDeviceIoControlFile (×2),
XamUserReadProfileSettings (×2), XamTaskSchedule, XamTaskCloseHandle,
KeReleaseSemaphore (×268), KeResetEvent, ExTerminateThread (×2).
- XGetAVPack has exactly one caller (sub_824AB578 at 0x824AB5A0).
The 4 instructions immediately preceding it are:
addi r3, r0, 10 ; privilege bit 10
bl XexCheckExecutablePrivilege
cmpli 0, r3, 0
bc 12, eq, 0x824AB724 ; if r3==0, skip whole block
- exports.rs:193 registers XexCheckExecutablePrivilege as
stub_return_zero. Always returning 0 -> guest takes the branch
and skips the entire AV/crypto/save-data init block.
- The other call site (sub_824A9710 at 0x824A99A0) queries privilege
11 with opposite polarity (bne) -> gates XamTaskSchedule on the
privilege-NOT-set arm. With both stubs returning 0, the guest
walks the wrong arm of every privilege-gated branch.
- This explains why the dispatcher fields read zero
([0x828F3D08+0x50]=0, [0x828F4070+0x24]=0 from AUDIT-004 dumps):
the ctors run, but the producers that would populate those fields
with a non-zero handle never execute.
Next session: replace XexCheckExecutablePrivilege stub with real
priv-bit lookup from XEX header. See audit-findings.md
KRNBUG-AUDIT-005 for the validation matrix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -196,6 +196,15 @@ pub struct KernelState {
|
||||
/// `this`. Hooking the ctor's PRE-prologue PC captures r3 = this
|
||||
/// before any save/restore can clobber it.
|
||||
pub ctor_probe_pcs: std::collections::HashSet<u32>,
|
||||
/// Diagnostic. Optional per-PC dispatcher snapshot. Maps a probe PC
|
||||
/// to a `(dispatcher_addr, offset)` pair; when the PC fires, the
|
||||
/// helper additionally logs the value of `[dispatcher_addr +
|
||||
/// offset]` — i.e. exactly what the producer's `lwz r3, OFF(r3)`
|
||||
/// is about to read after the `bl outer_getter` returns the
|
||||
/// dispatcher pointer in r3. Populated from the `PC@DISP:OFF`
|
||||
/// extended syntax of `--pc-probe` / `--ctor-probe`. Read-only
|
||||
/// load — does not mutate guest state.
|
||||
pub pc_probe_consumers: HashMap<u32, (u32, u32)>,
|
||||
/// Diagnostic. Guest addresses to dump (64 bytes each, hex + u32
|
||||
/// lanes) at end-of-run. Populated from `--dump-addr=0x828F3D08,
|
||||
/// 0x828F4070`. Used to inspect static dispatcher / job-queue /
|
||||
@@ -255,6 +264,7 @@ impl KernelState {
|
||||
ring_size_dwords: 0,
|
||||
parallel_active: false,
|
||||
ctor_probe_pcs: std::collections::HashSet::new(),
|
||||
pc_probe_consumers: HashMap::new(),
|
||||
dump_addrs: Vec::new(),
|
||||
};
|
||||
crate::exports::register_exports(&mut state);
|
||||
@@ -580,6 +590,14 @@ impl KernelState {
|
||||
"CTOR-PROBE pc={:#010x} tid={} hw={} cycle={} sp={:#010x} r3={:#010x} lr={:#010x}",
|
||||
pc, tid, hw_id, cycle, sp, r3, lr,
|
||||
);
|
||||
if let Some(&(disp, off)) = self.pc_probe_consumers.get(&pc) {
|
||||
let field_addr = disp.wrapping_add(off);
|
||||
let field_val = mem.read_u32(field_addr);
|
||||
println!(
|
||||
" CTOR-PROBE consumer disp={:#010x} off={} field={:#010x} (= [disp+off])",
|
||||
disp, off, field_val,
|
||||
);
|
||||
}
|
||||
for (i, (fp, frame_lr)) in frames.iter().enumerate() {
|
||||
let saved_r31 = mem.read_u32(fp.wrapping_sub(12));
|
||||
let saved_r30 = mem.read_u32(fp.wrapping_sub(16));
|
||||
|
||||
Reference in New Issue
Block a user