From be25496cdc7d08226daa26f0707997dee12612dd Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 5 Nov 2026 18:51:56 -0500 Subject: vgsp-gl: sweep the beef ring head for the client's init uploads and quarantine the screen/code-heap overlap The GL client's one-shot LOAD_INLINE_DATA uploads (code images, vertex and index buffers) sit at the head of its GPFIFO ring, but the first observed GP_PUT can already be far past them (r1508: put=8982), so the continuation sweep never decoded them and the FB fetch pages stayed zero. Sweep the head once per beef channel in inline-only mode with the read-arbitrated, tail-aware decoder: r1509 receipts show 'sema-utilsweep swept=64 writes=16' and 'inline write dst=0x44ff000 -> pa=0xb9f000 bytes=1550 data8=9a996940...', after which sema_probe reads the client's real vertex floats and the u16 index stream at 0xb9d410/0xb9fcc8. The guest's shader/code-heap BAR1 upload can also land on the captured X screen's pages (r1508/r1510: vidmem 0x200000 held a program image while the X screen VA 0x120400000 maps there). Snapshot the captured surface at bind time; before a launch whose surface page 0 carries the code-heap/SPH signature, restore the surface from that snapshot (a black X-clear baseline when the bind happened after the upload). The quarantine is content-gated, preserves raster stores that follow, and r1509/r1511 show the clean page 0 (0xff000000) with no code bytes. --- kernel/gsp_sema_service.c | 222 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 4 deletions(-) (limited to 'kernel/gsp_sema_service.c') diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 0d2c170..4181c0c 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -318,6 +318,13 @@ struct svc_channel { * way the RC watchdog's ring was starved. */ u8 ce_index; /* rank among the live CE channels (creation order) */ u8 gr_index; /* rank among the live GR channels (creation order) */ + /* SESSION 154 r1509: the beef GL client's ring head carries its + * one-shot init uploads (LOAD_INLINE_DATA code/data images). The + * first observed GP_PUT can already be far ahead of those entries + * (r1508: put=8982 at registration), so the continuation sweep + * never sees them and the fetch pages stay zero. Sweep the head + * once per channel registration in inline-only mode. */ + bool head_inline_done; u64 gpfifo_va; u32 gpfifo_entries; u64 userd_pa; @@ -16837,6 +16844,187 @@ static u32 svc_surf_owner_vas(struct nv_vgpu_device *vgpu, u32 emit_vas, return chosen; } +/* + * SESSION 154 r1510: screen/code-heap overlap quarantine. + * + * The guest driver's shader/code-heap upload (BAR1 aperture writes that the + * mmap'd shadow does not trap) can land on the captured X screen's first + * pages (r1508: vidmem 0x200000 held a program image while the X screen VA + * 0x120400000 maps there), so the xwd is a picture of code instead of the + * render target. The screen allocation is the legitimate owner of those + * pages; the code heap is an overlapping guest allocation whose offset the + * emulator does not control. + * + * At the surface capture (the X server's colour-target bind, normally + * before the client uploads) take a page snapshot of the captured surface. + * On later launches, if the surface's first page currently carries the + * client code-heap/SPH signature, restore the whole surface from that + * snapshot (once per pollution episode; the baseline stays armed so a later + * client context's upload is caught too). The restore is content-gated + * (only a page whose +0x700 word is a SPH magic or whose first word is the + * observed code-heap header fires) and happens before the engine runs, so + * raster stores that follow are preserved and the scanout stops showing + * code bytes. + */ +#define SVC_SURF_CLEAN_MAX 1024u +static struct nv_vgpu_device *svc_surf_clean_vgpu; +static u8 *svc_surf_clean_buf; +static u64 svc_surf_clean_va; +static u32 svc_surf_clean_npages; +static u32 svc_surf_clean_hvas; +static bool svc_surf_clean_armed; +static u32 svc_surf_clean_log; +static u32 svc_surf_clean_restore_log; +static u8 svc_surf_clean_page[SEMA_PAGE_SIZE]; +static DEFINE_MUTEX(svc_surf_clean_mtx); + +static bool svc_surf_code_sig(const u8 *p) +{ + u32 w0 = 0, w700 = 0; + + memcpy(&w0, p, 4); + memcpy(&w700, p + 0x700, 4); + return w0 == 0x0400780cu || w700 == 0x000a1482u || + w700 == 0x020a0481u || w700 == 0x020a0881u; +} + +static void svc_surf_clean_snapshot(struct nv_vgpu_device *vgpu, u64 va, + u32 npages, u32 hvas) +{ + u32 k; + + if (!vgpu || !va || npages < 2u || npages > SVC_SURF_CLEAN_MAX) + return; + mutex_lock(&svc_surf_clean_mtx); + if (svc_surf_clean_armed && va == svc_surf_clean_va && + npages == svc_surf_clean_npages) { + mutex_unlock(&svc_surf_clean_mtx); + return; + } + if (!svc_surf_clean_buf) { + svc_surf_clean_buf = vzalloc((size_t)SVC_SURF_CLEAN_MAX * + SEMA_PAGE_SIZE); + if (!svc_surf_clean_buf) { + mutex_unlock(&svc_surf_clean_mtx); + return; + } + } + for (k = 0; k < npages; k++) { + u64 page = va + ((u64)k << 12); + u64 pa = 0; + int ap = SVC_APERTURE_SYS; + u8 *dst = svc_surf_clean_buf + (size_t)k * SEMA_PAGE_SIZE; + + if (svc_vas_lookup_h(vgpu, hvas, page, &pa, &ap) || + svc_read_ap(vgpu, pa & ~(u64)(SEMA_PAGE_SIZE - 1), + svc_ap_of(ap), dst, SEMA_PAGE_SIZE)) + memset(dst, 0, SEMA_PAGE_SIZE); + } + if (svc_surf_code_sig(svc_surf_clean_buf)) { + /* + * Captured after the overlap (the client initialised before + * the X bind this boot): the legitimate screen state for + * this harness is the X opaque-black clear (the root window + * is black and the earlier clean boot's page 0 read + * 0xff000000). Use that as the baseline for every page so + * the quarantine can still give the render target back to + * the engine. + */ + u32 *w = (u32 *)svc_surf_clean_buf; + size_t n = (size_t)npages * SEMA_PAGE_SIZE / 4u; + size_t i; + + for (i = 0; i < n; i++) + w[i] = 0xff000000u; + if (svc_surf_clean_log < 8u) { + svc_surf_clean_log++; + pr_info("SURF-CLEAN-SNAP va=0x%llx pages=%u hvas=0x%x poisoned -> black baseline armed\n", + va, npages, hvas); + } + } else if (svc_surf_clean_log < 8u) { + u32 w0 = 0; + + memcpy(&w0, svc_surf_clean_buf, 4); + svc_surf_clean_log++; + pr_info("SURF-CLEAN-SNAP va=0x%llx pages=%u hvas=0x%x first0=%08x (clean baseline armed)\n", + va, npages, hvas, w0); + } + svc_surf_clean_va = va; + svc_surf_clean_npages = npages; + svc_surf_clean_hvas = hvas; + svc_surf_clean_vgpu = vgpu; + svc_surf_clean_armed = true; + mutex_unlock(&svc_surf_clean_mtx); +} + +int gsp_sema_surf_clean_restore(u64 va, u64 npages) +{ + struct nv_vgpu_device *vgpu; + u32 k, changed = 0; + u64 page = 0; + int ap = SVC_APERTURE_SYS; + u8 *cur = svc_surf_clean_page; + u8 before[8] = { 0 }; + + mutex_lock(&svc_surf_clean_mtx); + vgpu = svc_surf_clean_vgpu; + if (!svc_surf_clean_armed || !svc_surf_clean_buf || !vgpu || + va != svc_surf_clean_va || npages != svc_surf_clean_npages) { + mutex_unlock(&svc_surf_clean_mtx); + return 0; + } + if (svc_vas_lookup_h(vgpu, svc_surf_clean_hvas, va, &page, &ap) || + svc_read_ap(vgpu, page & ~(u64)(SEMA_PAGE_SIZE - 1), + svc_ap_of(ap), cur, SEMA_PAGE_SIZE)) { + mutex_unlock(&svc_surf_clean_mtx); + return 0; + } + if (!svc_surf_code_sig(cur)) { + if (svc_surf_clean_log < 8u) { + svc_surf_clean_log++; + pr_info("SURF-CLEAN-IDLE va=0x%llx pages=%llu page0_first=%08x (no code signature; baseline kept)\n", + va, npages, *(const u32 *)cur); + } + mutex_unlock(&svc_surf_clean_mtx); + return 0; + } + memcpy(before, cur, sizeof(before)); + for (k = 0; k < npages; k++) { + u64 p = va + ((u64)k << 12); + u64 pa = 0; + int a = SVC_APERTURE_SYS; + + if (svc_vas_lookup_h(vgpu, svc_surf_clean_hvas, p, &pa, &a)) { + a = SVC_APERTURE_SYS; + if (svc_walk_vaspaces(vgpu, p, &pa, &a)) + continue; + } + if (svc_write_ap(vgpu, pa & ~(u64)(SEMA_PAGE_SIZE - 1), + svc_ap_of(a), + svc_surf_clean_buf + + (size_t)k * SEMA_PAGE_SIZE, + SEMA_PAGE_SIZE)) + break; + changed++; + } + if (!svc_vas_lookup_h(vgpu, svc_surf_clean_hvas, va, &page, &ap) && + !svc_read_ap(vgpu, page & ~(u64)(SEMA_PAGE_SIZE - 1), + svc_ap_of(ap), cur, SEMA_PAGE_SIZE)) + ; + if (svc_surf_clean_restore_log < 16u) { + svc_surf_clean_restore_log++; + pr_info("SURF-CLEAN-RESTORE va=0x%llx pages=%llu changed=%u before8=%08x %08x after8=%08x %08x (screen code-heap quarantine)\n", + va, npages, changed, + ((const u32 *)before)[0], ((const u32 *)before)[1], + ((const u32 *)cur)[0], ((const u32 *)cur)[1]); + } + /* Keep the baseline armed: a later client context can upload the + * code heap again; the content gate only fires on the code + * signature, never on raster pixels. */ + mutex_unlock(&svc_surf_clean_mtx); + return (int)changed; +} + static void svc_surf_prelude_capture(struct nv_vgpu_device *vgpu, const u8 *buf, u32 len, u32 h_vaspace, u32 h_object, u64 push_va) @@ -16918,6 +17106,7 @@ static void svc_surf_prelude_capture(struct nv_vgpu_device *vgpu, svc_surf_owner_name(h_object), h_object, push_va); } + svc_surf_clean_snapshot(vgpu, va, (u32)npages, h_vaspace); off += span * 4u; } } @@ -37821,8 +38010,8 @@ static u32 svc_util_lid_scan(const u8 *buf, u32 len, u32 *first_subch) * this: a shadowed channel read consumed the entry without a parseable * buffer while the walk resolves the true pages. */ -static void svc_gr_util_sweep(struct nv_vgpu_device *vgpu, - struct svc_channel *ch) +static void svc_gr_util_sweep_n(struct nv_vgpu_device *vgpu, + struct svc_channel *ch, u32 maxent_override) { /* * The observed utility-channel pushes are the ~17 KB setup block and the @@ -37842,11 +38031,13 @@ static void svc_gr_util_sweep(struct nv_vgpu_device *vgpu, * channel read was zero-headed */ static u64 svc_util_tail_try; /* ... and the walk attempts (alt is * the subset that yielded non-zero) */ - u32 i, maxent = min_t(u32, gsp_sema_gr_util_scan, 64); + u32 i, maxent = maxent_override ? + min_t(u32, maxent_override, 64) : + min_t(u32, gsp_sema_gr_util_scan, 64); u32 swept = 0, nmade = 0; bool first_sight = false; - if (!gsp_sema_gr_xlate || !gsp_sema_gr_util_scan) + if (!gsp_sema_gr_xlate || !maxent) return; if (!ch->gpfifo_va || !ch->gpfifo_entries) { /* SESSION 143: name every channel the sweep cannot cover. */ @@ -38103,6 +38294,12 @@ static void svc_gr_util_sweep(struct nv_vgpu_device *vgpu, } } +static void svc_gr_util_sweep(struct nv_vgpu_device *vgpu, + struct svc_channel *ch) +{ + svc_gr_util_sweep_n(vgpu, ch, 0); +} + static void svc_pump_channels(struct nv_vgpu_device *vgpu) { if (svc_pdb_disc_pending) { @@ -38474,6 +38671,23 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) */ if (gsp_sema_gr_util_scan && gsp_sema_gr_xlate) svc_gr_util_sweep(vgpu, ch); + /* + * SESSION 154 r1509: the beef group's one-shot init uploads + * (the GL client's LOAD_INLINE_DATA code/constant/vertex + * images) sit at the HEAD of the ring, but the first observed + * GP_PUT can already be far past them (r1508: put=8982 at + * registration), so the continuation sweep above starts + * beyond the uploads and the fetch pages stay empty. Scan the + * head once per channel with the same read-arbitrated, + * tail-aware, inline-only decoder: it materializes the + * LOAD_INLINE_DATA/copy-ins and records the destination ring, + * while releases and QMD work stay untouched. + */ + if (gsp_sema_gr_xlate && !ch->head_inline_done && + (ch->h_object & 0xfffff000u) == 0xbeef0000u) { + ch->head_inline_done = true; + svc_gr_util_sweep_n(vgpu, ch, 64); + } /* * SESSION 153-CONT-7: a kick-family channel whose USERD -- cgit v1.2.3