[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:
@@ -53,9 +53,40 @@ const SILPH_WORKER_ENTRIES: [u32; 4] = [
|
||||
0x8250_65B8,
|
||||
];
|
||||
|
||||
/// Round 0x440 up to a page so the ctx alloc never straddles a page boundary
|
||||
/// in heap_alloc's bookkeeping.
|
||||
const SILPH_CTX_SIZE: u32 = 0x500;
|
||||
/// Round 0x440 up to a page-ish so the ctx alloc never straddles a page
|
||||
/// boundary in heap_alloc's bookkeeping. Round 20 grew the alloc from 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
|
||||
/// 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 + 0x1C, 0x3F80_2D83); // float ~1.0 (UI rate B)
|
||||
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
|
||||
// we'll see a guest fault and treat that as the next gate.
|
||||
|
||||
// +0x28..+0x30 = three foreign pointers.
|
||||
// +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 ----
|
||||
// X_DISPATCH_HEADER layout (canary xobject.h:35):
|
||||
|
||||
Reference in New Issue
Block a user