[2.BF] Silph WorkerCtx: synthesize sub-object + vtable at [+0x2C]

Audit-059 round 19 isolated the round-18 worker fault: the four silph::
WorkerCtx worker bodies all execute the sequence

    lwz r3, 44(rN)     ; r3 = [ctx+0x2C] — sub-object pointer
    lwz r11, 0(r3)     ; r11 = sub-object vtable
    lwz r11, 60(r11)   ; r11 = sub-object vtable[15]
    mtctr r11
    bctrl

Ours left [ctx+0x2C] NULL → PC=0 fault on first virtual dispatch. Round 19
recommended materialising a sub-object whose vtable points entirely at an
existing trivial-return stub so workers idle live, returning NULL work,
without crashing.

Changes (silph_synth.rs only, +63/-6):

- Grow SILPH_CTX_SIZE 0x500 → 0x800 to embed sub-object at +0x300 and a
  32-slot sub-vtable at +0x500 in the same heap_alloc.
- After ctx header init, write sub-object pointer at [ctx+0x2C], the XEX-
  resident wrapper constant 0xBE568F00 (round-7 finding) at [ctx+0x30],
  and leave [ctx+0x28] NULL (matches canary first-fire snapshot).
- Populate every slot of the 32-entry sub-vtable with VA 0x8216CAA4, the
  first 4-byte-aligned standalone `li r3, 0; blr` stub located by a fresh
  PE-text scan (preceded by a `blr` terminating the previous function).
- Sub-object body itself is zero-filled apart from the [+0]=vtable_ptr
  write; round-19 disassembly confirms workers only touch slots 15/17.

Smoke (XENIA_SILPH_SYNTH=1, persistent cache, 5e7 instr):

- Lockstep: no crash, all 4 workers (tid=6/7/8/9) reach Ready in deep
  worker-body PCs (0x825067xx/0x825089xx/0x825091xx). Verdict (D) —
  workers run their idle loop returning NULL; existing silph waiters
  (0x1020, 0x1090) remain <NO_SIGNALS_DESPITE_WAITS> because we
  deliberately neutered productive work.
- Parallel: identical picture, no PC=0/PC=garbage fault anywhere.

No regression in 765-test suite.

