A userptr buffer object created with ETNA_USERPTR_READ (no
ETNA_USERPTR_WRITE) is pinned without FOLL_WRITE in
etnaviv_gem_userptr_get_pages(); the kernel agrees to allow only
read access to the underlying user pages, and the state is
recorded as etnaviv_obj->userptr.ro = true.

etnaviv_iommu_map_gem() then maps the object's sg_table into
the GPU MMU with ETNAVIV_PROT_READ | ETNAVIV_PROT_WRITE
unconditionally, regardless of userptr.ro. A submission that
names the BO as a write target therefore causes the GPU to write
pages that the kernel pinned without write permission, including
page-cache pages of files the caller has only read access to.

The ioctl entry points (DRM_ETNAVIV_GEM_NEW_USERPTR, GEM_SUBMIT)
are DRM_RENDER_ALLOW, so the path is reachable from any
unprivileged render-group user. No intervening permission check,
no RO-aware branch, no MMU-level fallback.

This is the same class shape as drivers/accel/ivpu
commit 7dd57d7a6350 ("accel/ivpu: Disallow re-exporting imported GEM objects"),
fixed 2026-04-30. The Etnaviv manifestation has a simpler trigger
path that does not require PRIME re-import. Source-level analysis
only - no Vivante hardware was available to validate the mutation
primitive end-to-end.

Mask ETNAVIV_PROT_WRITE in the call to etnaviv_iommu_map() when
the buffer originates as a read-only userptr. The userptr.mm
check guards against any non-userptr object that might somehow
have userptr.ro set.

This work is LLM-assisted: Codex (gpt-5.5) drove the static
analysis and class sweep that identified the candidate site;
Claude (claude-opus-4-7) drove the writeup and patch refinement.
Operator review at each gate. Methodology context at
https://northecho.dev/posts/codex-vs-claude-code-vuln-research/.
Posted to the public list per the security-bugs.rst exception
for findings trivial to discover via automated tooling, as
interpreted by the kernel security team for LLM-assisted reports.

Signed-off-by: Christopher Lusk <[email protected]>
Assisted-by: Codex:gpt-5.5
Assisted-by: Claude:claude-opus-4-7
---
v7.1-rc2 reference: drivers/gpu/drm/etnaviv/etnaviv_mmu.c:303-304

Open questions for review:

1. v1 MMU shortcut. etnaviv_iommu_map_gem() takes a shortcut for
   v1 MMU + single-segment sg_table at lines 278-291, bypassing
   etnaviv_iommu_map() entirely and using sg_dma_address directly.
   For typical multi-page userptr BOs sgt->nents > 1 so the
   shortcut is not taken, but for small contiguous regions on v1
   MMU hardware it may apply. This patch does not address the v1
   MMU path. If you believe v1 MMU also needs handling, I am happy
   to follow up with a separate patch.

2. ETNA_SUBMIT_BO_WRITE per-BO flag. etnaviv_gem_submit accepts
   ETNA_SUBMIT_BO_READ / ETNA_SUBMIT_BO_WRITE per buffer object,
   currently used for synchronization dependency tracking. Should
   a submit naming an RO userptr BO with ETNA_SUBMIT_BO_WRITE be
   rejected at submit time as defense-in-depth? Orthogonal to
   this patch; happy to follow up with a separate small patch in
   etnaviv_gem_submit.c if you would like that.

3. Fixes: tag. My v7.1-rc2 clone is shallow; I could not trace
   the introducing commit for the userptr.ro / ETNA_USERPTR_WRITE
   distinction. The bug is present in v7.0, v7.1-rc2, and
   linux-next (2026-05-08). If you have the introducing commit
   handy from deep Etnaviv history, please add the Fixes: tag in
   your applied version, or let me know and I will supply it from
   an unshallowed mirror.

A reviewer with Vivante hardware can confirm the primitive with
the following pseudocode (before/after this patch):

  fd = open("/dev/dri/renderD128", O_RDWR);
  ptr = mmap(...);  /* page-aligned, page-cache-backed file */
  bo_handle = drm_ioctl(fd, DRM_IOCTL_ETNAVIV_GEM_USERPTR,
                        .ptr = ptr, .size = page,
                        .flags = ETNA_USERPTR_READ);
  /* GEM_SUBMIT with the BO listed as ETNA_SUBMIT_BO_WRITE and a
     command stream that writes to the BO GPU VA */
  drm_ioctl(fd, DRM_IOCTL_ETNAVIV_GEM_SUBMIT, ...);
  /* observe: on unpatched kernel, ptr contents have changed;
              on patched kernel, the write does not land in the
              user page-cache pages. */

I am happy to source a Vivante board (Sabre Lite, Wandboard,
HummingBoard Pro) and post a v2 with Tested-by trailers if you
prefer hardware validation before this lands. Source-only is
offered first to avoid asking for a hardware-spend decision that
may not be necessary.

 drivers/gpu/drm/etnaviv/etnaviv_mmu.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/etnaviv/etnaviv_mmu.c 
b/drivers/gpu/drm/etnaviv/etnaviv_mmu.c
index e3572461b599..c4dcb7ee3e49 100644
--- a/drivers/gpu/drm/etnaviv/etnaviv_mmu.c
+++ b/drivers/gpu/drm/etnaviv/etnaviv_mmu.c
@@ -301,7 +301,9 @@ int etnaviv_iommu_map_gem(struct etnaviv_iommu_context 
*context,
 
        mapping->iova = node->start;
        ret = etnaviv_iommu_map(context, node->start, etnaviv_obj->size, sgt,
-                               ETNAVIV_PROT_READ | ETNAVIV_PROT_WRITE);
+                               (etnaviv_obj->userptr.mm && 
etnaviv_obj->userptr.ro) ?
+                               ETNAVIV_PROT_READ :
+                               (ETNAVIV_PROT_READ | ETNAVIV_PROT_WRITE));
 
        if (ret < 0) {
                drm_mm_remove_node(node);
-- 
2.54.0

Reply via email to