[<prev] [next>] [day] [month] [year] [list]
Message-ID: <06a8cb3f5da040c2a439f9f16d6abcec@huawei.com>
Date: Sat, 31 Jan 2026 10:22:50 +0000
From: "liukai (Y)" <liukai284@...wei.com>
To: "maarten.lankhorst@...ux.intel.com" <maarten.lankhorst@...ux.intel.com>,
"mripard@...nel.org" <mripard@...nel.org>, "tzimmermann@...e.de"
<tzimmermann@...e.de>, "airlied@...ux.ie" <airlied@...ux.ie>,
"daniel@...ll.ch" <daniel@...ll.ch>, "dri-devel@...ts.freedesktop.org"
<dri-devel@...ts.freedesktop.org>, "linux-kernel@...r.kernel.org"
<linux-kernel@...r.kernel.org>
CC: "Chenhui (Judy)" <judy.chenhui@...wei.com>, "tanghui (C)"
<tanghui20@...wei.com>, "Zhangqiao (2012 lab)" <zhangqiao22@...wei.com>
Subject: drm/atomic: KASAN: use-after-free in
drm_atomic_helper_wait_for_vblanks
Dear Linux Kernel Developers,
I’ve identified a use-after-free (UAF) in the DRM atomic core that
occurs under a specific race condition between a non-blocking (async)
atomic commit and a synchronous one. This can lead to crashes like the
KASAN report below.
Reproduction Scenario:
CPU1: A non-blocking atomic commit (e.g., from drm_mode_page_flip_ioctl)
1. Creates new_crtc_state1 by atomic_duplicate_state()
2. Calls drm_atomic_helper_swap_state(), making crtc->state = new_crtc_state1
3. Queues commit_work for deferred execution (via queue_work)
CPU2: A synchronous atomic commit (e.g., from drm_fb_helper_damage_work)
runs before the async work starts:
1. Calls drm_atomic_get_crtc_state(), which reads crtc->state (= new_crtc_state1) as the current state
2. Duplicates it to create new_crtc_state2
3. Sets its internal crtcs[i].old_crtc_state = crtc->state (to be destroyed later)
4. Calls drm_atomic_helper_swap_state(), making crtcs[i].state_to_destroy = old_crtc_state (= new_crtc_state1)
5. Completes immediately and calls drm_atomic_state_put()
5. During clear, atomic_destroy_state() is called on crtcs[i].state_to_destory → new_crtc_state1 is freed.
CPU3: The deferred commit_work from CPU1 finally runs and accesses
state->crtcs[i].new_crtc_state (which points to the now-freed
new_crtc_state1) → UAF.
The core problem is that the DRM atomic framework assumes crtc_state
objects are only owned by a single drm_atomic_state. But once a state
becomes crtc->state, it can be captured as the "old state" by a
subsequent commit and destroyed prematurely.
The generic atomic helpers do not protect crtc_state across commit
boundaries. This violates the implicit lifetime contract and leads to
UAF in the async path.
Proposed fix:
For non-blocking commits, explicitly take a kref on each new_crtc_state
before queuing the work, and release it only after commit_tail()
completes. This ensures the state remains valid even if another commit
tries to destroy it as its "previous state".
I’m preparing a patch that adds a kref to drm_crtc_state and implements
the refcounting in the async commit path.
Is this the right direction, or should we instead enforce stricter
serialization between async and sync commits on the same CRTC?
==================================================================
BUG: KASAN: use-after-free in drm_atomic_helper_wait_for_vblanks.part.0+0x13f/0x530
Read of size 1 at addr ffff88810f1b7809 by task kworker/u8:0/350737
Call Trace:
dump_stack+0xbe/0xfd
print_address_description.constprop.0+0x19/0x170
? drm_atomic_helper_wait_for_vblanks.part.0+0x13f/0x530
__kasan_report.cold+0x6c/0x84
? irq_enter_rcu+0x90/0xd0
? drm_atomic_helper_wait_for_vblanks.part.0+0x13f/0x530
kasan_report+0x3a/0x50
drm_atomic_helper_wait_for_vblanks.part.0+0x13f/0x530
? disable_outputs+0x760/0x760
? swake_up_all_locked+0x1e/0xc0
? _raw_spin_unlock_irqrestore+0xe/0x20
? drm_atomic_helper_commit_hw_done+0x276/0x320
drm_atomic_helper_commit_tail+0x99/0xb0
commit_tail+0x1f9/0x260
process_one_work+0x414/0x7e0
worker_thread+0x93/0x6f0
? rescuer_thread+0x7a0/0x7a0
kthread+0x1cc/0x220
? kthread_park+0x150/0x150
ret_from_fork+0x22/0x30
Allocated by task 487100:
kasan_save_stack+0x1b/0x40
__kasan_kmalloc.constprop.0+0xb5/0xe0
drm_atomic_helper_crtc_duplicate_state+0x4f/0x90
drm_atomic_get_crtc_state+0xeb/0x1f0
page_flip_common+0x46/0x1a0
drm_atomic_helper_page_flip+0x7a/0x130
drm_mode_page_flip_ioctl+0xb03/0xb70
drm_ioctl_kernel+0x1b3/0x220
drm_ioctl+0x453/0x6c0
__se_sys_ioctl+0x114/0x160
do_syscall_64+0x2b/0x40
entry_SYSCALL_64_after_hwframe+0x6c/0xd6
Freed by task 417955:
kasan_save_stack+0x1b/0x40
kasan_set_track+0x1c/0x30
kasan_set_free_info+0x20/0x40
__kasan_slab_free+0x151/0x180
kfree+0xa9/0x5c0
drm_atomic_state_default_clear+0x249/0x710
__drm_atomic_state_free+0xbf/0x120
drm_atomic_helper_dirtyfb+0x4d5/0x500
drm_fb_helper_dirty_work+0x1fe/0x270
process_one_work+0x414/0x7e0
worker_thread+0x93/0x6f0
kthread+0x1cc/0x220
ret_from_fork+0x22/0x30
The buggy address belongs to the object at ffff88810f1b7800
which belongs to the cache kmalloc-512 of size 512
The buggy address is located 9 bytes inside of
512-byte region [ffff88810f1b7800, ffff88810f1b7a00)
The buggy address belongs to the page:
page:00000000ec2ce044 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10f1b4
head:00000000ec2ce044 order:2 compound_mapcount:0 compound_pincount:0
flags: 0x17ffffc0010200(slab|head|node=0|zone=2|lastcpupid=0x1fffff)
raw: 0017ffffc0010200 dead000000000100 dead000000000122 ffff888100043400
raw: 0000000000000000 0000000000100010 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810f1b7700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff88810f1b7780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff88810f1b7800: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88810f1b7880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88810f1b7900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Best regards, Liu Kai
Powered by blists - more mailing lists