dri-devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup
@ 2026-09-01  6:32 Peiyang He
  2026-09-01  6:49 ` sashiko-bot
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Peiyang He @ 2026-09-01  6:32 UTC (permalink / raw)
  To: lyude, dakr; +Cc: dri-devel, nouveau, linux-kernel, stable, Peiyang He

OP_UNMAP_SPARSE stores the result of nouveau_uvma_region_find()
in op->reg before the bind job owns the region.

A first sparse unmap can look up the region, prepare its unmap
state and mark the region dirty. A second async sparse unmap of
the same range then sees the dirty region and fails out through
unwind_continue. However, op->reg is left pointing at the looked-up
region even though this job never took a reference and never became
responsible for cleaning it up.

nouveau_uvmm_bind_job_cleanup() later treats any non-NULL op->reg
as job-owned cleanup state. It can therefore tear down the region
that is still owned by the first unmap job, leading to a UAF when
the failed submit cleans itself up.

Fix by clearing op->reg on the unwind_continue paths so failed
submits do not carry a stale region pointer into cleanup.

Found when fuzzing the nouveau driver with a modified Syzkaller:

BUG: KASAN: slab-use-after-free in nouveau_uvma_region_sparse_unref drivers/gpu/drm/nouveau/nouveau_uvmm.c:174 [inline]
BUG: KASAN: slab-use-after-free in nouveau_uvmm_bind_job_cleanup+0x6e1/0x7e0 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1568
Read of size 8 at addr ffff888129b7c108 by task syz.2.125/2454

CPU: 1 UID: 0 PID: 2454 Comm: syz.2.125 Not tainted 7.2.0 #5 PREEMPT(lazy) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x95/0xe0 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xcb/0x5a0 mm/kasan/report.c:482
 kasan_report+0xca/0x100 mm/kasan/report.c:595
 nouveau_uvma_region_sparse_unref drivers/gpu/drm/nouveau/nouveau_uvmm.c:174 [inline]
 nouveau_uvmm_bind_job_cleanup+0x6e1/0x7e0 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1568
 nouveau_uvmm_vm_bind drivers/gpu/drm/nouveau/nouveau_uvmm.c:1731 [inline]
 nouveau_uvmm_ioctl_vm_bind+0xadc/0xd20 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1817
 drm_ioctl_kernel+0x192/0x350 drivers/gpu/drm/drm_ioctl.c:817
 drm_ioctl+0x4f8/0xb40 drivers/gpu/drm/drm_ioctl.c:914
 nouveau_drm_ioctl+0xea/0x2c0 drivers/gpu/drm/nouveau/nouveau_drm.c:1338
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x180/0x1d0 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fd3f9f8594d
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fd3f89e7008 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007fd3fa215fa0 RCX: 00007fd3f9f8594d
RDX: 0000200000000600 RSI: 00000000c0286451 RDI: 0000000000000004
RBP: 00007fd3fa02c22b R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fd3fa216038 R14: 00007fd3fa215fa0 R15: 00007ffef3a1fa40
 </TASK>

clocksource: Watchdog remote CPU 1 read timed out
Allocated by task 2457 on cpu 1 at 138.974794s:
 kasan_save_stack+0x24/0x50 mm/kasan/common.c:57
 kasan_save_track+0x17/0x60 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __kmalloc_cache_noprof+0x299/0x630 mm/slub.c:5489
 _kmalloc_noprof include/linux/slab.h:988 [inline]
 _kzalloc_noprof include/linux/slab.h:1309 [inline]
 nouveau_uvma_region_alloc drivers/gpu/drm/nouveau/nouveau_uvmm.c:249 [inline]
 nouveau_uvma_region_create drivers/gpu/drm/nouveau/nouveau_uvmm.c:341 [inline]
 nouveau_uvmm_bind_job_submit+0x1a4a/0x2820 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1310
 nouveau_job_submit+0x70c/0x1240 drivers/gpu/drm/nouveau/nouveau_sched.c:301
 nouveau_uvmm_vm_bind drivers/gpu/drm/nouveau/nouveau_uvmm.c:1724 [inline]
 nouveau_uvmm_ioctl_vm_bind+0x8d6/0xd20 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1817
 drm_ioctl_kernel+0x192/0x350 drivers/gpu/drm/drm_ioctl.c:817
 drm_ioctl+0x4f8/0xb40 drivers/gpu/drm/drm_ioctl.c:914
 nouveau_drm_ioctl+0xea/0x2c0 drivers/gpu/drm/nouveau/nouveau_drm.c:1338
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x180/0x1d0 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x115/0x690 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 125 on cpu 1 at 139.013117s:
 kasan_save_stack+0x24/0x50 mm/kasan/common.c:57
 kasan_save_track+0x17/0x60 mm/kasan/common.c:78
 kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x61/0x80 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2677 [inline]
 slab_free mm/slub.c:6377 [inline]
 kfree+0x383/0x590 mm/slub.c:6692
 nouveau_uvma_region_free drivers/gpu/drm/nouveau/nouveau_uvmm.c:264 [inline]
 kref_put include/linux/kref.h:65 [inline]
 nouveau_uvma_region_put drivers/gpu/drm/nouveau/nouveau_uvmm.c:276 [inline]
 nouveau_uvmm_bind_job_cleanup+0x4df/0x7e0 drivers/gpu/drm/nouveau/nouveau_uvmm.c:1573
 drm_sched_free_job_work+0x33b/0x5d0 drivers/gpu/drm/scheduler/sched_main.c:1013
 process_one_work+0x8a5/0x1900 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5dd/0xd80 kernel/workqueue.c:3486
 kthread+0x31d/0x420 kernel/kthread.c:436
 ret_from_fork+0x662/0x940 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

The buggy address belongs to the object at ffff888129b7c100
 which belongs to the cache kmalloc-128 of size 128
The buggy address is located 8 bytes inside of
 freed 128-byte region [ffff888129b7c100, ffff888129b7c180)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x129b7c
flags: 0x200000000000000(node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000000 ffff888100041a00 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000100010 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected

Memory state around the buggy address:
 ffff888129b7c000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc
 ffff888129b7c080: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff888129b7c100: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
                      ^
 ffff888129b7c180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff888129b7c200: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb

Fixes: b88baab82871 ("drm/nouveau: implement new VM_BIND uAPI")
Cc: stable@vger.kernel.org
Tested-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.4
---
The first clearing of op->reg is enough to prevent the UAF,
but I also add a second clearing. Now all unwind_continue paths
in the OP_UNMAP_SPARSE case can properly clear op->reg.

 drivers/gpu/drm/nouveau/nouveau_uvmm.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index f5e4756b4de4..aad14316cbef 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1318,6 +1318,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
 			op->reg = nouveau_uvma_region_find(uvmm, op->va.addr,
 							   op->va.range);
 			if (!op->reg || op->reg->dirty) {
+				op->reg = NULL;
 				ret = -ENOENT;
 				goto unwind_continue;
 			}
@@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
 								op->va.range);
 			if (IS_ERR(op->ops)) {
 				ret = PTR_ERR(op->ops);
+				op->reg = NULL;
 				goto unwind_continue;
 			}
 

base-commit: 786262be6048deab760f68c8acc2c85607165894
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup
  2026-09-01  6:32 [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup Peiyang He
@ 2026-09-01  6:49 ` sashiko-bot
  2026-09-01  7:36 ` Peiyang He
  2026-09-02  2:38 ` Peiyang He
  2 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-09-01  6:49 UTC (permalink / raw)
  To: Peiyang He; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Unwinding a successfully prepared OP_MAP_SPARSE operation in nouveau_uvmm_bind_job_submit() causes a guaranteed NULL pointer dereference.
