From: Greg Kroah-Hartman Date: Tue, 17 Mar 2026 11:24:26 +0000 (+0100) Subject: 6.18-stable patches X-Git-Tag: v6.18.19~57 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=67d5d2cb1a1a7a744826b1477665fa7934832fe4;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-stable patches added patches: kthread-consolidate-kthread-exit-paths-to-prevent-use-after-free.patch revert-ptdesc-remove-references-to-folios-from-__pagetable_ctor-and-pagetable_dtor.patch revert-tcpm-allow-looking-for-role_sw-device-in-the-main-node.patch --- diff --git a/queue-6.18/kthread-consolidate-kthread-exit-paths-to-prevent-use-after-free.patch b/queue-6.18/kthread-consolidate-kthread-exit-paths-to-prevent-use-after-free.patch new file mode 100644 index 0000000000..c9718789c2 --- /dev/null +++ b/queue-6.18/kthread-consolidate-kthread-exit-paths-to-prevent-use-after-free.patch @@ -0,0 +1,213 @@ +From 28aaa9c39945b7925a1cc1d513c8f21ed38f5e4f Mon Sep 17 00:00:00 2001 +From: Christian Brauner +Date: Thu, 26 Feb 2026 10:43:55 +0100 +Subject: kthread: consolidate kthread exit paths to prevent use-after-free + +From: Christian Brauner + +commit 28aaa9c39945b7925a1cc1d513c8f21ed38f5e4f upstream. + +Guillaume reported crashes via corrupted RCU callback function pointers +during KUnit testing. The crash was traced back to the pidfs rhashtable +conversion which replaced the 24-byte rb_node with an 8-byte rhash_head +in struct pid, shrinking it from 160 to 144 bytes. + +struct kthread (without CONFIG_BLK_CGROUP) is also 144 bytes. With +CONFIG_SLAB_MERGE_DEFAULT and SLAB_HWCACHE_ALIGN both round up to +192 bytes and share the same slab cache. struct pid.rcu.func and +struct kthread.affinity_node both sit at offset 0x78. + +When a kthread exits via make_task_dead() it bypasses kthread_exit() and +misses the affinity_node cleanup. free_kthread_struct() frees the memory +while the node is still linked into the global kthread_affinity_list. A +subsequent list_del() by another kthread writes through dangling list +pointers into the freed and reused memory, corrupting the pid's +rcu.func pointer. + +Instead of patching free_kthread_struct() to handle the missed cleanup, +consolidate all kthread exit paths. Turn kthread_exit() into a macro +that calls do_exit() and add kthread_do_exit() which is called from +do_exit() for any task with PF_KTHREAD set. This guarantees that +kthread-specific cleanup always happens regardless of the exit path - +make_task_dead(), direct do_exit(), or kthread_exit(). + +Replace __to_kthread() with a new tsk_is_kthread() accessor in the +public header. Export do_exit() since module code using the +kthread_exit() macro now needs it directly. + +Reported-by: Guillaume Tucker +Tested-by: Guillaume Tucker +Tested-by: Mark Brown +Tested-by: David Gow +Cc: +Link: https://lore.kernel.org/all/20260224-mittlerweile-besessen-2738831ae7f6@brauner +Co-developed-by: Linus Torvalds +Fixes: 4d13f4304fa4 ("kthread: Implement preferred affinity") +Signed-off-by: Linus Torvalds +Signed-off-by: Christian Brauner +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/kthread.h | 21 ++++++++++++++++++++- + kernel/exit.c | 6 ++++++ + kernel/kthread.c | 41 +++++------------------------------------ + 3 files changed, 31 insertions(+), 37 deletions(-) + +--- a/include/linux/kthread.h ++++ b/include/linux/kthread.h +@@ -7,6 +7,24 @@ + + struct mm_struct; + ++/* opaque kthread data */ ++struct kthread; ++ ++/* ++ * When "(p->flags & PF_KTHREAD)" is set the task is a kthread and will ++ * always remain a kthread. For kthreads p->worker_private always ++ * points to a struct kthread. For tasks that are not kthreads ++ * p->worker_private is used to point to other things. ++ * ++ * Return NULL for any task that is not a kthread. ++ */ ++static inline struct kthread *tsk_is_kthread(struct task_struct *p) ++{ ++ if (p->flags & PF_KTHREAD) ++ return p->worker_private; ++ return NULL; ++} ++ + __printf(4, 5) + struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), + void *data, +@@ -98,8 +116,9 @@ void *kthread_probe_data(struct task_str + int kthread_park(struct task_struct *k); + void kthread_unpark(struct task_struct *k); + void kthread_parkme(void); +-void kthread_exit(long result) __noreturn; ++#define kthread_exit(result) do_exit(result) + void kthread_complete_and_exit(struct completion *, long) __noreturn; ++void kthread_do_exit(struct kthread *, long); + + int kthreadd(void *unused); + extern struct task_struct *kthreadd_task; +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -897,11 +897,16 @@ static void synchronize_group_exit(struc + void __noreturn do_exit(long code) + { + struct task_struct *tsk = current; ++ struct kthread *kthread; + int group_dead; + + WARN_ON(irqs_disabled()); + WARN_ON(tsk->plug); + ++ kthread = tsk_is_kthread(tsk); ++ if (unlikely(kthread)) ++ kthread_do_exit(kthread, code); ++ + kcov_task_exit(tsk); + kmsan_task_exit(tsk); + +@@ -1008,6 +1013,7 @@ void __noreturn do_exit(long code) + lockdep_free_task(tsk); + do_task_dead(); + } ++EXPORT_SYMBOL(do_exit); + + void __noreturn make_task_dead(int signr) + { +--- a/kernel/kthread.c ++++ b/kernel/kthread.c +@@ -85,24 +85,6 @@ static inline struct kthread *to_kthread + return k->worker_private; + } + +-/* +- * Variant of to_kthread() that doesn't assume @p is a kthread. +- * +- * When "(p->flags & PF_KTHREAD)" is set the task is a kthread and will +- * always remain a kthread. For kthreads p->worker_private always +- * points to a struct kthread. For tasks that are not kthreads +- * p->worker_private is used to point to other things. +- * +- * Return NULL for any task that is not a kthread. +- */ +-static inline struct kthread *__to_kthread(struct task_struct *p) +-{ +- void *kthread = p->worker_private; +- if (kthread && !(p->flags & PF_KTHREAD)) +- kthread = NULL; +- return kthread; +-} +- + void get_kthread_comm(char *buf, size_t buf_size, struct task_struct *tsk) + { + struct kthread *kthread = to_kthread(tsk); +@@ -193,7 +175,7 @@ EXPORT_SYMBOL_GPL(kthread_should_park); + + bool kthread_should_stop_or_park(void) + { +- struct kthread *kthread = __to_kthread(current); ++ struct kthread *kthread = tsk_is_kthread(current); + + if (!kthread) + return false; +@@ -234,7 +216,7 @@ EXPORT_SYMBOL_GPL(kthread_freezable_shou + */ + void *kthread_func(struct task_struct *task) + { +- struct kthread *kthread = __to_kthread(task); ++ struct kthread *kthread = tsk_is_kthread(task); + if (kthread) + return kthread->threadfn; + return NULL; +@@ -266,7 +248,7 @@ EXPORT_SYMBOL_GPL(kthread_data); + */ + void *kthread_probe_data(struct task_struct *task) + { +- struct kthread *kthread = __to_kthread(task); ++ struct kthread *kthread = tsk_is_kthread(task); + void *data = NULL; + + if (kthread) +@@ -309,19 +291,8 @@ void kthread_parkme(void) + } + EXPORT_SYMBOL_GPL(kthread_parkme); + +-/** +- * kthread_exit - Cause the current kthread return @result to kthread_stop(). +- * @result: The integer value to return to kthread_stop(). +- * +- * While kthread_exit can be called directly, it exists so that +- * functions which do some additional work in non-modular code such as +- * module_put_and_kthread_exit can be implemented. +- * +- * Does not return. +- */ +-void __noreturn kthread_exit(long result) ++void kthread_do_exit(struct kthread *kthread, long result) + { +- struct kthread *kthread = to_kthread(current); + kthread->result = result; + if (!list_empty(&kthread->hotplug_node)) { + mutex_lock(&kthreads_hotplug_lock); +@@ -333,9 +304,7 @@ void __noreturn kthread_exit(long result + kthread->preferred_affinity = NULL; + } + } +- do_exit(0); + } +-EXPORT_SYMBOL(kthread_exit); + + /** + * kthread_complete_and_exit - Exit the current kthread. +@@ -682,7 +651,7 @@ void kthread_set_per_cpu(struct task_str + + bool kthread_is_per_cpu(struct task_struct *p) + { +- struct kthread *kthread = __to_kthread(p); ++ struct kthread *kthread = tsk_is_kthread(p); + if (!kthread) + return false; + diff --git a/queue-6.18/revert-ptdesc-remove-references-to-folios-from-__pagetable_ctor-and-pagetable_dtor.patch b/queue-6.18/revert-ptdesc-remove-references-to-folios-from-__pagetable_ctor-and-pagetable_dtor.patch new file mode 100644 index 0000000000..9d1b12aa25 --- /dev/null +++ b/queue-6.18/revert-ptdesc-remove-references-to-folios-from-__pagetable_ctor-and-pagetable_dtor.patch @@ -0,0 +1,83 @@ +From 2d28ed588f8d7d0d41b0a4fad7f0d05e4bbf1797 Mon Sep 17 00:00:00 2001 +From: Axel Rasmussen +Date: Tue, 24 Feb 2026 16:24:34 -0800 +Subject: Revert "ptdesc: remove references to folios from __pagetable_ctor() and pagetable_dtor()" + +From: Axel Rasmussen + +commit 2d28ed588f8d7d0d41b0a4fad7f0d05e4bbf1797 upstream. + +This change swapped out mod_node_page_state for lruvec_stat_add_folio. +But, these two APIs are not interchangeable: the lruvec version also +increments memcg stats, in addition to "global" pgdat stats. + +So after this change, the "pagetables" memcg stat in memory.stat always +yields "0", which is a userspace visible regression. + +I tried to look for a refactor where we add a variant of +lruvec_stat_mod_folio which takes a pgdat and a memcg instead of a folio, +to try to adhere to the spirit of the original patch. But at the end of +the day this just means we have to call folio_memcg(ptdesc_folio(ptdesc)) +anyway, which doesn't really accomplish much. + +This regression is visible in master as well as 6.18 stable, so CC stable +too. + +Link: https://lkml.kernel.org/r/20260225002434.2953895-1-axelrasmussen@google.com +Fixes: f0c92726e89f ("ptdesc: remove references to folios from __pagetable_ctor() and pagetable_dtor()") +Signed-off-by: Axel Rasmussen +Acked-by: Shakeel Butt +Acked-by: Johannes Weiner +Reviewed-by: Vishal Moola (Oracle) +Cc: David Hildenbrand +Cc: Liam Howlett +Cc: Lorenzo Stoakes +Cc: Matthew Wilcox (Oracle) +Cc: Michal Hocko +Cc: Mike Rapoport +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: Roman Gushchin +Cc: Muchun Song +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/mm.h | 17 ++++++----------- + 1 file changed, 6 insertions(+), 11 deletions(-) + +--- a/include/linux/mm.h ++++ b/include/linux/mm.h +@@ -3138,26 +3138,21 @@ static inline bool ptlock_init(struct pt + static inline void ptlock_free(struct ptdesc *ptdesc) {} + #endif /* defined(CONFIG_SPLIT_PTE_PTLOCKS) */ + +-static inline unsigned long ptdesc_nr_pages(const struct ptdesc *ptdesc) +-{ +- return compound_nr(ptdesc_page(ptdesc)); +-} +- + static inline void __pagetable_ctor(struct ptdesc *ptdesc) + { +- pg_data_t *pgdat = NODE_DATA(memdesc_nid(ptdesc->pt_flags)); ++ struct folio *folio = ptdesc_folio(ptdesc); + +- __SetPageTable(ptdesc_page(ptdesc)); +- mod_node_page_state(pgdat, NR_PAGETABLE, ptdesc_nr_pages(ptdesc)); ++ __folio_set_pgtable(folio); ++ lruvec_stat_add_folio(folio, NR_PAGETABLE); + } + + static inline void pagetable_dtor(struct ptdesc *ptdesc) + { +- pg_data_t *pgdat = NODE_DATA(memdesc_nid(ptdesc->pt_flags)); ++ struct folio *folio = ptdesc_folio(ptdesc); + + ptlock_free(ptdesc); +- __ClearPageTable(ptdesc_page(ptdesc)); +- mod_node_page_state(pgdat, NR_PAGETABLE, -ptdesc_nr_pages(ptdesc)); ++ __folio_clear_pgtable(folio); ++ lruvec_stat_sub_folio(folio, NR_PAGETABLE); + } + + static inline void pagetable_dtor_free(struct ptdesc *ptdesc) diff --git a/queue-6.18/revert-tcpm-allow-looking-for-role_sw-device-in-the-main-node.patch b/queue-6.18/revert-tcpm-allow-looking-for-role_sw-device-in-the-main-node.patch new file mode 100644 index 0000000000..7a428b051f --- /dev/null +++ b/queue-6.18/revert-tcpm-allow-looking-for-role_sw-device-in-the-main-node.patch @@ -0,0 +1,42 @@ +From 6b275bfaa16be3fb1689fa6794e445ecd127a1b4 Mon Sep 17 00:00:00 2001 +From: Xu Yang +Date: Mon, 9 Mar 2026 15:43:12 +0800 +Subject: Revert "tcpm: allow looking for role_sw device in the main node" + +From: Xu Yang + +commit 6b275bfaa16be3fb1689fa6794e445ecd127a1b4 upstream. + +This reverts commit 1366cd228b0c67b60a2c0c26ef37fe9f7cfedb7f. + +The fwnode_usb_role_switch_get() returns NULL only if no connection is +found, returns ERR_PTR(-EPROBE_DEFER) if connection is found but deferred +probe is needed, or a valid pointer of usb_role_switch. + +When switching from a NULL check to IS_ERR_OR_NULL(), usb_role_switch_get() +returns NULL and overwrites the ERR_PTR(-EPROBE_DEFER) returned by +fwnode_usb_role_switch_get(). This causes the deferred probe indication to +be lost, preventing the USB role switch from ever being retrieved. + +Fixes: 1366cd228b0c ("tcpm: allow looking for role_sw device in the main node") +Cc: stable +Signed-off-by: Xu Yang +Tested-by: Arnaud Ferraris +Reviewed-by: Heikki Krogerus +Link: https://patch.msgid.link/20260309074313.2809867-2-xu.yang_2@nxp.com +Signed-off-by: Greg Kroah-Hartman +--- + drivers/usb/typec/tcpm/tcpm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -7877,7 +7877,7 @@ struct tcpm_port *tcpm_register_port(str + port->partner_desc.identity = &port->partner_ident; + + port->role_sw = fwnode_usb_role_switch_get(tcpc->fwnode); +- if (IS_ERR_OR_NULL(port->role_sw)) ++ if (!port->role_sw) + port->role_sw = usb_role_switch_get(port->dev); + if (IS_ERR(port->role_sw)) { + err = PTR_ERR(port->role_sw); diff --git a/queue-6.18/series b/queue-6.18/series index d06a6f0aa0..a357e878bd 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -176,3 +176,6 @@ i3c-dw-i3c-master-set-sir_reject-in-dat-on-device-at.patch powerpc-perf-check-that-current-mm-is-alive-before-g.patch scsi-ufs-core-fix-serror-in-ufshcd_rtc_work-during-u.patch scsi-hisi_sas-fix-null-pointer-exception-during-user.patch +revert-tcpm-allow-looking-for-role_sw-device-in-the-main-node.patch +revert-ptdesc-remove-references-to-folios-from-__pagetable_ctor-and-pagetable_dtor.patch +kthread-consolidate-kthread-exit-paths-to-prevent-use-after-free.patch