summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-05 18:51:56 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-05 18:51:56 -0500
commitbe25496cdc7d08226daa26f0707997dee12612dd (patch)
tree6b91b9614887530a681c9a4d06e5c9458eb956fa /kernel
parent52a98cfc8d2100c4025f1ee3514fa5cc070c1284 (diff)
downloadopen-iov-be25496cdc7d08226daa26f0707997dee12612dd.tar.gz
open-iov-be25496cdc7d08226daa26f0707997dee12612dd.zip
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.
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_phys_booter.c23
-rw-r--r--kernel/gsp_sema_service.c222
-rw-r--r--kernel/gsp_sema_service.h11
3 files changed, 251 insertions, 5 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index c1d9936..98bbc4a 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -27488,8 +27488,14 @@ static void gsp_phys_gr_surface_alias_scan_locked(
if (npages < 2u || npages > 4096u)
goto next;
for (sdi = 0; sdi < gsp_phys_gr_surf_n; sdi++)
- if (gsp_phys_gr_surf[sdi].va == va)
+ if (gsp_phys_gr_surf[sdi].va == va) {
+ /* r1510: content-gated screen
+ * code-heap quarantine (no-op when
+ * the surface is clean). */
+ (void)gsp_sema_surf_clean_restore(
+ va, npages);
goto next; /* already aliased */
+ }
if (gsp_phys_gr_surface_alias_locked(req, va, npages,
&alias)) {
if (gsp_phys_gr_surf_n <
@@ -29641,6 +29647,21 @@ gr_page_unmapped:
const u32 *sp;
u32 spn = 0, spg = 0;
+ /*
+ * r1510: if the guest's code-heap upload overlapped
+ * the captured X screen, restore the screen from the
+ * capture-time baseline before this launch's replay.
+ * Content-gated (see the sema service's
+ * gsp_sema_surf_clean_restore).
+ */
+ {
+ u64 cva = 0;
+ u32 cnp = 0;
+
+ if (gsp_sema_surf_va_get(&cva, &cnp))
+ (void)gsp_sema_surf_clean_restore(cva,
+ cnp);
+ }
sp = gsp_sema_surf_prelude_get(&spn, &spg);
if (sp && spn && spg) {
bool repatch =
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
diff --git a/kernel/gsp_sema_service.h b/kernel/gsp_sema_service.h
index ff3d088..8fec320 100644
--- a/kernel/gsp_sema_service.h
+++ b/kernel/gsp_sema_service.h
@@ -549,6 +549,17 @@ const u32 *gsp_sema_surf_prelude_get(u32 *n, u32 *gen);
bool gsp_sema_surf_va_get(u64 *va, u32 *npages);
/*
+ * SESSION 154 r1510: quarantine the guest code-heap/shader upload that can
+ * overlap the captured X screen (BAR1 mmap writes the emulator cannot
+ * trap). The surface capture snapshots the screen; a later launch whose
+ * screen page 0 carries the code signature restores the surface from that
+ * snapshot before the engine runs (the baseline stays armed so a later
+ * client context's upload is caught too). Returns the pages restored (0
+ * when there is no pollution or no baseline).
+ */
+int gsp_sema_surf_clean_restore(u64 va, u64 npages);
+
+/*
* SESSION 153-CONTINUED-63: the fill's completed render-target tracking run
* (base VA + page count), true only when every page of the surface resolved
* into contiguous data recs this pass. The booter prefers these recs over