include/linux/kthread.h | 1 - include/linux/module.h | 2 +- include/linux/sunrpc/svc.h | 2 +- kernel/bpf/verifier.c | 1 - kernel/kthread.c | 8 ++++---- kernel/module/main.c | 2 +- lib/kunit/try-catch.c | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-)
In 28aaa9c39945 ("kthread: consolidate kthread exit paths to prevent use-after-free")
we folded kthread_exit() into do_exit() when we fixed a nasty UAF bug.
We left kthread_exit() around as an alias to do_exit(). Remove it
completely.
Reported-by: Christian Loehle <christian.loehle@arm.com>
Link: https://lore.kernel.org/1ff1bce2-8bb4-463c-a631-16e14f4ea7e2@arm.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
include/linux/kthread.h | 1 -
include/linux/module.h | 2 +-
include/linux/sunrpc/svc.h | 2 +-
kernel/bpf/verifier.c | 1 -
kernel/kthread.c | 8 ++++----
kernel/module/main.c | 2 +-
lib/kunit/try-catch.c | 2 +-
7 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/include/linux/kthread.h b/include/linux/kthread.h
index a01a474719a7..37982eca94f1 100644
--- a/include/linux/kthread.h
+++ b/include/linux/kthread.h
@@ -116,7 +116,6 @@ void *kthread_probe_data(struct task_struct *k);
int kthread_park(struct task_struct *k);
void kthread_unpark(struct task_struct *k);
void kthread_parkme(void);
-#define kthread_exit(result) do_exit(result)
void kthread_complete_and_exit(struct completion *, long) __noreturn;
int kthreads_update_housekeeping(void);
void kthread_do_exit(struct kthread *, long);
diff --git a/include/linux/module.h b/include/linux/module.h
index 14f391b186c6..79ac4a700b39 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -855,7 +855,7 @@ static inline int unregister_module_notifier(struct notifier_block *nb)
return 0;
}
-#define module_put_and_kthread_exit(code) kthread_exit(code)
+#define module_put_and_kthread_exit(code) do_exit(code)
static inline void print_modules(void)
{
diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h
index 4dc14c7a711b..c86fc8a87eae 100644
--- a/include/linux/sunrpc/svc.h
+++ b/include/linux/sunrpc/svc.h
@@ -338,7 +338,7 @@ static inline void svc_thread_init_status(struct svc_rqst *rqstp, int err)
{
store_release_wake_up(&rqstp->rq_err, err);
if (err)
- kthread_exit(1);
+ do_exit(1);
}
struct svc_deferred_req {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 401d6c4960ec..8db79e593156 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -25261,7 +25261,6 @@ BTF_ID(func, __x64_sys_exit_group)
BTF_ID(func, do_exit)
BTF_ID(func, do_group_exit)
BTF_ID(func, kthread_complete_and_exit)
-BTF_ID(func, kthread_exit)
BTF_ID(func, make_task_dead)
BTF_SET_END(noreturn_deny)
diff --git a/kernel/kthread.c b/kernel/kthread.c
index 791210daf8b4..1447c14c8540 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -323,7 +323,7 @@ void __noreturn kthread_complete_and_exit(struct completion *comp, long code)
if (comp)
complete(comp);
- kthread_exit(code);
+ do_exit(code);
}
EXPORT_SYMBOL(kthread_complete_and_exit);
@@ -395,7 +395,7 @@ static int kthread(void *_create)
if (!done) {
kfree(create->full_name);
kfree(create);
- kthread_exit(-EINTR);
+ do_exit(-EINTR);
}
self->full_name = create->full_name;
@@ -435,7 +435,7 @@ static int kthread(void *_create)
__kthread_parkme(self);
ret = threadfn(data);
}
- kthread_exit(ret);
+ do_exit(ret);
}
/* called from kernel_clone() to get node information for about to be created task */
@@ -738,7 +738,7 @@ EXPORT_SYMBOL_GPL(kthread_park);
* instead of calling wake_up_process(): the thread will exit without
* calling threadfn().
*
- * If threadfn() may call kthread_exit() itself, the caller must ensure
+ * If threadfn() may call do_exit() itself, the caller must ensure
* task_struct can't go away.
*
* Returns the result of threadfn(), or %-EINTR if wake_up_process()
diff --git a/kernel/module/main.c b/kernel/module/main.c
index c3ce106c70af..340b4dc5c692 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -228,7 +228,7 @@ static int mod_strncmp(const char *str_a, const char *str_b, size_t n)
void __noreturn __module_put_and_kthread_exit(struct module *mod, long code)
{
module_put(mod);
- kthread_exit(code);
+ do_exit(code);
}
EXPORT_SYMBOL(__module_put_and_kthread_exit);
diff --git a/lib/kunit/try-catch.c b/lib/kunit/try-catch.c
index d84a879f0a78..99d9603a2cfd 100644
--- a/lib/kunit/try-catch.c
+++ b/lib/kunit/try-catch.c
@@ -18,7 +18,7 @@
void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)
{
try_catch->try_result = -EFAULT;
- kthread_exit(0);
+ do_exit(0);
}
EXPORT_SYMBOL_GPL(kunit_try_catch_throw);
---
base-commit: c107785c7e8dbabd1c18301a1c362544b5786282
change-id: 20260306-work-kernel-exit-2e8fd9e2c2a1
Le 06/03/2026 à 10:07 PM, 'Christian Brauner' via KUnit Development a
écrit :
> In 28aaa9c39945 ("kthread: consolidate kthread exit paths to prevent use-after-free")
> we folded kthread_exit() into do_exit() when we fixed a nasty UAF bug.
> We left kthread_exit() around as an alias to do_exit(). Remove it
> completely.
>
> Reported-by: Christian Loehle <christian.loehle@arm.com>
> Link: https://lore.kernel.org/1ff1bce2-8bb4-463c-a631-16e14f4ea7e2@arm.com
> Signed-off-by: Christian Brauner <brauner@kernel.org>
> ---
The KUnit bits of this look fine, and it all works for me.
(That being said, I agree that do_exit() isn't as nice a name.)
Acked-by: David Gow <david@davidgow.net>
Tested-by: David Gow <david@davidgow.net>
Cheers,
-- David
More a comment on the previous patch, but I think exporting do_exit which can work on any task is a really bad idea. I'd much rather re-add a kthread_exit wrapper that checks PF_KTHREAD first and only export that, which would obsolete this patch.
On Fri, 6 Mar 2026 at 06:44, Christoph Hellwig <hch@infradead.org> wrote:
>
> More a comment on the previous patch, but I think exporting do_exit
> which can work on any task is a really bad idea.
What is the advantage of a module only being able to do
kthread_exit(), and not able to do a regular do_exit()?
I think the only real advantage of having a "kthread_exit()" is that
it's a nicer name.
Because if that's the main issue, then I agree that "do_exit()" is
really not a great name, and it matches a very traditional "this is an
internal function" naming convention, and not typically a "this is
what random modules should use".
So kthread_exit() is a much better name, but it basically *has* to act
exactly like do_exit(), and adding a limitation to only work on
kthreads doesn't actually seem like an improvement.
Why make a function that is intentionally limited with no real
technical upside? It's not like there's any real reason why a module
couldn't call exit - we may not have exported it before, but we do
have code that looks like it *could* be a module that calls do_exit()
today.
For example, I'm looking at kernel/vhost_task.c, and the only users
are things that *are* modules, and it's not hugely obvious that
there's a big advantage to saying "that task handling has to be
built-in for those modules".
So my reaction is that "no, do_exit() is not a great name, but there's
no real technical upside to havign a separate kthread_exit()"
function.
If it's just about naming, maybe we could just unify it all and call
it "task_exit()" or something?
Linus
On Fri, Mar 06, 2026 at 10:27:26AM -0800, Linus Torvalds wrote: > On Fri, 6 Mar 2026 at 06:44, Christoph Hellwig <hch@infradead.org> wrote: > > > > More a comment on the previous patch, but I think exporting do_exit > > which can work on any task is a really bad idea. > > What is the advantage of a module only being able to do > kthread_exit(), and not able to do a regular do_exit()? Because it can't fuck up the state of random tasks. > I think the only real advantage of having a "kthread_exit()" is that > it's a nicer name. That's another one, but not the main point. > For example, I'm looking at kernel/vhost_task.c, and the only users > are things that *are* modules, and it's not hugely obvious that > there's a big advantage to saying "that task handling has to be > built-in for those modules". That's always built in for various good reasons. As are other random do_exit callers.
On Mon, 9 Mar 2026 at 08:43, Christoph Hellwig <hch@infradead.org> wrote:
>
> Because it can't f&*^ up the state of random tasks.
Christoph, you make no sense.
"do_exit()" cannot mess up random tasks. It can only exit the
*current* task. The task that is running right now in that module.
And exiting a task is about the least effed up you can do to a task
when you are in kernel mode.
Compared to everything else you can do by mistake - like just
corrupting task state randomly - it's a very benign operation, *and*
it is obvious both in source code and in behavior. It's not like it is
some subtle operation.
I'd be *much* more worried about actual subtle bugs, not somebody
explicitly calling exit.
So what is the actual problem? No more random rants. Explain yourself
without making wild handwaving gestures.
Now, there are real exports in this area that are actually strange and
should be removed: for some historical reason we export 'free_task()'
which makes no sense to me at all (but probably did at some point).
Now *that* is a strange export that can mess up another task in major ways.
[ Ok, I was intrigued and I went and dug into history: we used to do
it in the oprofile driver many many moons ago. ]
And since I looked at the history of this all due to that odd export,
that also made it clear that historically we used to export
complete_and_exit(), which was this beauty:
NORET_TYPE void complete_and_exit(struct completion *comp, long code)
{
if (comp)
complete(comp);
do_exit(code);
}
so you could always do "do_exit()" by just doing
"complete_and_exit(NULL, code)".
And yes, that function was exported since at least 2003 (it was
exported even before that, under the name 'up_and_exit()', and that's
the point where I couldn't be bothered any more because it predates
even the old BK history).
Yes, it was indeed renamed to kthread_complete_and_exit() back in
2021, but that wasn't due to any fundamental "it has to work only on
kthreads". It was simply because nothing but kthreads used it - and
because that was also the time when kthread_exit() started doing
*extra* things over and beyond just the regular do_exit().
So it was a practical thing brought on by kthread changes, not some
kind of "exit is evil" thing.
And that "ktrhead_exit() does extra things" was the actual bug that
needed fixing and caused nasty memory corruption due to subtle lack of
cleanup when it was bypassed.
End result: we've never historically felt that exit() was somehow bad,
and when we limited it to kthreads and made it special, it caused
actual bugs.
Linus
On Mon, Mar 09, 2026 at 09:37:26AM -0700, Linus Torvalds wrote: > On Mon, 9 Mar 2026 at 08:43, Christoph Hellwig <hch@infradead.org> wrote: > > > > Because it can't f&*^ up the state of random tasks. > > Christoph, you make no sense. Thanks, I always appreciated rational and detailed answers. > > "do_exit()" cannot mess up random tasks. It can only exit the > *current* task. The task that is running right now in that module. Yes. And random code should not be able to end arbitrary tasks. > So what is the actual problem? No more random rants. Explain yourself > without making wild handwaving gestures. Wow, you're a bit aggressive, aren't you? Maybe take your meds in the morning to stay a bit calmer. As said before I don't think allowing random code (and module is a proxy for that) do exit a task. They really should not be exiting random kthreads either, but certainly not more than that. > Now, there are real exports in this area that are actually strange and > should be removed: for some historical reason we export 'free_task()' > which makes no sense to me at all (but probably did at some point). > > Now *that* is a strange export that can mess up another task in major ways. No argument about that, but that's not the point here.
On Mon, 9 Mar 2026 at 09:37, Linus Torvalds
<torvalds@linux-foundation.org> wrote:
>
> Now, there are real exports in this area that are actually strange and
> should be removed: for some historical reason we export 'free_task()'
> which makes no sense to me at all (but probably did at some point).
>
> Now *that* is a strange export that can mess up another task in major ways.
>
> [ Ok, I was intrigued and I went and dug into history: we used to do
> it in the oprofile driver many many moons ago. ]
It looks like it should not only no longer be exported, it should
actually be static to kernel/fork.c. As far as I can tell, that
historic oprofile use was the only reason ever this was exposed in any
way.
IOW, a patch like the attached is probably a good idea.
Somebody should probably remind me next merge window (I'm not going to
make 7.0 bigger, and I find examples of people using
kretprobe/free_task, which *should* still work just fine with a static
function, but for all I know if the compiler inlines things it will
cause issues).
Or just add this patch to one of the trees scheduled for next merge
window. I'm not currently carrying a linux-next branch (I actively
deleted it because it caused problems due to messing up other peoples
linux-next branches, so if I ever introduce one I will have to come up
with a better name anyway)
Linus
On Fri, Mar 06, 2026 at 10:27:26AM -0800, Linus Torvalds wrote: > On Fri, 6 Mar 2026 at 06:44, Christoph Hellwig <hch@infradead.org> wrote: > > > > More a comment on the previous patch, but I think exporting do_exit > > which can work on any task is a really bad idea. > > What is the advantage of a module only being able to do > kthread_exit(), and not able to do a regular do_exit()? > > I think the only real advantage of having a "kthread_exit()" is that > it's a nicer name. > > Because if that's the main issue, then I agree that "do_exit()" is > really not a great name, and it matches a very traditional "this is an > internal function" naming convention, and not typically a "this is > what random modules should use". > > So kthread_exit() is a much better name, but it basically *has* to act > exactly like do_exit(), and adding a limitation to only work on > kthreads doesn't actually seem like an improvement. > > Why make a function that is intentionally limited with no real > technical upside? It's not like there's any real reason why a module > couldn't call exit - we may not have exported it before, but we do > have code that looks like it *could* be a module that calls do_exit() > today. > > For example, I'm looking at kernel/vhost_task.c, and the only users > are things that *are* modules, and it's not hugely obvious that > there's a big advantage to saying "that task handling has to be > built-in for those modules". > > So my reaction is that "no, do_exit() is not a great name, but there's > no real technical upside to havign a separate kthread_exit()" > function. > > If it's just about naming, maybe we could just unify it all and call > it "task_exit()" or something? I have that as a follow-up series... But I didn't want to muddle the two patches as this one was meant as a clean-up for the kthread_exit() leftover. Now that you've applied the fixup for the BTF thing directly I can send both at the same time. Give me a few minutes. And no, we should definitely not keep kthread_exit() as a separate thing around. That just seems weird and clearly that has led to bugs before. The vhost example is good for another reason: the line between a task acting as a proper kthread and something that is aking to a kthread is very very blurry by now. Let's ignore usermodehelpers for a minute (ugh). There's io workers or more generalized - like vhost - user workers which are hybrid workers that aren't clearly completely userspace conceptually but also aren't clearly kthreads so I'd not want to have special-cases for any of them. task_exit() just should to the right thing no matter what exactly exits. We also have way to many cleanup hooks that need to be called depending on what precisely exists to split this over different helpers.
© 2016 - 2026 Red Hat, Inc.