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:
@@ -4891,3 +4891,111 @@ divergence; otherwise the next gate is in `XeCryptSha` /
|
||||
**Trace artifacts:** `audit-runs/post-priv-fix/ours.log` (5.6M lines,
|
||||
500M-instruction PC-probe + handle-focus run; full diagnostic dump
|
||||
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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user