[PATCH] drm/nouveau: fix memory leak in r535_gr_promote_ctx
From: Peiyang He
Date: Fri Sep 11 2026 - 05:44:17 EST
r535_gr_promote_ctx() gets the GPU_PROMOTE_CTX RM control RPC before it
allocates and maps the context buffers. If nvkm_memory_new(),
nvkm_vmm_get_locked(), or nvkm_memory_map() fails, the function returns
without submitting the RPC and leaks the request allocation. The leaked
allocation is a 4KiB GSP message queue element. This leak can be reliably
triggered by a non-root user with access to the nouveau device node,
and repeated leak can lead to a local denial of service.
Found when fuzzing the nouveau driver with a modified Syzkaller:
BUG: memory leak
unreferenced object 0xffff888019cb8000 (size 4096):
comm "syz.6.42", pid 2747, jiffies 4294720158
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace (crc 43f743b8):
kmemleak_alloc_recursive include/linux/kmemleak.h:44 [inline]
slab_post_alloc_hook mm/slub.c:4597 [inline]
slab_alloc_node mm/slub.c:4917 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kvmalloc_node_noprof+0x62c/0x9e0 mm/slub.c:6905
r535_gsp_cmdq_get drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c:426 [inline]
r535_gsp_rpc_get+0x3e/0x230 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/rpc.c:617
nvkm_gsp_rpc_get drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h:281 [inline]
r535_gsp_rpc_rm_ctrl_get+0x138/0x490 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/ctrl.c:75
nvkm_gsp_rm_ctrl_get drivers/gpu/drm/nouveau/include/nvkm/subdev/gsp.h:323 [inline]
r535_gr_promote_ctx+0x1c5/0x1700 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c:68
r535_gr_chan_new+0x140/0x1c0 drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c:159
nvkm_gr_cclass_new+0x99/0xd0 drivers/gpu/drm/nouveau/nvkm/engine/gr/base.c:117
nvkm_cgrp_ectx_get+0x48a/0x860 drivers/gpu/drm/nouveau/nvkm/engine/fifo/cgrp.c:82
nvkm_cgrp_vctx_get+0x252/0xd00 drivers/gpu/drm/nouveau/nvkm/engine/fifo/cgrp.c:136
nvkm_chan_cctx_get+0x2ae/0xb70 drivers/gpu/drm/nouveau/nvkm/engine/fifo/chan.c:112
nvkm_uchan_object_new+0x285/0x840 drivers/gpu/drm/nouveau/nvkm/engine/fifo/uchan.c:177
nvkm_ioctl_new+0x378/0x930 drivers/gpu/drm/nouveau/nvkm/core/ioctl.c:132
nvkm_ioctl_path drivers/gpu/drm/nouveau/nvkm/core/ioctl.c:278 [inline]
nvkm_ioctl+0x286/0x9b0 drivers/gpu/drm/nouveau/nvkm/core/ioctl.c:300
nvif_object_ioctl drivers/gpu/drm/nouveau/nvif/object.c:46 [inline]
nvif_object_ctor+0x3cc/0x5c0 drivers/gpu/drm/nouveau/nvif/object.c:269
nouveau_abi16_ioctl_grobj_alloc+0x43f/0x9d0 drivers/gpu/drm/nouveau/nouveau_abi16.c:635
drm_ioctl_kernel+0x192/0x350 drivers/gpu/drm/drm_ioctl.c:817
drm_ioctl+0x4f8/0xb40 drivers/gpu/drm/drm_ioctl.c:914
Fix by splitting the original loop in r535_gr_promote_ctx() in two.
The first loop performs all fallible allocation and mapping work.
After the first loop succeeds, allocate the control RPC.
Then use the second loop to fill the promote entries and submit the RPC.
Cc: stable@xxxxxxxxxxxxxxx
Fixes: 361c3cd8ae12 ("drm/nouveau/gr/r535: initial support")
Signed-off-by: Peiyang He <peiyang_he@xxxxxxxxxxxxxxxx>
Assisted-by: Codex:gpt-5.5
---
drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c | 54 +++++++++++++---------
1 file changed, 33 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c
index 034db286d285..8b3474733311 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/gr.c
@@ -47,97 +47,109 @@ r535_gr_chan_dtor(struct nvkm_object *object)
nvkm_memory_unref(&grc->mem[i]);
}
nvkm_vmm_unref(&grc->vmm);
return grc;
}
static const struct nvkm_object_func
r535_gr_chan = {
.dtor = r535_gr_chan_dtor,
};
int
r535_gr_promote_ctx(struct r535_gr *gr, bool golden, struct nvkm_vmm *vmm,
struct nvkm_memory **pmem, struct nvkm_vma **pvma,
struct nvkm_gsp_object *chan)
{
struct nvkm_subdev *subdev = &gr->base.engine.subdev;
struct nvkm_device *device = subdev->device;
NV2080_CTRL_GPU_PROMOTE_CTX_PARAMS *ctrl;
+ int ret;
- ctrl = nvkm_gsp_rm_ctrl_get(&vmm->rm.device.subdevice,
- NV2080_CTRL_CMD_GPU_PROMOTE_CTX, sizeof(*ctrl));
- if (WARN_ON(IS_ERR(ctrl)))
- return PTR_ERR(ctrl);
-
- ctrl->engineType = 1;
- ctrl->hChanClient = vmm->rm.client.object.handle;
- ctrl->hObject = chan->handle;
-
+ /* Allocate and map all context buffers before getting the control RPC. */
for (int i = 0; i < gr->ctxbuf_nr; i++) {
- NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ENTRY *entry =
- &ctrl->promoteEntry[ctrl->entryCount];
const bool alloc = golden || !gr->ctxbuf[i].global;
- int ret;
-
- entry->bufferId = gr->ctxbuf[i].bufferId;
- entry->bInitialize = gr->ctxbuf[i].init && alloc;
+ const bool nonmapped = alloc && gr->ctxbuf[i].bufferId ==
+ NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ID_PRIV_ACCESS_MAP;
if (alloc) {
ret = nvkm_memory_new(device, gr->ctxbuf[i].init ?
NVKM_MEM_TARGET_INST : NVKM_MEM_TARGET_INST_SR_LOST,
gr->ctxbuf[i].size, 1 << gr->ctxbuf[i].page,
gr->ctxbuf[i].init, &pmem[i]);
if (WARN_ON(ret))
return ret;
-
- if (gr->ctxbuf[i].bufferId ==
- NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ID_PRIV_ACCESS_MAP)
- entry->bNonmapped = 1;
} else {
if (gr->ctxbuf[i].bufferId ==
NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ID_UNRESTRICTED_PRIV_ACCESS_MAP)
continue;
pmem[i] = nvkm_memory_ref(gr->ctxbuf_mem[i]);
}
- if (!entry->bNonmapped) {
+ if (!nonmapped) {
struct gf100_vmm_map_v0 args = {
.priv = 1,
.ro = gr->ctxbuf[i].ro,
};
mutex_lock(&vmm->mutex.vmm);
ret = nvkm_vmm_get_locked(vmm, false, true, false, 0, gr->ctxbuf[i].align,
nvkm_memory_size(pmem[i]), &pvma[i]);
mutex_unlock(&vmm->mutex.vmm);
if (ret)
return ret;
ret = nvkm_memory_map(pmem[i], 0, vmm, pvma[i], &args, sizeof(args));
if (ret)
return ret;
+ }
+ }
+ ctrl = nvkm_gsp_rm_ctrl_get(&vmm->rm.device.subdevice,
+ NV2080_CTRL_CMD_GPU_PROMOTE_CTX, sizeof(*ctrl));
+ if (WARN_ON(IS_ERR(ctrl)))
+ return PTR_ERR(ctrl);
+
+ ctrl->engineType = 1;
+ ctrl->hChanClient = vmm->rm.client.object.handle;
+ ctrl->hObject = chan->handle;
+
+ for (int i = 0; i < gr->ctxbuf_nr; i++) {
+ NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ENTRY *entry =
+ &ctrl->promoteEntry[ctrl->entryCount];
+ const bool alloc = golden || !gr->ctxbuf[i].global;
+
+ if (!alloc && gr->ctxbuf[i].bufferId ==
+ NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ID_UNRESTRICTED_PRIV_ACCESS_MAP)
+ continue;
+
+ entry->bufferId = gr->ctxbuf[i].bufferId;
+ entry->bInitialize = gr->ctxbuf[i].init && alloc;
+
+ if (alloc && gr->ctxbuf[i].bufferId ==
+ NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ID_PRIV_ACCESS_MAP)
+ entry->bNonmapped = 1;
+
+ if (!entry->bNonmapped)
entry->gpuVirtAddr = pvma[i]->addr;
- }
if (entry->bInitialize) {
entry->gpuPhysAddr = nvkm_memory_addr(pmem[i]);
entry->size = gr->ctxbuf[i].size;
entry->physAttr = 4;
}
nvkm_debug(subdev,
"promote %02d: pa %016llx/%08x sz %016llx va %016llx init:%d nm:%d\n",
entry->bufferId, entry->gpuPhysAddr, entry->physAttr, entry->size,
entry->gpuVirtAddr, entry->bInitialize, entry->bNonmapped);
ctrl->entryCount++;
}
return nvkm_gsp_rm_ctrl_wr(&vmm->rm.device.subdevice, ctrl);
}
int
r535_gr_chan_new(struct nvkm_gr *base, struct nvkm_chan *chan, const struct nvkm_oclass *oclass,
--
2.43.0