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