CVE-2025-38352 is a race condition use-after-free vulnerability in the POSIX CPU timers component of the Linux kernel. Security researchers reported that the flaw had been actively exploited in the wild, though the attacks appeared to be limited and highly targeted rather than widespread.
The vulnerability allows attackers to exploit improper memory handling during timer operations, potentially leading to kernel memory corruption. Given the kernel-level impact, successful exploitation could enable elevated privileges or system compromise, making it particularly concerning for Android devices that rely on affected kernel versions.
A detailed analysis of this vulnerability was previously published by @streypaws, whose blog provides a clear explanation of how POSIX CPU timers operate and the specific conditions required to trigger this flaw. Their work offers valuable background and technical context for understanding the issue.
However, the original analysis does not include a working proof-of-concept (PoC) that demonstrates the vulnerability in action. To bridge that gap, I spent time independently researching the flaw and developing my own PoC implementation.
This post offers insight into my approach to vulnerability analysis and PoC development, highlighting how hands-on experimentation can significantly deepen technical understanding. It also illustrates the learning value of breaking down complex kernel issues through practical testing.
Table of Contents
Proof of Concept Overview
Patch Commit Reference
Testing Environment (TL;DR)
Kernel Version
CONFIG_POSIX_CPU_TIMERS_TASK_WORK
QEMU Configuration
Vulnerability Summary
PoC Design Strategy
Building a Minimal POSIX CPU Timer PoC
Creating a Zombie Process
Cleaning Up a Zombie Process
Controlled Zombie Reaping
Implementing the PoC
Extending the Race Window
Kernel Patch to Increase Race Reliability
Triggering the Race Condition
Custom Wait-Time Logic
Timer Initialization
Reaping the Timer Thread and Removing the Timer
PoC Validation
KASAN Crash Output
Non-KASAN Crash Output
Notes on CONFIG_POSIX_CPU_TIMERS_TASK_WORK
Exploitation Discussion
Conclusion
Final Proof of Concept
Patch Commit Details
The kernel patch that addresses CVE-2025-38352 is available in the Linux stable tree. The commit resolves the race condition in the POSIX CPU timers implementation that could lead to a use-after-free scenario.
Patch commit reference:
f90fff1e152dedf52b932240ebbd670d83330eca
Testing Environment
Kernel Version
The proof of concept was developed and tested against Linux kernel 6.12.33 (LTS), which was the most recent long-term support release still affected by CVE-2025-38352 at the time of testing.
CONFIG_POSIX_CPU_TIMERS_TASK_WORK
The patch commit notes that the vulnerability cannot be triggered when CONFIG_POSIX_CPU_TIMERS_TASK_WORK is enabled. This option is normally enabled by default and not exposed to users, as it is defined internally within the kernel configuration.
Because this flag is enabled on both x86 and ARM64 architectures, the vulnerability is effectively exploitable only on 32-bit Android devices. This restriction aligns with reports describing the issue as being exploited in a limited and targeted manner in the wild.
To study the vulnerability, the configuration was modified to make CONFIG_POSIX_CPU_TIMERS_TASK_WORK user-toggleable via menuconfig. Aside from this single change, the kernelCTF LTS configuration was used as a baseline.
QEMU Setup
Since the issue is a race condition, testing required a multi-CPU environment. A QEMU virtual machine configured with four CPU cores was used to reliably trigger the race window.
Vulnerability Recap
CVE-2025-38352 occurs in the Linux kernel’s handling of POSIX CPU timers. On every per-CPU scheduler tick, the kernel evaluates whether CPU timers are ready to fire. The flaw arises because this logic can still execute even after a task has transitioned into the EXIT_ZOMBIE state.
During timer processing, firing timers are collected into a local list while holding the task’s signal handler lock. Once the lock is released, the kernel iterates over that list and executes the timers. If the task becomes a zombie during this window, a second thread can reclaim the task and delete the timer, freeing its memory while it is still being referenced—resulting in a use-after-free condition.
This race window exists between dropping the signal handler lock and firing the collected timers, allowing the timer object to be freed via timer_delete() while still in use.
Planning the Proof of Concept
To reliably trigger the bug, the PoC needed to combine three components:
-
A POSIX CPU timer that fires during task exit
-
A zombie task whose signal handler remains valid long enough for timer collection
-
A parent process capable of precisely controlling when the zombie task is reaped
The approach involved creating a non-main thread that exits into a zombie state while being ptraced, allowing the parent process to delay reaping until the race window is reached.
Extending the Race Window
To improve reliability during testing, a temporary kernel modification was introduced to delay execution inside the timer-handling path. This artificially widened the race window, making it easier to observe the vulnerability. While not required for exploitation, this change significantly reduced trial-and-error during development.
Triggering the Race Condition
The race is triggered by carefully aligning the timer expiration with the task’s transition into the zombie state. The timer must fire after exit_notify() marks the task as a zombie, but before the task is fully cleaned up.
This required fine-tuning the CPU time consumed by the thread prior to exit. In the test environment, a delay of approximately 250 microseconds proved effective, though the optimal value may vary depending on system performance.
Observing the Crash
KASAN Enabled
With KASAN enabled, the kernel reports a clear slab use-after-free, confirming that the timer object is accessed after being freed.
KASAN Disabled
Without KASAN, the issue manifests as kernel warnings during signal delivery, still indicating memory corruption stemming from invalid timer access.
Notes on CONFIG_POSIX_CPU_TIMERS_TASK_WORK
Although earlier research suggested the vulnerability could be triggered with this option enabled, testing showed otherwise. The task-work mechanism prevents timer processing after exit, effectively closing the race window. This explains why the vulnerability is limited to configurations where this feature is disabled—primarily older or 32-bit Android builds.
Exploitation Considerations
While a reliable exploitation primitive is demonstrated, full exploitation would require overcoming several challenges:
-
Timers are allocated from a dedicated slab cache
-
Cross-cache reuse may be required
-
The race occurs in scheduler interrupt context, limiting timing flexibility
Despite these challenges, the presence of in-the-wild exploitation indicates the vulnerability is exploitable with sufficient engineering effort.
Conclusion
Developing a proof of concept for CVE-2025-38352 highlights the value of hands-on vulnerability research. Beyond confirming exploitability, the process deepens understanding of POSIX CPU timers, kernel task lifecycles, and signal handling internals.
Carefully breaking down complex kernel behavior into testable components remains one of the most effective ways to learn—and to validate real-world security impact.
Testing Environment
Kernel Version
The proof of concept was developed and tested against Linux kernel 6.12.33 (LTS), which was the most recent long-term support release still affected by CVE-2025-38352 at the time of testing.
CONFIG_POSIX_CPU_TIMERS_TASK_WORK
The patch commit notes that the vulnerability cannot be triggered when CONFIG_POSIX_CPU_TIMERS_TASK_WORK is enabled. This option is normally enabled by default and not exposed to users, as it is defined internally within the kernel configuration.
Because this flag is enabled on both x86 and ARM64 architectures, the vulnerability is effectively exploitable only on 32-bit Android devices. This restriction aligns with reports describing the issue as being exploited in a limited and targeted manner in the wild.
To study the vulnerability, the configuration was modified to make CONFIG_POSIX_CPU_TIMERS_TASK_WORK user-toggleable via menuconfig. Aside from this single change, the kernelCTF LTS configuration was used as a baseline.
QEMU Setup
Since the issue is a race condition, testing required a multi-CPU environment. A QEMU virtual machine configured with four CPU cores was used to reliably trigger the race window.
Vulnerability Recap
CVE-2025-38352 occurs in the Linux kernel’s handling of POSIX CPU timers. On every per-CPU scheduler tick, the kernel evaluates whether CPU timers are ready to fire. The flaw arises because this logic can still execute even after a task has transitioned into the EXIT_ZOMBIE state.
During timer processing, firing timers are collected into a local list while holding the task’s signal handler lock. Once the lock is released, the kernel iterates over that list and executes the timers. If the task becomes a zombie during this window, a second thread can reclaim the task and delete the timer, freeing its memory while it is still being referenced—resulting in a use-after-free condition.
This race window exists between dropping the signal handler lock and firing the collected timers, allowing the timer object to be freed via timer_delete() while still in use.
Planning the Proof of Concept
To reliably trigger the bug, the PoC needed to combine three components:
-
A POSIX CPU timer that fires during task exit
-
A zombie task whose signal handler remains valid long enough for timer collection
-
A parent process capable of precisely controlling when the zombie task is reaped
The approach involved creating a non-main thread that exits into a zombie state while being ptraced, allowing the parent process to delay reaping until the race window is reached.
Extending the Race Window
To improve reliability during testing, a temporary kernel modification was introduced to delay execution inside the timer-handling path. This artificially widened the race window, making it easier to observe the vulnerability. While not required for exploitation, this change significantly reduced trial-and-error during development.
Triggering the Race Condition
The race is triggered by carefully aligning the timer expiration with the task’s transition into the zombie state. The timer must fire after exit_notify() marks the task as a zombie, but before the task is fully cleaned up.
This required fine-tuning the CPU time consumed by the thread prior to exit. In the test environment, a delay of approximately 250 microseconds proved effective, though the optimal value may vary depending on system performance.
Observing the Crash
KASAN Enabled
With KASAN enabled, the kernel reports a clear slab use-after-free, confirming that the timer object is accessed after being freed.
KASAN Disabled
Without KASAN, the issue manifests as kernel warnings during signal delivery, still indicating memory corruption stemming from invalid timer access.
Notes on CONFIG_POSIX_CPU_TIMERS_TASK_WORK
Although earlier research suggested the vulnerability could be triggered with this option enabled, testing showed otherwise. The task-work mechanism prevents timer processing after exit, effectively closing the race window. This explains why the vulnerability is limited to configurations where this feature is disabled—primarily older or 32-bit Android builds.
Exploitation Considerations
While a reliable exploitation primitive is demonstrated, full exploitation would require overcoming several challenges:
-
Timers are allocated from a dedicated slab cache
-
Cross-cache reuse may be required
-
The race occurs in scheduler interrupt context, limiting timing flexibility
Despite these challenges, the presence of in-the-wild exploitation indicates the vulnerability is exploitable with sufficient engineering effort.
Conclusion
Developing a proof of concept for CVE-2025-38352 highlights the value of hands-on vulnerability research. Beyond confirming exploitability, the process deepens understanding of POSIX CPU timers, kernel task lifecycles, and signal handling internals.
Carefully breaking down complex kernel behavior into testable components remains one of the most effective ways to learn—and to validate real-world security impact.


Comments
Post a Comment