In DRM atomic modesetting, non-blocking atomic commits defer the execution of commit_tail to a workqueue worker. Once drm_atomic_helper_commit_hw_done() is called during commit_tail, hardware programming is marked as complete, allowing subsequent commits waiting in drm_atomic_helper_wait_for_dependencies() to proceed. The subsequent commit can then swap CRTC states and free the previous state (which was the non-blocking commit's new_crtc_state) once its commit cycle completes. Consequently, accessing new_crtc_state after drm_atomic_helper_commit_hw_done() is unsafe. However, drm_atomic_helper_wait_for_vblanks() is called after drm_atomic_helper_commit_hw_done() and iterates over CRTCs using for_each_oldnew_crtc_in_state(), directly dereferencing new_crtc_state->active. If the commit worker is preempted immediately after calling drm_atomic_helper_commit_hw_done(), a concurrent commit can run to completion and free the state in drm_atomic_commit_default_clear(). When the worker resumes and runs drm_atomic_helper_wait_for_vblanks(), accessing new_crtc_state->active triggers a slab-use-after-free: ================================================================== BUG: KASAN: slab-use-after-free in drm_atomic_helper_wait_for_vblanks+0x317/0x8f0 drivers/gpu/drm/drm_atomic_helper.c:1905 Read of size 1 at addr ffff88819b694c09 by task kworker/u10:4/87 Workqueue: events_unbound commit_work Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 print_report+0x58/0x70 mm/kasan/report.c:482 kasan_report+0x117/0x150 mm/kasan/report.c:595 drm_atomic_helper_wait_for_vblanks+0x317/0x8f0 drivers/gpu/drm/drm_atomic_helper.c:1905 drm_atomic_helper_commit_tail+0x2e9/0x510 drivers/gpu/drm/drm_atomic_helper.c:2003 commit_tail+0x2b1/0x3c0 drivers/gpu/drm/drm_atomic_helper.c:2080 process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3470 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3551 kthread+0x38b/0x480 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Allocated by task 11498: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415 __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563 drm_atomic_helper_crtc_duplicate_state+0x72/0xb0 drivers/gpu/drm/drm_atomic_state_helper.c:204 drm_atomic_get_crtc_state+0x1b3/0x570 drivers/gpu/drm/drm_atomic.c:462 page_flip_common+0x56/0x2a0 drivers/gpu/drm/drm_atomic_helper.c:3947 drm_atomic_helper_page_flip+0xa5/0x160 drivers/gpu/drm/drm_atomic_helper.c:4008 drm_mode_page_flip_ioctl+0xe2d/0x13d0 drivers/gpu/drm/drm_plane.c:1543 drm_ioctl+0x70e/0xba0 drivers/gpu/drm/drm_ioctl.c:914 do_syscall_64+0x155/0x510 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 11498: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584 __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285 kfree+0x1c5/0x650 mm/slub.c:6792 drm_atomic_commit_default_clear+0x47f/0xf00 drivers/gpu/drm/drm_atomic.c:311 drm_atomic_commit_clear drivers/gpu/drm/drm_atomic.c:394 [inline] __drm_atomic_commit_free+0xb4/0x230 drivers/gpu/drm/drm_atomic.c:411 drm_atomic_commit_put include/drm/drm_atomic.h:713 [inline] drm_client_modeset_commit_atomic+0x6ec/0x7e0 drivers/gpu/drm/drm_client_modeset.c:1110 drm_client_modeset_commit_locked+0xcb/0x4d0 drivers/gpu/drm/drm_client_modeset.c:1207 drm_client_modeset_commit+0x4a/0x70 drivers/gpu/drm/drm_client_modeset.c:1233 drm_release+0x32d/0x400 drivers/gpu/drm/drm_file.c:441 do_syscall_64+0x155/0x510 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f ================================================================== Fix this by caching new_crtc_state->active into a new new_active field in struct __drm_crtcs_state during drm_atomic_get_crtc_state(), drm_atomic_helper_setup_commit(), and drm_atomic_helper_swap_state(). drm_atomic_helper_wait_for_vblanks() can then check state->crtcs[i].new_active using for_each_old_crtc_in_state() without dereferencing new_crtc_state after hw_done. Fixes: 415c3ac3f256 ("drm/atomic: Fix atomic helpers to use the new iterator macros, v3.") Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+0f999d26a4fd79c3a23b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0f999d26a4fd79c3a23b Link: https://syzkaller.appspot.com/ai_job?id=401dc2f0-db02-4684-b99f-d615bff63626 To: "David Airlie" To: To: "Maarten Lankhorst" To: "Maxime Ripard" To: "Simona Vetter" To: "Thomas Zimmermann" Cc: --- diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index e5c8ef06c..73c667af5 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -315,6 +315,7 @@ void drm_atomic_commit_default_clear(struct drm_atomic_commit *state) state->crtcs[i].state_to_destroy = NULL; state->crtcs[i].old_state = NULL; state->crtcs[i].new_state = NULL; + state->crtcs[i].new_active = false; if (state->crtcs[i].commit) { drm_crtc_commit_put(state->crtcs[i].commit); @@ -466,6 +467,7 @@ drm_atomic_get_crtc_state(struct drm_atomic_commit *state, state->crtcs[index].state_to_destroy = crtc_state; state->crtcs[index].old_state = crtc->state; state->crtcs[index].new_state = crtc_state; + state->crtcs[index].new_active = crtc_state->active; state->crtcs[index].ptr = crtc; crtc_state->state = state; diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 285aac355..94f23320e 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1890,7 +1890,7 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, struct drm_atomic_commit *state) { struct drm_crtc *crtc; - struct drm_crtc_state *old_crtc_state, *new_crtc_state; + struct drm_crtc_state *old_crtc_state; int i, ret; unsigned int crtc_mask = 0; @@ -1901,8 +1901,8 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, if (state->legacy_cursor_update) return; - for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { - if (!new_crtc_state->active) + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { + if (!state->crtcs[i].new_active) continue; ret = drm_crtc_vblank_get(crtc); @@ -2530,6 +2530,8 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_commit *state, funcs = state->dev->mode_config.helper_private; for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { + state->crtcs[i].new_active = new_crtc_state->active; + commit = kzalloc_obj(*commit); if (!commit) return -ENOMEM; @@ -3333,6 +3335,7 @@ int drm_atomic_helper_swap_state(struct drm_atomic_commit *state, new_crtc_state->state = NULL; state->crtcs[i].state_to_destroy = old_crtc_state; + state->crtcs[i].new_active = new_crtc_state->active; crtc->state = new_crtc_state; if (new_crtc_state->commit) { diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h index 88087910a..b1a488b91 100644 --- a/include/drm/drm_atomic.h +++ b/include/drm/drm_atomic.h @@ -215,6 +215,17 @@ struct __drm_crtcs_state { s32 __user *out_fence_ptr; u64 last_vblank_count; + + /** + * @new_active: + * + * Caches &drm_crtc_state.active of @new_state for use by + * drm_atomic_helper_wait_for_vblanks() after + * drm_atomic_helper_commit_hw_done() is called. This ensures that a + * concurrent commit won't free the CRTC state before active status + * is inspected. + */ + bool new_active; }; struct __drm_connnectors_state { base-commit: cee9395acd8043be0644b25c34bfa86623f2b935 -- This is an AI-generated patch subject to moderation. Reply with '#syz upstream' to Sign-off the patch as a human author and send it to the upstream kernel mailing lists. Reply with '#syz reject' to reject it ('#syz unreject' to undo). See https://goo.gle/syzbot-ai-patches for information about AI-generated patches. You can comment on the patch as usual, syzbot will try to address the comments and send a new version of the patch if necessary. syzbot engineers can be reached at syzkaller@googlegroups.com.