feat(kernel): KRNBUG-XEX-001 — real XexCheckExecutablePrivilege from XEX header bitmap
Replace stub_return_zero with a canary-faithful implementation that returns bit `priv` of the loaded XEX's XEX_HEADER_SYSTEM_FLAGS (key 0x00030000) bitmap. Mirrors xenia-canary xboxkrnl_modules.cc:22-39: `(flags >> priv) & 1` for priv < 32, else 0. Plumbing: - xenia-xex: header_keys::SYSTEM_FLAGS const + get_system_flags() accessor. - xenia-kernel/state.rs: pub xex_system_flags: u32 + xex_priv_logged HashSet for one-shot per-priv tracing. - xenia-app: kernel.xex_system_flags wired in cmd_exec_inner. - xenia-kernel/exports.rs: real export body + unit test covering bits 10/11/0/64 + zero-flags case. Sylpheed's bitmap is 0x00000400 (only XEX_SYSTEM_PAL50_INCOMPATIBLE, bit 10). At -n 500M with the fix: - XGetAVPack: 0 -> 1 (priv-10 gate at lr=0x824ab598 flipped). - 10 other canary-only exports + 9 producer PCs + 3 parked handles unchanged. Priv-11 site at sub_824A9710 is downstream and still not reached — AV/crypto block aborts after XGetAVPack returns our placeholder 0x16 (canary returns 8/HDMI; Sylpheed accepts only 3/4/6/8 per xenia-canary xam_info.cc:250-251). Tests 588 -> 589. Lockstep deterministic (3 reruns identical): n50m goes 50000008 -> 50000005 instr / 407415 -> 407417 imp / swaps=2 / draws=0. Goldens re-baselined (sylpheed_n50m, sylpheed_n2m); oracle test green. Full chain-of-effects + next-frontier hand-off in audit-findings.md under KRNBUG-XEX-001. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -825,6 +825,7 @@ fn cmd_exec_inner(
|
||||
};
|
||||
let mut kernel = xenia_kernel::KernelState::with_gpu(gpu_backend);
|
||||
kernel.image_base = base;
|
||||
kernel.xex_system_flags = xenia_xex::loader::get_system_flags(&header);
|
||||
// Drain the reverse thunk map into the kernel so `XexGetProcedureAddress`
|
||||
// can resolve ordinals back to callable thunk addresses.
|
||||
for (module, ordinal, addr) in thunk_addr_map.drain(..) {
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
{
|
||||
"path": "/home/fabi/RE Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso",
|
||||
"instructions": 2000000,
|
||||
"imports": 5634,
|
||||
"imports": 5636,
|
||||
"unimpl": 0,
|
||||
"packets": 0,
|
||||
"draws": 0,
|
||||
"swaps": 0,
|
||||
"resolves": 0,
|
||||
"unique_render_targets": 0,
|
||||
"shader_blobs_live": 0,
|
||||
"interrupts_delivered": 0,
|
||||
"interrupts_dropped": 13,
|
||||
"texture_cache_entries": 0,
|
||||
"texture_decodes": 0
|
||||
"texture_cache_entries": 0
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"instructions": 50000008,
|
||||
"imports": 407415,
|
||||
"instructions": 50000005,
|
||||
"imports": 407417,
|
||||
"unimpl": 0,
|
||||
"draws": 0,
|
||||
"swaps": 2,
|
||||
|
||||
@@ -190,7 +190,7 @@ pub fn register_exports(state: &mut KernelState) {
|
||||
state.register_export(Xboxkrnl, 0x0257, "XeKeysConsoleSignatureVerification", stub_success);
|
||||
|
||||
// Xex module
|
||||
state.register_export(Xboxkrnl, 0x0194, "XexCheckExecutablePrivilege", stub_return_zero);
|
||||
state.register_export(Xboxkrnl, 0x0194, "XexCheckExecutablePrivilege", xex_check_executable_privilege);
|
||||
state.register_export(Xboxkrnl, 0x0195, "XexGetModuleHandle", xex_get_module_handle);
|
||||
state.register_export(Xboxkrnl, 0x0197, "XexGetProcedureAddress", xex_get_procedure_address);
|
||||
|
||||
@@ -2696,6 +2696,31 @@ fn xma_create_context(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut Kern
|
||||
|
||||
// ===== Xex =====
|
||||
|
||||
/// Mirrors xenia-canary `XexCheckExecutablePrivilege_entry`
|
||||
/// (xboxkrnl_modules.cc:22-39): returns whether bit `privilege` of the
|
||||
/// loaded executable module's `XEX_HEADER_SYSTEM_FLAGS` (key 0x00030000)
|
||||
/// is set. Privilege ≥ 32 returns 0 (matches `1 << priv` UB-via-overflow
|
||||
/// behavior — canary's mask becomes 0 once the shift saturates the
|
||||
/// uint32_t range, and `(flags & 0)` is always 0).
|
||||
fn xex_check_executable_privilege(ctx: &mut PpcContext, _mem: &GuestMemory, state: &mut KernelState) {
|
||||
let privilege = ctx.gpr[3] as u32;
|
||||
let result = if privilege < 32 {
|
||||
(state.xex_system_flags >> privilege) & 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if state.xex_priv_logged.insert(privilege) {
|
||||
tracing::info!(
|
||||
priv = privilege,
|
||||
flags = format_args!("{:#010x}", state.xex_system_flags),
|
||||
result,
|
||||
lr = format_args!("{:#010x}", ctx.lr),
|
||||
"XexCheckExecutablePrivilege",
|
||||
);
|
||||
}
|
||||
ctx.gpr[3] = result as u64;
|
||||
}
|
||||
|
||||
fn xex_get_procedure_address(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
|
||||
// Mirrors xenia-canary XexGetProcedureAddress_entry
|
||||
// (xboxkrnl_modules.cc:195): r3 = hmodule, r4 = ordinal,
|
||||
@@ -5242,6 +5267,39 @@ mod tests {
|
||||
assert_ne!(h_krnl, h_xam, "xboxkrnl distinct from xam");
|
||||
}
|
||||
|
||||
/// `XexCheckExecutablePrivilege` must return bit `priv` of the loaded
|
||||
/// XEX's `XEX_HEADER_SYSTEM_FLAGS` bitmap. Mirrors canary
|
||||
/// [xboxkrnl_modules.cc:22-39](../../../xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_modules.cc#L22-L39).
|
||||
#[test]
|
||||
fn xex_check_executable_privilege_reads_system_flags_bitmap() {
|
||||
let (mut ctx, mem, mut state) = fresh();
|
||||
// bit 10 set, bit 11 clear (matches Sylpheed's actual bitmap value
|
||||
// `0x00000400` / XEX_SYSTEM_PAL50_INCOMPATIBLE).
|
||||
state.xex_system_flags = 0x0000_0400;
|
||||
|
||||
ctx.gpr[3] = 10;
|
||||
xex_check_executable_privilege(&mut ctx, &mem, &mut state);
|
||||
assert_eq!(ctx.gpr[3], 1, "priv 10 set in flags 0x0400");
|
||||
|
||||
ctx.gpr[3] = 11;
|
||||
xex_check_executable_privilege(&mut ctx, &mem, &mut state);
|
||||
assert_eq!(ctx.gpr[3], 0, "priv 11 clear in flags 0x0400");
|
||||
|
||||
ctx.gpr[3] = 0;
|
||||
xex_check_executable_privilege(&mut ctx, &mem, &mut state);
|
||||
assert_eq!(ctx.gpr[3], 0, "priv 0 clear in flags 0x0400");
|
||||
|
||||
ctx.gpr[3] = 64;
|
||||
xex_check_executable_privilege(&mut ctx, &mem, &mut state);
|
||||
assert_eq!(ctx.gpr[3], 0, "priv >= 32 returns 0");
|
||||
|
||||
// With no flags set, every priv reads 0.
|
||||
state.xex_system_flags = 0;
|
||||
ctx.gpr[3] = 10;
|
||||
xex_check_executable_privilege(&mut ctx, &mem, &mut state);
|
||||
assert_eq!(ctx.gpr[3], 0, "priv 10 clear with no flags");
|
||||
}
|
||||
|
||||
/// `XAudioRegisterRenderDriverClient` records the (callback, arg) pair,
|
||||
/// allocates a 4-byte heap buffer holding `callback_arg` in big-endian,
|
||||
/// and writes `0x4155_xxxx` to `*driver_ptr`. Mirrors canary
|
||||
|
||||
@@ -91,6 +91,16 @@ pub struct KernelState {
|
||||
pub last_input_bytes: u128,
|
||||
/// Image base of the loaded XEX (for XexExecutableModuleHandle etc.)
|
||||
pub image_base: u32,
|
||||
/// `XEX_HEADER_SYSTEM_FLAGS` (key `0x00030000`) parsed from the loaded
|
||||
/// XEX header. Queried by `XexCheckExecutablePrivilege`: privilege bit
|
||||
/// `n` is set iff `(xex_system_flags & (1 << n)) != 0`. Zero before the
|
||||
/// app installs the loaded image — that matches canary's behavior when
|
||||
/// no executable module is registered (returns 0).
|
||||
pub xex_system_flags: u32,
|
||||
/// One-shot log gate for `XexCheckExecutablePrivilege`: tracks which
|
||||
/// privilege numbers have already produced a `tracing::info!` line so
|
||||
/// the import-hot path doesn't spam at -n 500M.
|
||||
pub xex_priv_logged: std::collections::HashSet<u32>,
|
||||
/// Next thread ID. M2.4: atomic.
|
||||
pub next_thread_id: std::sync::atomic::AtomicU32,
|
||||
/// Virtual file system for NtCreateFile/NtReadFile/etc. The app mounts
|
||||
@@ -248,6 +258,8 @@ impl KernelState {
|
||||
input_packet_number: 0,
|
||||
last_input_bytes: 0,
|
||||
image_base: 0,
|
||||
xex_system_flags: 0,
|
||||
xex_priv_logged: std::collections::HashSet::new(),
|
||||
next_thread_id: AtomicU32::new(1),
|
||||
vfs: None,
|
||||
ui: None,
|
||||
|
||||
@@ -125,4 +125,5 @@ pub mod header_keys {
|
||||
pub const DEFAULT_STACK_SIZE: u32 = 0x00020104;
|
||||
pub const ORIGINAL_PE_NAME: u32 = 0x000183FF;
|
||||
pub const FILE_FORMAT_INFO: u32 = 0x000003FF;
|
||||
pub const SYSTEM_FLAGS: u32 = 0x00030000;
|
||||
}
|
||||
|
||||
@@ -357,6 +357,14 @@ pub fn get_stack_size(header: &Xex2Header) -> u32 {
|
||||
get_opt_header(header, header_keys::DEFAULT_STACK_SIZE).unwrap_or(0x10_0000) // Default 1MB
|
||||
}
|
||||
|
||||
/// XEX `XEX_HEADER_SYSTEM_FLAGS` (key `0x00030000`) — the privilege bitmap
|
||||
/// queried by `XexCheckExecutablePrivilege`. Low byte 0x00 means the inline
|
||||
/// `value` field is the u32 itself (canary `xex_module.cc:103-108`). Returns
|
||||
/// 0 when the header is absent (matches canary's `GetOptHeader` zero-init).
|
||||
pub fn get_system_flags(header: &Xex2Header) -> u32 {
|
||||
get_opt_header(header, header_keys::SYSTEM_FLAGS).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Load the XEX image data into a flat buffer (decompressing if needed).
|
||||
/// Returns the decompressed image bytes ready to map into guest memory.
|
||||
#[tracing::instrument(skip_all, fields(bytes = data.len()))]
|
||||
|
||||
Reference in New Issue
Block a user