CVE-2026-46173
Description
In the Linux kernel, the following vulnerability has been resolved:
exit: prevent preemption of oopsing TASK_DEAD task
When an already-exiting task oopses, make_task_dead() currently calls do_task_dead() with preemption enabled. That is forbidden: do_task_dead() calls __schedule(), which has a comment saying "WARNING: must be called with preemption disabled!".
If an oopsing task is preempted in do_task_dead(), between becoming TASK_DEAD and entering the scheduler explicitly, bad things happen: finish_task_switch() assumes that once the scheduler has switched away from a TASK_DEAD task, the task can never run again and its stack is no longer needed; but that assumption apparently doesn't hold if the dead task was preempted (the SM_PREEMPT case).
This means that the scheduler ends up repeatedly dropping references on the dead task's stack, which can lead to use-after-free or double-free of the entire task stack; in other words, two tasks can end up running on the same stack, resulting in various kinds of memory corruption.
(This does not just affect "recursively oopsing" tasks; it is enough to oops once during task exit, for example in a file_operations::release handler)
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In Linux kernel, a preemption bug in do_task_dead() during an oopsing TASK_DEAD task can lead to use-after-free or double-free of the task stack.
Vulnerability
In the Linux kernel, when an already-exiting task triggers an oops, the function make_task_dead() calls do_task_dead() with preemption enabled, violating the requirement that do_task_dead() must be called with preemption disabled. This bug affects kernel versions prior to the inclusion of the fix commit 6f49f94f3b11 [1]. The vulnerable code path is reachable when a task oopses during exit, for example in a file_operations::release handler [1].
Exploitation
An attacker needs to trigger an oops in a task that is already exiting. No special privileges or network position are required beyond the ability to cause the oops. The vulnerability relies on a race condition: after the oopsing task becomes TASK_DEAD but before it explicitly enters the scheduler via __schedule(), preemption can occur. This allows a different task to be scheduled on the same CPU while the dead task's stack is still considered in use [1].
Impact
On successful exploitation, the scheduler can assume the dead task's stack is no longer needed and drop references repeatedly, leading to use-after-free or double-free of the entire task stack. This can result in two tasks running on the same stack, causing memory corruption and potentially enabling privilege escalation or system instability [1].
Mitigation
The issue is fixed in commit 6f49f94f3b11 in the Linux kernel stable tree [1]. Users should apply the patch or update to a kernel version containing this commit. No workaround is available; the vulnerability requires a kernel fix.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
10c1fa0bb633e4exit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 25e9cb6de7e793..9a909993ab1d8b 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1073,6 +1073,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 25e9cb6de7e793..9a909993ab1d8b 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1073,6 +1073,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
640b4c00fb0eexit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 686bbe72bb41ad..910ffc7450408e 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -981,6 +981,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 686bbe72bb41ad..910ffc7450408e 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -981,6 +981,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
7b2800ba5f5fexit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index d465b36bcc8696..021403fc756a53 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1045,6 +1045,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index d465b36bcc8696..021403fc756a53 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1045,6 +1045,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
6f49f94f3b11exit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index c8c3ff935a84b8..c97db291c5d1cb 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1069,6 +1069,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index c8c3ff935a84b8..c97db291c5d1cb 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1069,6 +1069,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
9756b3db5db6exit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index ede3117fa7d413..9852444627a0bf 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1074,6 +1074,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index ede3117fa7d413..9852444627a0bf 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1074,6 +1074,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
640b4c00fb0eexit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 686bbe72bb41ad..910ffc7450408e 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -981,6 +981,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 686bbe72bb41ad..910ffc7450408e 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -981,6 +981,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
6f49f94f3b11exit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index c8c3ff935a84b8..c97db291c5d1cb 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1069,6 +1069,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index c8c3ff935a84b8..c97db291c5d1cb 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1069,6 +1069,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
7b2800ba5f5fexit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index d465b36bcc8696..021403fc756a53 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1045,6 +1045,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index d465b36bcc8696..021403fc756a53 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1045,6 +1045,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
9756b3db5db6exit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index ede3117fa7d413..9852444627a0bf 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1074,6 +1074,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index ede3117fa7d413..9852444627a0bf 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1074,6 +1074,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
c1fa0bb633e4exit: prevent preemption of oopsing TASK_DEAD task
2 files changed · +2 −2
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 25e9cb6de7e793..9a909993ab1d8b 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1073,6 +1073,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
kernel/exit.c+1 −1 modifieddiff --git a/kernel/exit.c b/kernel/exit.c index 25e9cb6de7e793..9a909993ab1d8b 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1073,6 +1073,7 @@ void __noreturn make_task_dead(int signr) futex_exit_recursive(tsk); tsk->exit_state = EXIT_DEAD; refcount_inc(&tsk->rcu_users); + preempt_disable(); do_task_dead(); } -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing preempt_disable() before do_task_dead() in make_task_dead() allows preemption of a TASK_DEAD task, violating scheduler assumptions and causing use-after-free of the task stack."
Attack vector
An attacker who can trigger a kernel oops (e.g., via a bug in a `file_operations::release` handler) in a task that is already exiting can cause the kernel to call `make_task_dead()` and then `do_task_dead()` with preemption still enabled. If the task is preempted between becoming `TASK_DEAD` and entering the scheduler, the scheduler's `finish_task_switch()` logic for the `SM_PREEMPT` case mishandles the dead task's stack reference, leading to use-after-free or double-free of the task stack. This can result in two tasks running on the same stack and arbitrary memory corruption [patch_id=2898064].
Affected code
The vulnerability is in the `make_task_dead()` function in `kernel/exit.c`. The function calls `do_task_dead()` without first disabling preemption, violating the requirement that `__schedule()` (called by `do_task_dead()`) must be invoked with preemption disabled [patch_id=2898064].
What the fix does
The fix adds a single line — `preempt_disable();` — immediately before the call to `do_task_dead()` inside `make_task_dead()` [patch_id=2898064]. This ensures that preemption is disabled before the task transitions to `TASK_DEAD` and calls into the scheduler, preventing the race condition where a preempted dead task could have its stack mishandled by `finish_task_switch()`. The patch is identical across all stable backports [patch_id=2898067][patch_id=2898062].
Preconditions
- inputA task must already be in the process of exiting (e.g., in a file_operations::release handler) when it triggers a kernel oops.
- configThe kernel must be running a preemptible configuration (CONFIG_PREEMPT).
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/640b4c00fb0e2920327435f6176cbefc3c546165nvd
- git.kernel.org/stable/c/6f49f94f3b11fe8bff1bf2a054143789e76aaf17nvd
- git.kernel.org/stable/c/7b2800ba5f5f77a8ee7f4cbadb19cf1264597a34nvd
- git.kernel.org/stable/c/9756b3db5db6c2f5eccb32dddbd88eb4c54f575envd
- git.kernel.org/stable/c/c1fa0bb633e4a6b11e83ffc57fa5abe8ebb87891nvd
News mentions
0No linked articles in our index yet.