- [High] Unwinding a successfully prepared OP_UNMAP_SPARSE operation leaves the region permanently dirty, causing uninterruptible hangs for future operations.
--

commit 203035037bce29ea5a47c8cbbdd0cff202e6dbea
Author: Peiyang He <peiyang_he@smail.nju.edu.cn>
Subject: drm/nouveau/uvmm: fix UAF in bind job cleanup

This patch clears op->reg on the unwind_continue paths for OP_UNMAP_SPARSE.
This prevents a stale region pointer from being passed to cleanup after an
async sparse unmap fails, avoiding a use-after-free.

> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index f5e4756b4de4a..aad14316cbefd 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1318,6 +1318,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
>  			op->reg = nouveau_uvma_region_find(uvmm, op->va.addr,
>  							   op->va.range);
>  			if (!op->reg || op->reg->dirty) {
> +				op->reg = NULL;
>  				ret = -ENOENT;
>  				goto unwind_continue;
>  			}
> @@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
>  								op->va.range);
>  			if (IS_ERR(op->ops)) {
>  				ret = PTR_ERR(op->ops);
> +				op->reg = NULL;
>  				goto unwind_continue;
>  			}

[Severity: High]
This isn't a bug introduced by this patch, but looking at the unwind path
triggered by goto unwind_continue, does the unwind loop cause a null pointer
dereference for OP_MAP_SPARSE operations?

When unwinding a successfully prepared OP_MAP_SPARSE operation, the code
unconditionally calls drm_gpuva_ops_free(&uvmm->base, op->ops). However,
op->ops is never allocated for OP_MAP_SPARSE and remains NULL.