Next round: feed real work-items into the intrusive ring at ctx+0x210
so workers' returned-NULL idle becomes returned-work productive; or
discover which sub-vtable slots actually need real callees (slot 15
worker drain, slot 17 producer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 21:04:04 +02:00
parent b5885b8560
commit 8683fb59ed

View File

@@ -53,9 +53,40 @@ const SILPH_WORKER_ENTRIES: [u32; 4] = [
0x8250_65B8, 0x8250_65B8,
]; ];
/// Round 0x440 up to a page so the ctx alloc never straddles a page boundary /// Round 0x440 up to a page-ish so the ctx alloc never straddles a page
/// in heap_alloc's bookkeeping. /// boundary in heap_alloc's bookkeeping. Round 20 grew the alloc from 0x500
const SILPH_CTX_SIZE: u32 = 0x500; /// to 0x800 to make room for a synthesised sub-object at +0x300 and its
/// 32-slot vtable at +0x500 (= ctx + 0x500..0x580). Both are required because
/// the worker bodies dereference `[ctx+0x2C]` to obtain a sub-object pointer
/// and then virtual-dispatch through its slot 15.
const SILPH_CTX_SIZE: u32 = 0x800;
/// Offset within the ctx allocation of the synthetic sub-object referenced
/// at `[ctx+0x2C]`. Canary's sub-object sits ~0x300 bytes above the ctx and
/// varies per-instance; we keep it embedded in the same alloc so a single
/// `heap_alloc` covers everything.
const SILPH_SUBOBJ_OFFSET: u32 = 0x300;
/// Offset within the ctx allocation of the synthetic sub-object vtable.
const SILPH_SUBVTBL_OFFSET: u32 = 0x500;
/// Number of slots in the synthetic sub-vtable. The worker bodies access
/// slots 15 and 17 (per audit-059 round 19); 32 slots is generous and gives
/// headroom for any downstream vtable index we haven't surveyed yet.
const SILPH_SUBVTBL_SLOTS: u32 = 32;
/// Trivial `li r3, 0; blr` stub at VA `0x8216CAA4` in the XEX `.text`
/// section — the first 4-byte-aligned standalone instance located by the
/// round-20 PE scan (preceded by a `blr` ending the previous function).
/// We populate every slot of the synthetic sub-vtable with this address so
/// any virtual call through the sub-object idles, returning NULL work and
/// keeping the worker live.
const SILPH_STUB_RETURN_NULL_VA: u32 = 0x8216_CAA4;
/// Round-19 XEX-resident wrapper constant observed at `[ctx+0x30]` in every
/// canary ctx (audit-059 round 7). Same value for all four ctxes — opaque
/// pointer / handle the worker passes through without dereferencing.
const SILPH_CTX_FIELD_30_CONST: u32 = 0xBE56_8F00;
/// 64 KiB worker stack (mirrors AUDIT-048 audio worker), half of canary's /// 64 KiB worker stack (mirrors AUDIT-048 audio worker), half of canary's
/// 128 KiB default. /// 128 KiB default.
@@ -109,9 +140,35 @@ pub fn spawn_silph_workers(
mem.write_u32(ctx + 0x18, 0x3F7F_CCCC); // float ~1.0 (UI rate A) mem.write_u32(ctx + 0x18, 0x3F7F_CCCC); // float ~1.0 (UI rate A)
mem.write_u32(ctx + 0x1C, 0x3F80_2D83); // float ~1.0 (UI rate B) mem.write_u32(ctx + 0x1C, 0x3F80_2D83); // float ~1.0 (UI rate B)
mem.write_u32(ctx + 0x24, 0x0000_0001); mem.write_u32(ctx + 0x24, 0x0000_0001);
// +0x28..+0x30 = three foreign pointers (heap arenas BE/701C/BCA4/B1B6
// per audit-059 round 7). Left NULL — if any worker dereferences these // +0x28..+0x30 = three foreign pointers.
// we'll see a guest fault and treat that as the next gate. // +0x28 — canary's first-fire snapshot has NULL here. Round-19 fault
// analysis shows worker bodies don't dereference this on
// first entry, so we leave it NULL too.
// +0x2C — sub-object pointer. Worker bodies do
// `lwz r3,44(rN); lwz r11,0(r3); lwz r11,60(r11); bctrl`,
// i.e. virtual-dispatch through slot 15 of the sub-object's
// vtable. Point this at our synthesised sub-object embedded
// at ctx + SILPH_SUBOBJ_OFFSET.
// +0x30 — XEX-resident wrapper constant 0xBE568F00 (round 7). Opaque
// but identical across all four canary ctxes.
let subobj_ptr = ctx + SILPH_SUBOBJ_OFFSET;
let subvtbl_ptr = ctx + SILPH_SUBVTBL_OFFSET;
mem.write_u32(ctx + 0x2C, subobj_ptr);
mem.write_u32(ctx + 0x30, SILPH_CTX_FIELD_30_CONST);
// ---- Synthetic sub-object at +0x300 ----
// Round-19 disassembly shows worker bodies only touch the sub-object's
// vtable (slots 15/17) and treat the rest as opaque. Zero-fill is
// sufficient. The only required field is `[+0]` = sub-vtable pointer.
mem.write_u32(subobj_ptr, subvtbl_ptr);
// ---- Synthetic sub-vtable at +0x500 ----
// Every slot points at SILPH_STUB_RETURN_NULL_VA so any virtual call
// through the sub-object idles, returning NULL work without crashing.
for slot in 0..SILPH_SUBVTBL_SLOTS {
mem.write_u32(subvtbl_ptr + slot * 4, SILPH_STUB_RETURN_NULL_VA);
}
// ---- 4× X_KEVENT auto-reset at +0x54/+0x64/+0x74/+0x84, state = 0 ---- // ---- 4× X_KEVENT auto-reset at +0x54/+0x64/+0x74/+0x84, state = 0 ----
// X_DISPATCH_HEADER layout (canary xobject.h:35): // X_DISPATCH_HEADER layout (canary xobject.h:35):