--- /dev/null
+From 28aaa9c39945b7925a1cc1d513c8f21ed38f5e4f Mon Sep 17 00:00:00 2001
+From: Christian Brauner <brauner@kernel.org>
+Date: Thu, 26 Feb 2026 10:43:55 +0100
+Subject: kthread: consolidate kthread exit paths to prevent use-after-free
+
+From: Christian Brauner <brauner@kernel.org>
+
+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 <gtucker@gtucker.io>
+Tested-by: Guillaume Tucker <gtucker@gtucker.io>
+Tested-by: Mark Brown <broonie@kernel.org>
+Tested-by: David Gow <davidgow@google.com>
+Cc: <stable@vger.kernel.org>
+Link: https://lore.kernel.org/all/20260224-mittlerweile-besessen-2738831ae7f6@brauner
+Co-developed-by: Linus Torvalds <torvalds@linux-foundation.org>
+Fixes: 4d13f4304fa4 ("kthread: Implement preferred affinity")
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ 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;
+
--- /dev/null
+From 2d28ed588f8d7d0d41b0a4fad7f0d05e4bbf1797 Mon Sep 17 00:00:00 2001
+From: Axel Rasmussen <axelrasmussen@google.com>
+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 <axelrasmussen@google.com>
+
+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 <axelrasmussen@google.com>
+Acked-by: Shakeel Butt <shakeel.butt@linux.dev>
+Acked-by: Johannes Weiner <hannes@cmpxchg.org>
+Reviewed-by: Vishal Moola (Oracle) <vishal.moola@gmail.com>
+Cc: David Hildenbrand <david@kernel.org>
+Cc: Liam Howlett <liam.howlett@oracle.com>
+Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Mike Rapoport <rppt@kernel.org>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@suse.cz>
+Cc: Roman Gushchin <roman.gushchin@linux.dev>
+Cc: Muchun Song <muchun.song@linux.dev>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ 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)
--- /dev/null
+From 6b275bfaa16be3fb1689fa6794e445ecd127a1b4 Mon Sep 17 00:00:00 2001
+From: Xu Yang <xu.yang_2@nxp.com>
+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 <xu.yang_2@nxp.com>
+
+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 <stable@kernel.org>
+Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
+Tested-by: Arnaud Ferraris <arnaud.ferraris@collabora.com>
+Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Link: https://patch.msgid.link/20260309074313.2809867-2-xu.yang_2@nxp.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ 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);