The drm_gpuva_ops_free() function does not check for NULL and evaluates
&(ops)->list inside the drm_gpuva_for_each_op_safe() iterator:

drm_gpuvm.c:drm_gpuva_ops_free() {
    struct drm_gpuva_op *op, *next;

    drm_gpuva_for_each_op_safe(op, next, ops) {
        list_del(&op->entry);
        ...

Could this crash if a VM_BIND job submits an OP_MAP_SPARSE operation
followed by a malformed operation that fails?

[Severity: High]
This is also a pre-existing issue in the same unwind path, but does
unwinding a successfully prepared OP_UNMAP_SPARSE operation leave the region
permanently dirty?

The prepare phase for OP_UNMAP_SPARSE calls nouveau_uvma_region_dirty(op->reg).
If a subsequent operation fails, the unwind loop restores the region to the
maple tree and unconditionally clears op->reg = NULL without clearing the
dirty flag or signaling the completion:

nouveau_uvmm.c:nouveau_uvmm_bind_job_submit() unwind path {
    ...
    case OP_UNMAP_SPARSE:
        __nouveau_uvma_region_insert(uvmm, op->reg);
        nouveau_uvmm_sm_unmap_prepare_unwind(uvmm, &op->new, op->ops);
        break;
    ...
    drm_gpuva_ops_free(&uvmm->base, op->ops);
    op->ops = NULL;
    op->reg = NULL;
    ...

Because op->reg is cleared here, the nouveau_uvmm_bind_job_cleanup() function
skips it, meaning nouveau_uvma_region_complete(op->reg) is never called.

Will future tasks attempting to map over this leaked region hang forever
waiting on &reg->complete in bind_validate_map_common()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/7F26D29D0BD60619+20260901063255.661503-1-peiyang_he@smail.nju.edu.cn?part=1

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup
  2026-09-01  6:32 [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup Peiyang He
  2026-09-01  6:49 ` sashiko-bot
@ 2026-09-01  7:36 ` Peiyang He
  2026-09-02  2:38 ` Peiyang He
  2 siblings, 0 replies; 4+ messages in thread
From: Peiyang He @ 2026-09-01  7:36 UTC (permalink / raw)
  To: lyude, dakr; +Cc: dri-devel, nouveau, linux-kernel, stable

On 2026/9/1 14:32, Peiyang He wrote:
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index f5e4756b4de4..aad14316cbef 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1318,6 +1318,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
>  			op->reg = nouveau_uvma_region_find(uvmm, op->va.addr,
>  							   op->va.range);
>  			if (!op->reg || op->reg->dirty) {
> +				op->reg = NULL;
>  				ret = -ENOENT;
>  				goto unwind_continue;
>  			}
> @@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
>  								op->va.range);
>  			if (IS_ERR(op->ops)) {
>  				ret = PTR_ERR(op->ops);
> +				op->reg = NULL;
>  				goto unwind_continue;
>  			}

None of the two issues pointed by Sashiko AI are introduced by this patch.

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup
  2026-09-01  6:32 [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup Peiyang He
  2026-09-01  6:49 ` sashiko-bot
  2026-09-01  7:36 ` Peiyang He
@ 2026-09-02  2:38 ` Peiyang He
  2 siblings, 0 replies; 4+ messages in thread
From: Peiyang He @ 2026-09-02  2:38 UTC (permalink / raw)
  To: lyude, dakr; +Cc: dri-devel, nouveau, linux-kernel, stable

On 2026/9/1 14:32, Peiyang He wrote:
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index f5e4756b4de4..aad14316cbef 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1318,6 +1318,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
>  			op->reg = nouveau_uvma_region_find(uvmm, op->va.addr,
>  							   op->va.range);
>  			if (!op->reg || op->reg->dirty) {
> +				op->reg = NULL;
>  				ret = -ENOENT;
>  				goto unwind_continue;
>  			}
> @@ -1327,6 +1328,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job,
>  								op->va.range);
>  			if (IS_ERR(op->ops)) {
>  				ret = PTR_ERR(op->ops);
> +				op->reg = NULL;
>  				goto unwind_continue;
>  			}
>  
> 
> base-commit: 786262be6048deab760f68c8acc2c85607165894

This patch is identical to Zhenhao's[1], so please ignore this patch.
Sorry for the disturbance.

[1]: https://lore.kernel.org/all/20260811-nouveau-uvmm-vmbind-fixes-v2-2-aaee4b395d04@gmail.com/

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-03  7:34 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-01  6:32 [PATCH] drm/nouveau/uvmm: fix UAF in bind job cleanup Peiyang He
2026-09-01  6:49 ` sashiko-bot
2026-09-01  7:36 ` Peiyang He
2026-09-02  2:38 ` Peiyang He

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox