feat(kernel): KRNBUG-XAM-001 — XGetAVPack returns 8 (HDMI), not 0x16

Mirrors canary's cvars::avpack default (xam_info.cc:35) and Sylpheed's
accepted set {3,4,6,8} (xam_info.cc:250-251). With KRNBUG-XEX-001 having
flipped the priv-10 gate, XGetAVPack now reaches its caller in
sub_824AB578; returning 0x16 caused Sylpheed to abort the AV/crypto
block before XeCryptSha. Cascade walks one step (canary-only export
list 11 → 10); sub_824ABA98 is the next candidate.

Tests: 589 → 590. Goldens re-baselined (n50m: 50000005→50000004,
imports 407417→407416). Lockstep deterministic across 3 reruns at
-n 100M (instructions=100000010, import_calls=987686 +2.4×, swaps=2).
9-PC producer probe still 0×; parked handles 0x1004/0x100c/0x15e0
still signal_attempts=0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-04 18:54:24 +02:00
parent 33e49e70c8
commit 19659d7f76
4 changed files with 120 additions and 5 deletions

View File

@@ -4891,3 +4891,111 @@ divergence; otherwise the next gate is in `XeCryptSha` /
**Trace artifacts:** `audit-runs/post-priv-fix/ours.log` (5.6M lines, **Trace artifacts:** `audit-runs/post-priv-fix/ours.log` (5.6M lines,
500M-instruction PC-probe + handle-focus run; full diagnostic dump 500M-instruction PC-probe + handle-focus run; full diagnostic dump
in stdout). in stdout).
---
### KRNBUG-XAM-001 — `XGetAVPack` returned non-canary `0x16`; canary default is `8` (HDMI)
**Status:** LANDED 2026-05-04. Closes the first follow-up of
KRNBUG-XEX-001 (the `XGetAVPack` arm flipped 0→1 by the priv-10 fix
exposed this as the next gate).
**One-line fix.** `crates/xenia-kernel/src/xam.rs:382-384`:
```rust
fn xget_avpack(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 8;
}
```
Was `0x16`. Canary's `XGetAVPack_entry` returns `cvars::avpack`
(`xam_info.cc:252`); the cvar is `DEFINE_int32(avpack, 8, ...)`
(`xam_info.cc:35`). Canary's inline comment at `xam_info.cc:250-251`:
*"Games seem to use this as a PAL check — if the result is not
3/4/6/8 they explode with errors if not in PAL mode."* `0x16` (=22)
is not in `{3, 4, 6, 8}`, so Sylpheed's caller treated the response
as invalid.
**Tests.** 589 → 590. New unit test `xget_avpack_returns_hdmi` asserts
`r3 == 8`. Constant-return change; one assertion is enough.
**Validation chain (Step 3 of the hand-off):**
| step | outcome |
|---|---|
| (a) `cargo test --workspace --release` | 589 → 590; all green. |
| (b) `--stable-digest -n 100M` lockstep | `instructions=100000010, import_calls=987686, swaps=2`. 3-run identical (counter sets bit-equal). Was `100000013, 407417, 2`. The +2.4× import-call jump is the deterministic guest divergence into the canary-correct branch (the AV/crypto block now executes more thunks). NOT non-determinism. |
| (c) AUDIT-005 9-PC probe at -n 500M | All 9 producer probe sites still 0× (`grep -c CTOR-PROBE = 0`). |
| (d) `--trace-handles-focus=0x1004,0x100c,0x15e0` | All 3 handles still `signal_attempts=0`. The producers live deeper in the init flow than what `XGetAVPack` alone unlocks. |
| (e) Canary kernel-call diff (set-diff `audit-runs/post-fix/ours-500m.log` vs `audit-runs/audit-005/canary.log`) | 11 → **10** canary-only exports. The single match unlocked is `XGetAVPack` (canary=1, ours=1). Remaining absent: `ExTerminateThread ×2`, `KeReleaseSemaphore ×268`, `KeResetEvent ×1`, `NtDeviceIoControlFile ×2`, `ObCreateSymbolicLink ×1`, `XamTaskCloseHandle ×1`, `XamTaskSchedule ×1`, `XamUserReadProfileSettings ×2`, `XeCryptSha ×1`, `XeKeysConsolePrivateKeySign ×1`. |
| (f) `sylpheed_oracles` (n50m) | Re-baselined: `instructions=50000004, imports=407416, swaps=2, draws=0` (was `50000005, 407417, 2, 0`). 3 deterministic re-runs. Orphan golden `sylpheed_n2m.json` (no test refers to it) also re-baselined for hygiene. |
**Decisive interpretation.** The fix is **correct but partial**. The
`XGetAVPack` value returns are now in the canary-accepted set, and
the call site at `0x824ab5a0` reaches it; the rest of the AV/crypto
block at `sub_824AB578` between `XGetAVPack` returning (`lr=0x824ab5a4`)
and `XeCryptSha` does not execute. The cascade walked exactly **one**
step.
**Telemetry signal — `lr=0x824a97e4` post-fix.** A new `RtlNtStatusToDosError(r3=0xc0000011 ...)` (`STATUS_NOT_IMPLEMENTED`)
fires from `lr=0x824a97e4` immediately after `XGetAVPack` returns.
That PC is **inside** `sub_824A9710` (the priv-11 site), so the
priv-11-caller IS being entered (probably via fall-through from a
caller of `sub_824AB578`'s post-AV block), but the priv-11 query
itself never fires — there's a precondition between block entry and
priv-11 that fails. Almost certainly a downstream sub of the
AV/crypto block (one of `sub_824ABA98` and friends from AUDIT-005's
disasm) returns negative NTSTATUS, which routes here.
**Next-frontier bug (the new gate identified by this fix).** Between
`XGetAVPack` (`lr=0x824ab5a4`) and `XeCryptSha`. Two candidates:
1. **The 4 unreached siblings inside `sub_824AB578`** —
`XeCryptSha`, `XeKeysConsolePrivateKeySign`, `NtDeviceIoControlFile ×2`,
`ObCreateSymbolicLink`. Currently all stubs (`stub_success` for
the crypto, real for `NtDeviceIoControlFile` but the caller may
not be reached). Need to diff sub_824AB578 step-by-step from
`0x824ab5a4` onward to find the failing precondition.
2. **`sub_824ABA98` returning negative NTSTATUS** (the AUDIT-005
call site referenced from `lr=0x824a97e4`). If the AV/crypto
block calls `sub_824ABA98` and gets a negative return, control
transfers to the error path that triggers the
`RtlNtStatusToDosError(c0000011)` we observe. That PC is the
tail signal — finding what's upstream of it is the next probe.
**What did NOT change** (per the AUDIT-004 diagnosis chain):
- The 9 producer-callsite PCs for handles `0x100c` (5 sites) and
`0x15e0` (4 sites): still 0× hits.
- The 3 parked-waiter handles `0x1004 / 0x100c / 0x15e0`:
still `signal_attempts=0`.
- `swaps=2` plateau, `draws=0`: unchanged.
**Trace artifacts:** `audit-runs/post-fix/ours-500m.log` (5.6M lines,
500M-instruction PC-probe + handle-focus run, post-AV-pack-fix).
Same probe configuration as KRNBUG-AUDIT-005's `audit-runs/audit-005/ours.log`,
re-runnable with the command in that finding's "Reproduce" block.
**Reproduce the canary set-diff:**
```bash
python3 - <<'PY'
import re
from pathlib import Path
from collections import Counter
HERE = Path('audit-runs/audit-005')
CR = re.compile(r'^d>\s+[0-9A-Fa-f]+\s+([A-Z][A-Za-z0-9_]+)\(')
OR_ = re.compile(r'probe_calls.*?call=([A-Za-z_][A-Za-z0-9_]*)')
def extract(p, rx):
out = Counter()
with open(p, errors='replace') as f:
for line in f:
m = rx.search(line)
if m: out[m.group(1)] += 1
return out
canary = extract(HERE/'canary.log', CR)
ours = extract('audit-runs/post-fix/ours-500m.log', OR_)
for n in sorted(set(canary) - set(ours)):
print(f' {canary[n]:>5} {n}')
PY
```

View File

@@ -1,6 +1,6 @@
{ {
"instructions": 2000000, "instructions": 2000005,
"imports": 5636, "imports": 5635,
"unimpl": 0, "unimpl": 0,
"draws": 0, "draws": 0,
"swaps": 0, "swaps": 0,

View File

@@ -1,6 +1,6 @@
{ {
"instructions": 50000005, "instructions": 50000004,
"imports": 407417, "imports": 407416,
"unimpl": 0, "unimpl": 0,
"draws": 0, "draws": 0,
"swaps": 2, "swaps": 2,

View File

@@ -380,7 +380,7 @@ fn xam_session_create_handle(ctx: &mut PpcContext, mem: &GuestMemory, state: &mu
// ===== Locale ===== // ===== Locale =====
fn xget_avpack(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) { fn xget_avpack(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
ctx.gpr[3] = 0x16; // HDMI ctx.gpr[3] = 8;
} }
fn xget_game_region(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) { fn xget_game_region(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) {
@@ -465,4 +465,11 @@ mod tests {
other => panic!("expected Thread object with hw_id set, got {:?}", other), other => panic!("expected Thread object with hw_id set, got {:?}", other),
} }
} }
#[test]
fn xget_avpack_returns_hdmi() {
let (mut ctx, mem, mut state) = fresh();
xget_avpack(&mut ctx, &mem, &mut state);
assert_eq!(ctx.gpr[3], 8);
}
} }