From e1eac3685d6c4208ce7e47bd621e31b76e194811 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Fri, 13 Nov 2026 11:30:46 -0500 Subject: vgsp: content-gated FB-shadow push rescue for recycled client pushes (eng20, end-to-end validation pending) The direct-GL client's one-shot 59,556 B setup push (VA 0x4001004) reads zero at decode time: the live page is recycled before our decode runs, so no C597 bind attaches for h=0xbeef0100. The rescue recovers the push body from the FB shadow with strict content gates: reject fill windows (require >=2 multi-word INC runs + >=8 distinct headers), scan at the push VA's in-page offset (0x4041004 starts 4 B into a page), and log the channel handle. eng20 refuted the unregistered-VAS hypothesis: the payload pages DO walk via VAS 0xbeef0300 (0x4000000->pa=0x342ead000, 0x4001000->pa=0x342eae000, consistent delta). Also found: sema_gl_xlate (absent from params-r1780; default 0) gates every C597 section-recognition site - with it on, the lane produced its first C597 captures (127x c305, 20x c307). Validation cyclic c308 (guest init panic) / c309 (QGA wedge) blocked; no observation yet. Unvalidated end to end. --- kernel/gsp_sema_service.c | 85 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 9 deletions(-) (limited to 'kernel/gsp_sema_service.c') diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 9730c58..efb4fc1 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -15838,12 +15838,78 @@ struct svc_fb_push_hit { u64 off; }; -static bool svc_fb_push_rescue(struct nv_vgpu_device *vgpu, u64 va, u32 len, - u8 *out, u64 *off_out) +/* + * CONT.445 follow-up: content-identity gate for the FB push rescue. The raw + * method-stream parse alone cannot admit a rescue window: op 7 (END_PB_ + * SEGMENT) and op 0 (SET/STORE/USE_SUB_DEV_MASK) are 4-byte header-only + * forms, so a fill window (0xff000000 / 0x00000000 / one repeated dword) + * parses cleanly - the V36 rescue false-positived on exactly such an X + * screen fill. A real setup/draw push carries multi-word INC runs and a + * variety of method headers; require both, so a fill can never win. + */ +static bool svc_fb_push_window_ok(const u8 *buf, u32 len) +{ + u32 off = 0, inc = 0; + u32 seen[64]; + u32 nseen = 0; + + if (!svc_method_stream_plausible(buf, len)) + return false; + while (off + 4 <= len) { + u32 hdr, op, cnt; + + memcpy(&hdr, buf + off, 4); + op = (hdr >> 29) & 7u; + cnt = (hdr >> 16) & 0xfffu; + if (op == 1u || op == 3u || op == 5u) { + if (!cnt || cnt > 0xfff || + off + 4 + cnt * 4u > len) + return false; + if (cnt >= 2) + inc++; + off += 4 + cnt * 4u; + } else if (op == 0u || op == 2u || op == 4u || op == 7u) { + if (nseen < ARRAY_SIZE(seen)) { + u32 i; + bool dup = false; + + for (i = 0; i < nseen; i++) + if (seen[i] == hdr) { + dup = true; + break; + } + if (!dup) + seen[nseen++] = hdr; + } + off += 4; + } else { + return false; + } + } + /* + * Measured real pushes: the 59556-byte setup stream and the 6400-byte + * draw streams carry many distinct method headers and several + * multi-word INC runs. 8 distinct headers + 2 INC runs is well below + * any observed real stream and far above any fill window (1 header, + * 0 INC runs). + */ + return inc >= 2 && nseen >= 8; +} + +static bool svc_fb_push_rescue(struct nv_vgpu_device *vgpu, u32 h_object, + u64 va, u32 len, u8 *out, u64 *off_out) { static struct svc_fb_push_hit hits[8]; u8 *base = vgpu->bars[1].vaddr; u64 lim = min_t(u64, svc_fb_shadow_size(vgpu), 0x4000000ULL), off; + /* + * The client writes the pushbuffer at its VA's page offset inside the + * BAR1 window, so a push whose VA is 0x...004 starts 4 bytes into a + * shadow page. Scan at that same in-page offset (the old page-aligned + * scan could never match such a push - e.g. the 59556-byte setup + * push at va 0x4041004). + */ + u32 poff = (u32)(va & (SEMA_PAGE_SIZE - 1)); static u32 log; int i; @@ -15855,19 +15921,19 @@ static bool svc_fb_push_rescue(struct nv_vgpu_device *vgpu, u64 va, u32 len, for (i = 0; i < ARRAY_SIZE(hits); i++) { if (hits[i].n && hits[i].len == len && hits[i].off + len <= lim && - svc_method_stream_plausible(base + hits[i].off, len)) { + svc_fb_push_window_ok(base + hits[i].off, len)) { memcpy(out, base + hits[i].off, len); *off_out = hits[i].off; return true; } } /* Cheap first: the first 64 bytes must parse as a stream prefix. */ - for (off = 0; off + len <= lim; off += SEMA_PAGE_SIZE) { + for (off = poff; off + len <= lim; off += SEMA_PAGE_SIZE) { u32 pre = min_t(u32, len, 64); if (!svc_method_stream_plausible(base + off, pre)) continue; - if (!svc_method_stream_plausible(base + off, len)) + if (!svc_fb_push_window_ok(base + off, len)) continue; memcpy(out, base + off, len); *off_out = off; @@ -15881,15 +15947,15 @@ static bool svc_fb_push_rescue(struct nv_vgpu_device *vgpu, u64 va, u32 len, } if (log < 16) { log++; - pr_info("sema-chan: FB-PUSH rescue h=0x%x va=0x%llx len=%u -> fb 0x%llx (plausible window; pinned for later reads)\n", - va, len, off); + pr_info("sema-chan: FB-PUSH rescue h=0x%x va=0x%llx len=%u -> fb 0x%llx (content-gated window; pinned for later reads)\n", + h_object, va, len, off); } return true; } if (log < 16) { log++; - pr_info("sema-chan: FB-PUSH rescue h=0x%x va=0x%llx len=%u: no plausible window in 0x%llx shadow\n", - va, len, lim); + pr_info("sema-chan: FB-PUSH rescue h=0x%x va=0x%llx len=%u: no content-gated window in 0x%llx shadow\n", + h_object, va, len, lim); } return false; } @@ -46377,6 +46443,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) u64 fbo = 0; if (svc_fb_push_rescue(vgpu, + ch->h_object, va, len, pb, &fbo)) { ok = true; -- cgit v1.2.3