]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
Fixes for all trees
authorSasha Levin <sashal@kernel.org>
Fri, 31 Jul 2026 02:39:37 +0000 (22:39 -0400)
committerSasha Levin <sashal@kernel.org>
Fri, 31 Jul 2026 02:39:37 +0000 (22:39 -0400)
Signed-off-by: Sasha Levin <sashal@kernel.org>
22 files changed:
staging-6.1/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch [new file with mode: 0644]
staging-6.1/mm-damon-core-disallow-overlapping-input-ranges-for-.patch [new file with mode: 0644]
staging-6.1/mm-damon-core-validate-ranges-in-damon_set_regions.patch [new file with mode: 0644]
staging-6.1/netfilter-nf_conntrack_expect-restore-helper-propaga.patch [new file with mode: 0644]
staging-6.1/series [new file with mode: 0644]
staging-6.12/kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch [new file with mode: 0644]
staging-6.12/kunit-tool-terminate-kernel-under-test-on-sigint.patch [new file with mode: 0644]
staging-6.12/netfilter-nf_conntrack_expect-restore-helper-propaga.patch [new file with mode: 0644]
staging-6.12/series [new file with mode: 0644]
staging-6.12/um-add-os_set_pdeathsig-helper-function.patch [new file with mode: 0644]
staging-6.12/um-preserve-errno-within-signal-handler.patch [new file with mode: 0644]
staging-6.12/um-set-parent-death-signal-for-ubd-io-thread-process.patch [new file with mode: 0644]
staging-6.12/um-set-parent-death-signal-for-userspace-process.patch [new file with mode: 0644]
staging-6.12/um-set-parent-death-signal-for-winch-thread-process.patch [new file with mode: 0644]
staging-6.12/um-set-parent-death-signal-for-write_sigio-thread-pr.patch [new file with mode: 0644]
staging-6.12/um-use-os_set_pdeathsig-helper-in-winch-thread-proce.patch [new file with mode: 0644]
staging-6.18/kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch [new file with mode: 0644]
staging-6.18/kunit-tool-terminate-kernel-under-test-on-sigint.patch [new file with mode: 0644]
staging-6.18/netfilter-nf_conntrack_expect-restore-helper-propaga.patch [new file with mode: 0644]
staging-6.18/series [new file with mode: 0644]
staging-6.6/netfilter-nf_conntrack_expect-restore-helper-propaga.patch [new file with mode: 0644]
staging-6.6/series [new file with mode: 0644]

diff --git a/staging-6.1/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch b/staging-6.1/io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch
new file mode 100644 (file)
index 0000000..85eee11
--- /dev/null
@@ -0,0 +1,89 @@
+From e80be64690b11276f3e38b4b25875f2f2c8fa60f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 20:45:51 +0800
+Subject: io_uring/rw: fix missing ERESTARTSYS conversion in read paths
+
+From: Yitang Yang <yi1tang.yang@gmail.com>
+
+Commit ab05caca123c6d0b41850b7c05b246e4dca4a770 upstream.
+
+Both read and write may receive internal restart error codes from
+the filesystem layer and should be converted to -EINTR. However,
+when multishot read support was added, the error code normalization
+was lost for both io_read() and io_read_mshot().
+
+Extract the conversion into io_fixup_restart_res() and apply it
+in all three locations: io_rw_done(), io_read(), and io_read_mshot().
+
+Fixes: a08d195b586a ("io_uring/rw: split io_read() into a helper")
+Cc: stable@vger.kernel.org
+Signed-off-by: Yitang Yang <yi1tang.yang@gmail.com>
+Link: https://patch.msgid.link/20260722124551.130563-1-yi1tang.yang@gmail.com
+Signed-off-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ io_uring/rw.c | 28 +++++++++++++++++++---------
+ 1 file changed, 19 insertions(+), 9 deletions(-)
+
+diff --git a/io_uring/rw.c b/io_uring/rw.c
+index b75f62dccce6..cbd3ee9a9373 100644
+--- a/io_uring/rw.c
++++ b/io_uring/rw.c
+@@ -129,27 +129,37 @@ void io_readv_writev_cleanup(struct io_kiocb *req)
+       kfree(io->free_iovec);
+ }
+-static inline void io_rw_done(struct kiocb *kiocb, ssize_t ret)
++static inline ssize_t io_fixup_restart_res(ssize_t ret)
+ {
+       switch (ret) {
+-      case -EIOCBQUEUED:
+-              break;
+       case -ERESTARTSYS:
+       case -ERESTARTNOINTR:
+       case -ERESTARTNOHAND:
+       case -ERESTART_RESTARTBLOCK:
+               /*
+                * We can't just restart the syscall, since previously
+-               * submitted sqes may already be in progress. Just fail this
+-               * IO with EINTR.
++               * submitted sqes may already be in progress. Just fail
++               * this IO with EINTR.
+                */
+-              ret = -EINTR;
+-              fallthrough;
++              return -EINTR;
+       default:
+-              kiocb->ki_complete(kiocb, ret);
++              return ret;
+       }
+ }
++static inline void io_rw_done(struct kiocb *kiocb, ssize_t ret)
++{
++      /* IO was queued async, completion will happen later */
++      if (ret == -EIOCBQUEUED)
++              return;
++
++      /* transform internal restart error codes */
++      if (unlikely(ret < 0))
++              ret = io_fixup_restart_res(ret);
++
++      kiocb->ki_complete(kiocb, ret);
++}
++
+ static inline loff_t *io_kiocb_update_pos(struct io_kiocb *req)
+ {
+       struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw);
+@@ -854,7 +864,7 @@ int io_read(struct io_kiocb *req, unsigned int issue_flags)
+       if (ret >= 0)
+               return kiocb_done(req, ret, issue_flags);
+-      return ret;
++      return io_fixup_restart_res(ret);
+ }
+ static bool io_kiocb_start_write(struct io_kiocb *req, struct kiocb *kiocb)
+-- 
+2.53.0
+
diff --git a/staging-6.1/mm-damon-core-disallow-overlapping-input-ranges-for-.patch b/staging-6.1/mm-damon-core-disallow-overlapping-input-ranges-for-.patch
new file mode 100644 (file)
index 0000000..0868730
--- /dev/null
@@ -0,0 +1,77 @@
+From af70be1962666d603abd5411e68bd92456280e9c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 18:20:58 -0700
+Subject: mm/damon/core: disallow overlapping input ranges for
+ damon_set_regions()
+
+From: SJ Park <sj@kernel.org>
+
+commit 954157679ec34661c2e87e7eb796104a797c32db upstream.
+
+damon_set_regions() assumes the input ranges are sorted by the address and
+don't overlap each other.  Hence the assumption was initially to be
+explicitly validated.  But commit 97d482f4592f ("mm/damon/sysfs: reuse
+damon_set_regions() for regions setting") has mistakenly removed the
+validation.
+
+This can make DAMON behave in unexpected ways.  At the best, the
+monitoring results snapshot will just look weird since there will be
+overlapping regions.  DAMOS will also work weirdly, applying the same
+action multiple times for overlapping regions, and make DAMOS quota weird.
+More seriously, depending on the setup and regions updates sequence,
+negative size regions can be made.  It will trigger WARN_ONCE() if the
+kernel is built with CONFIG_DAMON_DEBUG_SANITY=y.  Depending on the
+monitoring results, the negative size region can further trigger division
+by zero in damon_merge_two_regions().
+
+Note that some of the consequences including the WARN_ONCE() and the
+divide by zero depend on commits that were introduced after the root cause
+commit 97d482f4592f ("mm/damon/sysfs: reuse damon_set_regions() for
+regions setting").
+
+Fix the problems by checking the assumption and returning an error if
+the input ranges don't meet the assumption.
+
+The issue was discovered [1] by Sashiko.
+
+Link: https://lore.kernel.org/20260703165610.92894-1-sj@kernel.org
+Link: https://lore.kernel.org/20260630041806.151124-1-sj@kernel.org [1]
+Fixes: 97d482f4592f ("mm/damon/sysfs: reuse damon_set_regions() for regions setting")
+Signed-off-by: SJ Park <sj@kernel.org>
+Cc: <stable@vger.kernel.org> # 5.19.x
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: SJ Park <sj@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ mm/damon/core.c | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/mm/damon/core.c b/mm/damon/core.c
+index 0a0bb033f28a..dd4eafe8b961 100644
+--- a/mm/damon/core.c
++++ b/mm/damon/core.c
+@@ -210,12 +210,19 @@ int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges,
+ {
+       struct damon_region *r, *next;
+       unsigned int i;
++      unsigned long last_end;
+       int err;
+       for (i = 0; i < nr_ranges; i++) {
+-              if (ALIGN_DOWN(ranges[i].start, DAMON_MIN_REGION) >=
+-                              ALIGN(ranges[i].end, DAMON_MIN_REGION))
++              unsigned long start, end;
++
++              start = ALIGN_DOWN(ranges[i].start, DAMON_MIN_REGION);
++              end = ALIGN(ranges[i].end, DAMON_MIN_REGION);
++              if (start >= end)
++                      return -EINVAL;
++              if (i > 0 && last_end > start)
+                       return -EINVAL;
++              last_end = end;
+       }
+       /* Remove regions which are not in the new ranges */
+-- 
+2.53.0
+
diff --git a/staging-6.1/mm-damon-core-validate-ranges-in-damon_set_regions.patch b/staging-6.1/mm-damon-core-validate-ranges-in-damon_set_regions.patch
new file mode 100644 (file)
index 0000000..61daaed
--- /dev/null
@@ -0,0 +1,66 @@
+From f274de37aa67665a98e14a92c2e88cfaef5fa13f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 18:01:51 -0700
+Subject: mm/damon/core: validate ranges in damon_set_regions()
+
+From: SJ Park <sj@kernel.org>
+
+commit 1292c0ecb1caefb8ca064a3639d5673991e8810c upstream.
+
+DAMON core logic assumes zero length regions don't exist.  However, a few
+DAMON API callers including DAMON_SYSFS, DAMON_RECLAIM and DAMON_LRU_SORT
+allow users to set empty monitoring target regions.  This could result in
+WARN_ONCE() on CONFIG_DAMON_DEBUG_SANITY enabled kernel, and
+divide-by-zero from damon_merge_two_regions().
+
+For example, the WANR_ONCE() can be triggered like below.
+
+    # grep DAMON_DEBUG_SANITY /boot/config-$(uname -r)
+    # CONFIG_DAMON_DEBUG_SANITY=y
+    # damo start
+    # cd /sys/kernel/mm/damon/admin/kdamonds/0
+    # echo 0 > contexts/0/targets/0/regions/0/start
+    # echo 0 > contexts/0/targets/0/regions/0/end
+    # echo commit > state
+    # dmesg
+    [....]
+    [   73.705780] ------------[ cut here ]------------
+    [   73.707552] start 0 >= end 0
+    [   73.708452] WARNING: mm/damon/core.c:359 at damon_new_region+0x6e/0x80, CPU#1: kdamond.0/758
+    [...]
+
+All DAMON API callers eventually use damon_set_regions() to setup the
+regions.  Add the validation logic in the function.
+
+Link: https://lore.kernel.org/20260630035221.146458-1-sj@kernel.org
+Fixes: 43b0536cb471 ("mm/damon: introduce DAMON-based Reclamation (DAMON_RECLAIM)")
+Signed-off-by: SJ Park <sj@kernel.org>
+Cc: Yang yingliang <yangyingliang@huawei.com>
+Cc: <stable@vger.kernel.org> # 5.16.x
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: SJ Park <sj@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ mm/damon/core.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/mm/damon/core.c b/mm/damon/core.c
+index 4b434ebd37c5..0a0bb033f28a 100644
+--- a/mm/damon/core.c
++++ b/mm/damon/core.c
+@@ -212,6 +212,12 @@ int damon_set_regions(struct damon_target *t, struct damon_addr_range *ranges,
+       unsigned int i;
+       int err;
++      for (i = 0; i < nr_ranges; i++) {
++              if (ALIGN_DOWN(ranges[i].start, DAMON_MIN_REGION) >=
++                              ALIGN(ranges[i].end, DAMON_MIN_REGION))
++                      return -EINVAL;
++      }
++
+       /* Remove regions which are not in the new ranges */
+       damon_for_each_region_safe(r, next, t) {
+               for (i = 0; i < nr_ranges; i++) {
+-- 
+2.53.0
+
diff --git a/staging-6.1/netfilter-nf_conntrack_expect-restore-helper-propaga.patch b/staging-6.1/netfilter-nf_conntrack_expect-restore-helper-propaga.patch
new file mode 100644 (file)
index 0000000..7787ff1
--- /dev/null
@@ -0,0 +1,269 @@
+From d56e298bedc6043cf9d784d7c44e8d38c01fbcaf Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 7 May 2026 13:00:28 +0200
+Subject: netfilter: nf_conntrack_expect: restore helper propagation via
+ expectation
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit dcb0f9aefdd604d36710fda53c25bd7cf4a3e37a ]
+
+A recent series to fix expectations broke helper propagation via
+expectation, this mechanism is used by the sip and h323 helper. This
+also propagates the conntrack helper to expected connections. I changed
+semantics of exp->helper which now tells us the actual helper that
+created the expectation.
+
+Add an explicit assign_helper field to expectations for this purpose
+and update helpers to use it.
+
+Restore this feature for userspace conntrack helper via ctnetlink
+nfqueue integration so it is again possible to attach a helper to an
+expectation, where it makes sense. This is not restored via ctnetlink
+expectation creation as there is no client for such feature. Use the
+expectation layer 4 protocol number for the helper lookup for
+consistency.
+
+Make sure the expectation using this helper propagation mechanism also
+go away when the helper is unregistered.
+
+Fixes: 9c42bc9db90a ("netfilter: nf_conntrack_expect: honor expectation helper field")
+Fixes: 917b61fa2042 ("netfilter: ctnetlink: ignore explicit helper on new expectations")
+Reported-by: Ilya Maximets <i.maximets@ovn.org>
+Tested-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/netfilter/nf_conntrack_expect.h |  5 ++++-
+ net/netfilter/nf_conntrack_broadcast.c      |  1 +
+ net/netfilter/nf_conntrack_core.c           |  7 +++++--
+ net/netfilter/nf_conntrack_expect.c         |  1 +
+ net/netfilter/nf_conntrack_h323_main.c      | 12 ++++++------
+ net/netfilter/nf_conntrack_helper.c         |  5 +++++
+ net/netfilter/nf_conntrack_netlink.c        | 18 ++++++++++++++++--
+ net/netfilter/nf_conntrack_sip.c            |  2 +-
+ 8 files changed, 39 insertions(+), 12 deletions(-)
+
+diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
+index e9a8350e7ccf..80f50fd0f7ad 100644
+--- a/include/net/netfilter/nf_conntrack_expect.h
++++ b/include/net/netfilter/nf_conntrack_expect.h
+@@ -45,9 +45,12 @@ struct nf_conntrack_expect {
+       void (*expectfn)(struct nf_conn *new,
+                        struct nf_conntrack_expect *this);
+-      /* Helper to assign to new connection */
++      /* Helper that created this expectation */
+       struct nf_conntrack_helper __rcu *helper;
++      /* Helper to assign to new connection */
++      struct nf_conntrack_helper __rcu *assign_helper;
++
+       /* The conntrack of the master connection */
+       struct nf_conn *master;
+diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c
+index d44d9379a8a0..ef8a7ca8c116 100644
+--- a/net/netfilter/nf_conntrack_broadcast.c
++++ b/net/netfilter/nf_conntrack_broadcast.c
+@@ -72,6 +72,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb,
+       exp->flags                = NF_CT_EXPECT_PERMANENT;
+       exp->class                = NF_CT_EXPECT_CLASS_DEFAULT;
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
+index f5c466ea1e7d..342627b0d32b 100644
+--- a/net/netfilter/nf_conntrack_core.c
++++ b/net/netfilter/nf_conntrack_core.c
+@@ -1800,16 +1800,19 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
+               spin_lock_bh(&nf_conntrack_expect_lock);
+               exp = nf_ct_find_expectation(net, zone, tuple, !tmpl || nf_ct_is_confirmed(tmpl));
+               if (exp) {
++                      struct nf_conntrack_helper *assign_helper;
++
+                       pr_debug("expectation arrives ct=%p exp=%p\n",
+                                ct, exp);
+                       /* Welcome, Mr. Bond.  We've been expecting you... */
+                       __set_bit(IPS_EXPECTED_BIT, &ct->status);
+                       /* exp->master safe, refcnt bumped in nf_ct_find_expectation */
+                       ct->master = exp->master;
+-                      if (exp->helper) {
++                      assign_helper = rcu_dereference(exp->assign_helper);
++                      if (assign_helper) {
+                               help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
+                               if (help)
+-                                      rcu_assign_pointer(help->helper, exp->helper);
++                                      rcu_assign_pointer(help->helper, assign_helper);
+                       }
+ #ifdef CONFIG_NF_CONNTRACK_MARK
+diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
+index 379711ea5ab6..34324dece89d 100644
+--- a/net/netfilter/nf_conntrack_expect.c
++++ b/net/netfilter/nf_conntrack_expect.c
+@@ -344,6 +344,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class,
+               helper = rcu_dereference(help->helper);
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c
+index 791aafe9f396..c42547284f35 100644
+--- a/net/netfilter/nf_conntrack_h323_main.c
++++ b/net/netfilter/nf_conntrack_h323_main.c
+@@ -642,7 +642,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, &nf_conntrack_helper_h245);
++      rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -766,7 +766,7 @@ static int expect_callforwarding(struct sk_buff *skb,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -1233,7 +1233,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct,
+                               &ct->tuplehash[!dir].tuple.src.u3 : NULL,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       exp->flags = NF_CT_EXPECT_PERMANENT;    /* Accept multiple calls */
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+@@ -1305,7 +1305,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_UDP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_ras);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect RAS ");
+@@ -1522,7 +1522,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+@@ -1576,7 +1576,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c
+index 8e72c3d4db4a..efa080cb1709 100644
+--- a/net/netfilter/nf_conntrack_helper.c
++++ b/net/netfilter/nf_conntrack_helper.c
+@@ -422,6 +422,11 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data)
+       this = rcu_dereference_protected(exp->helper,
+                                        lockdep_is_held(&nf_conntrack_expect_lock));
++      if (this == me)
++              return true;
++
++      this = rcu_dereference_protected(exp->assign_helper,
++                                       lockdep_is_held(&nf_conntrack_expect_lock));
+       return this == me;
+ }
+diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
+index a3b18042adec..55bc5626b967 100644
+--- a/net/netfilter/nf_conntrack_netlink.c
++++ b/net/netfilter/nf_conntrack_netlink.c
+@@ -2628,6 +2628,7 @@ static const struct nla_policy exp_nla_policy[CTA_EXPECT_MAX+1] = {
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr *const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask);
+@@ -2854,6 +2855,7 @@ static int
+ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+                            u32 portid, u32 report)
+ {
++      struct nf_conntrack_helper *assign_helper = NULL;
+       struct nlattr *cda[CTA_EXPECT_MAX+1];
+       struct nf_conntrack_tuple tuple, mask;
+       struct nf_conntrack_expect *exp;
+@@ -2869,8 +2871,18 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+       if (err < 0)
+               return err;
++      if (cda[CTA_EXPECT_HELP_NAME]) {
++              const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]);
++
++              assign_helper = __nf_conntrack_helper_find(helpname,
++                                                         nf_ct_l3num(ct),
++                                                         tuple.dst.protonum);
++              if (!assign_helper)
++                      return -EOPNOTSUPP;
++      }
++
+       exp = ctnetlink_alloc_expect((const struct nlattr * const *)cda, ct,
+-                                   &tuple, &mask);
++                                   assign_helper, &tuple, &mask);
+       if (IS_ERR(exp))
+               return PTR_ERR(exp);
+@@ -3509,6 +3521,7 @@ ctnetlink_parse_expect_nat(const struct nlattr *attr,
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask)
+ {
+@@ -3562,6 +3575,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
+       exp->zone = ct->zone;
+ #endif
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, assign_helper);
+       exp->tuple = *tuple;
+       exp->mask.src.u3 = mask->src.u3;
+       exp->mask.src.u.all = mask->src.u.all;
+@@ -3617,7 +3631,7 @@ ctnetlink_create_expect(struct net *net,
+       ct = nf_ct_tuplehash_to_ctrack(h);
+       rcu_read_lock();
+-      exp = ctnetlink_alloc_expect(cda, ct, &tuple, &mask);
++      exp = ctnetlink_alloc_expect(cda, ct, NULL, &tuple, &mask);
+       if (IS_ERR(exp)) {
+               err = PTR_ERR(exp);
+               goto err_rcu;
+diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
+index ec31611b7a29..0af6ede4b92c 100644
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -1384,7 +1384,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff,
+       nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
+                         saddr, &daddr, proto, NULL, &port);
+       exp->timeout.expires = sip_timeout * HZ;
+-      rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, helper);
+       exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
+       hooks = rcu_dereference(nf_nat_sip_hooks);
+-- 
+2.53.0
+
diff --git a/staging-6.1/series b/staging-6.1/series
new file mode 100644 (file)
index 0000000..5d3a24c
--- /dev/null
@@ -0,0 +1,4 @@
+io_uring-rw-fix-missing-erestartsys-conversion-in-re.patch
+mm-damon-core-validate-ranges-in-damon_set_regions.patch
+mm-damon-core-disallow-overlapping-input-ranges-for-.patch
+netfilter-nf_conntrack_expect-restore-helper-propaga.patch
diff --git a/staging-6.12/kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch b/staging-6.12/kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch
new file mode 100644 (file)
index 0000000..34bb3b5
--- /dev/null
@@ -0,0 +1,128 @@
+From c386b26dbc1f2cc87b2743aa63f3070b229d5ccb Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:18 -0700
+Subject: kunit: tool: skip stty when stdin is not a tty
+
+From: Shuvam Pandey <shuvampandey1@gmail.com>
+
+commit e42c349f4cdfa43cb39a68c8f764f8cafc23a9a9 upstream.
+
+run_kernel() cleanup and signal_handler() invoke stty unconditionally.
+When stdin is not a tty (for example in CI or unit tests), this writes
+noise to stderr.
+
+Call stty only when stdin is a tty.
+
+Add regression tests for these paths:
+- run_kernel() with non-tty stdin
+- signal_handler() with non-tty stdin
+- signal_handler() with tty stdin
+
+Signed-off-by: Shuvam Pandey <shuvampandey1@gmail.com>
+Reviewed-by: David Gow <david@davidgow.net>
+Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
+[florian: add missing 'import sys' required by sys.stdin.isatty(); the
+module was already present in the mainline tree before this commit but
+was absent from the 6.12 base of kunit_kernel.py]
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/kunit/kunit_kernel.py    | 11 +++++--
+ tools/testing/kunit/kunit_tool_test.py | 42 ++++++++++++++++++++++++++
+ 2 files changed, 51 insertions(+), 2 deletions(-)
+
+diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py
+index 3f2979f2f6ea..c09b66ba734b 100644
+--- a/tools/testing/kunit/kunit_kernel.py
++++ b/tools/testing/kunit/kunit_kernel.py
+@@ -14,6 +14,7 @@ import os
+ import shlex
+ import shutil
+ import signal
++import sys
+ import threading
+ from typing import Iterator, List, Optional, Tuple, Any
+ from types import FrameType
+@@ -333,6 +334,12 @@ class LinuxSourceTree:
+                       return False
+               return self.validate_config(build_dir)
++      def _restore_terminal_if_tty(self) -> None:
++              # stty requires a controlling terminal; skip headless runs.
++              if sys.stdin is None or not sys.stdin.isatty():
++                      return
++              subprocess.call(['stty', 'sane'])
++
+       def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', filter: str='', filter_action: Optional[str]=None, timeout: Optional[int]=None) -> Iterator[str]:
+               # Copy to avoid mutating the caller-supplied list. exec_tests() reuses
+               # the same args across repeated run_kernel() calls (e.g. --run_isolated),
+@@ -380,11 +387,11 @@ class LinuxSourceTree:
+                       output.close()
+                       waiter.join()
+-                      subprocess.call(['stty', 'sane'])
++                      self._restore_terminal_if_tty()
+       def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
+               logging.error('Build interruption occurred. Cleaning console.')
+               if self._process:
+                               self._process.terminate()
+                               self._process.wait()
+-              subprocess.call(['stty', 'sane'])
++              self._restore_terminal_if_tty()
+diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
+index 70e5d0abe87f..6e6200d52540 100755
+--- a/tools/testing/kunit/kunit_tool_test.py
++++ b/tools/testing/kunit/kunit_tool_test.py
+@@ -503,6 +503,48 @@ class LinuxSourceTreeTest(unittest.TestCase):
+                               self.assertIn('kunit.filter_glob=suite.test1', start_calls[0])
+                               self.assertIn('kunit.filter_glob=suite.test2', start_calls[1])
++      def test_run_kernel_skips_terminal_reset_without_tty(self):
++              def fake_start(unused_args, unused_build_dir):
++                      return subprocess.Popen(['printf', 'KTAP version 1\n'],
++                                              text=True, stdout=subprocess.PIPE)
++
++              non_tty_stdin = mock.Mock()
++              non_tty_stdin.isatty.return_value = False
++
++              with tempfile.TemporaryDirectory('') as build_dir:
++                      tree = kunit_kernel.LinuxSourceTree(build_dir, kunitconfig_paths=[os.devnull])
++                      with mock.patch.object(tree._ops, 'start', side_effect=fake_start), \
++                           mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
++                           mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call:
++                              for _ in tree.run_kernel(build_dir=build_dir):
++                                      pass
++
++                              mock_call.assert_not_called()
++
++      def test_signal_handler_skips_terminal_reset_without_tty(self):
++              non_tty_stdin = mock.Mock()
++              non_tty_stdin.isatty.return_value = False
++              tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])
++
++              with mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
++                   mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
++                   mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
++                      tree.signal_handler(signal.SIGINT, None)
++                      mock_error.assert_called_once()
++                      mock_call.assert_not_called()
++
++      def test_signal_handler_resets_terminal_with_tty(self):
++              tty_stdin = mock.Mock()
++              tty_stdin.isatty.return_value = True
++              tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])
++
++              with mock.patch.object(kunit_kernel.sys, 'stdin', tty_stdin), \
++                   mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
++                   mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
++                      tree.signal_handler(signal.SIGINT, None)
++                      mock_error.assert_called_once()
++                      mock_call.assert_called_once_with(['stty', 'sane'])
++
+       def test_build_reconfig_no_config(self):
+               with tempfile.TemporaryDirectory('') as build_dir:
+                       with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
+-- 
+2.53.0
+
diff --git a/staging-6.12/kunit-tool-terminate-kernel-under-test-on-sigint.patch b/staging-6.12/kunit-tool-terminate-kernel-under-test-on-sigint.patch
new file mode 100644 (file)
index 0000000..66c92c1
--- /dev/null
@@ -0,0 +1,112 @@
+From 32d59ef004ad145696e0cee42173d5f512e55a5f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:17 -0700
+Subject: kunit: tool: Terminate kernel under test on SIGINT
+
+From: David Gow <david@davidgow.net>
+
+commit 8f260b02eeeffbf2263c2b82b6e3e32fd73cde2b upstream.
+
+kunit.py will attempt to catch SIGINT / ^C in order to ensure the TTY isn't
+messed up, but never actually attempts to terminate the running kernel (be
+it UML or QEMU). This can lead to a bit of frustration if the kernel has
+crashed or hung.
+
+Terminate the kernel process in the signal handler, if it's running. This
+requires plumbing through the process handle in a few more places (and
+having some checks to see if the kernel is still running in places where it
+may have already been killed).
+
+Reported-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Closes: https://lore.kernel.org/all/aaFmiAmg9S18EANA@smile.fi.intel.com/
+Signed-off-by: David Gow <david@davidgow.net>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Tested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
+[florian: resolved conflict in signal_handler(): upstream references
+_restore_terminal_if_tty() which is introduced by the following commit;
+retained subprocess.call(['stty', 'sane']) until that helper is available]
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/kunit/kunit_kernel.py | 28 +++++++++++++++++++---------
+ 1 file changed, 19 insertions(+), 9 deletions(-)
+
+diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py
+index 12b0f2ee5665..3f2979f2f6ea 100644
+--- a/tools/testing/kunit/kunit_kernel.py
++++ b/tools/testing/kunit/kunit_kernel.py
+@@ -15,7 +15,7 @@ import shlex
+ import shutil
+ import signal
+ import threading
+-from typing import Iterator, List, Optional, Tuple
++from typing import Iterator, List, Optional, Tuple, Any
+ from types import FrameType
+ import kunit_config
+@@ -252,6 +252,7 @@ class LinuxSourceTree:
+               if kconfig_add:
+                       kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add))
+                       self._kconfig.merge_in_entries(kconfig)
++              self._process : Optional[subprocess.Popen[Any]] = None
+       def arch(self) -> str:
+               return self._arch
+@@ -345,36 +346,45 @@ class LinuxSourceTree:
+                       args.append('kunit.filter_action=' + filter_action)
+               args.append('kunit.enable=1')
+-              process = self._ops.start(args, build_dir)
+-              assert process.stdout is not None  # tell mypy it's set
++              self._process = self._ops.start(args, build_dir)
++              assert self._process is not None # tell mypy it's set
++              assert self._process.stdout is not None  # tell mypy it's set
+               # Enforce the timeout in a background thread.
+               def _wait_proc() -> None:
+                       try:
+-                              process.wait(timeout=timeout)
++                              if self._process:
++                                      self._process.wait(timeout=timeout)
+                       except Exception as e:
+                               print(e)
+-                              process.terminate()
+-                              process.wait()
++                              if self._process:
++                                      self._process.terminate()
++                                      self._process.wait()
+               waiter = threading.Thread(target=_wait_proc)
+               waiter.start()
+               output = open(get_outfile_path(build_dir), 'w')
+               try:
+                       # Tee the output to the file and to our caller in real time.
+-                      for line in process.stdout:
++                      for line in self._process.stdout:
+                               output.write(line)
+                               yield line
+               # This runs even if our caller doesn't consume every line.
+               finally:
+                       # Flush any leftover output to the file
+-                      output.write(process.stdout.read())
++                      if self._process:
++                              if self._process.stdout:
++                                      output.write(self._process.stdout.read())
++                                      self._process.stdout.close()
++                              self._process = None
+                       output.close()
+-                      process.stdout.close()
+                       waiter.join()
+                       subprocess.call(['stty', 'sane'])
+       def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
+               logging.error('Build interruption occurred. Cleaning console.')
++              if self._process:
++                              self._process.terminate()
++                              self._process.wait()
+               subprocess.call(['stty', 'sane'])
+-- 
+2.53.0
+
diff --git a/staging-6.12/netfilter-nf_conntrack_expect-restore-helper-propaga.patch b/staging-6.12/netfilter-nf_conntrack_expect-restore-helper-propaga.patch
new file mode 100644 (file)
index 0000000..fa8ebbb
--- /dev/null
@@ -0,0 +1,267 @@
+From ba7135b24bed82b00e13b2c8efa680a78387ced6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 7 May 2026 13:00:28 +0200
+Subject: netfilter: nf_conntrack_expect: restore helper propagation via
+ expectation
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit dcb0f9aefdd604d36710fda53c25bd7cf4a3e37a ]
+
+A recent series to fix expectations broke helper propagation via
+expectation, this mechanism is used by the sip and h323 helper. This
+also propagates the conntrack helper to expected connections. I changed
+semantics of exp->helper which now tells us the actual helper that
+created the expectation.
+
+Add an explicit assign_helper field to expectations for this purpose
+and update helpers to use it.
+
+Restore this feature for userspace conntrack helper via ctnetlink
+nfqueue integration so it is again possible to attach a helper to an
+expectation, where it makes sense. This is not restored via ctnetlink
+expectation creation as there is no client for such feature. Use the
+expectation layer 4 protocol number for the helper lookup for
+consistency.
+
+Make sure the expectation using this helper propagation mechanism also
+go away when the helper is unregistered.
+
+Fixes: 9c42bc9db90a ("netfilter: nf_conntrack_expect: honor expectation helper field")
+Fixes: 917b61fa2042 ("netfilter: ctnetlink: ignore explicit helper on new expectations")
+Reported-by: Ilya Maximets <i.maximets@ovn.org>
+Tested-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/netfilter/nf_conntrack_expect.h |  5 ++++-
+ net/netfilter/nf_conntrack_broadcast.c      |  1 +
+ net/netfilter/nf_conntrack_core.c           |  7 +++++--
+ net/netfilter/nf_conntrack_expect.c         |  1 +
+ net/netfilter/nf_conntrack_h323_main.c      | 12 ++++++------
+ net/netfilter/nf_conntrack_helper.c         |  5 +++++
+ net/netfilter/nf_conntrack_netlink.c        | 18 ++++++++++++++++--
+ net/netfilter/nf_conntrack_sip.c            |  2 +-
+ 8 files changed, 39 insertions(+), 12 deletions(-)
+
+diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
+index e9a8350e7ccf..80f50fd0f7ad 100644
+--- a/include/net/netfilter/nf_conntrack_expect.h
++++ b/include/net/netfilter/nf_conntrack_expect.h
+@@ -45,9 +45,12 @@ struct nf_conntrack_expect {
+       void (*expectfn)(struct nf_conn *new,
+                        struct nf_conntrack_expect *this);
+-      /* Helper to assign to new connection */
++      /* Helper that created this expectation */
+       struct nf_conntrack_helper __rcu *helper;
++      /* Helper to assign to new connection */
++      struct nf_conntrack_helper __rcu *assign_helper;
++
+       /* The conntrack of the master connection */
+       struct nf_conn *master;
+diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c
+index f9528d4db0a8..93c501d9d399 100644
+--- a/net/netfilter/nf_conntrack_broadcast.c
++++ b/net/netfilter/nf_conntrack_broadcast.c
+@@ -72,6 +72,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb,
+       exp->flags                = NF_CT_EXPECT_PERMANENT;
+       exp->class                = NF_CT_EXPECT_CLASS_DEFAULT;
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
+index 423080cf86a4..0c457e159727 100644
+--- a/net/netfilter/nf_conntrack_core.c
++++ b/net/netfilter/nf_conntrack_core.c
+@@ -1773,14 +1773,17 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
+               spin_lock_bh(&nf_conntrack_expect_lock);
+               exp = nf_ct_find_expectation(net, zone, tuple, !tmpl || nf_ct_is_confirmed(tmpl));
+               if (exp) {
++                      struct nf_conntrack_helper *assign_helper;
++
+                       /* Welcome, Mr. Bond.  We've been expecting you... */
+                       __set_bit(IPS_EXPECTED_BIT, &ct->status);
+                       /* exp->master safe, refcnt bumped in nf_ct_find_expectation */
+                       ct->master = exp->master;
+-                      if (exp->helper) {
++                      assign_helper = rcu_dereference(exp->assign_helper);
++                      if (assign_helper) {
+                               help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
+                               if (help)
+-                                      rcu_assign_pointer(help->helper, exp->helper);
++                                      rcu_assign_pointer(help->helper, assign_helper);
+                       }
+ #ifdef CONFIG_NF_CONNTRACK_MARK
+diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
+index bb8b87f9ee50..a8929885485b 100644
+--- a/net/netfilter/nf_conntrack_expect.c
++++ b/net/netfilter/nf_conntrack_expect.c
+@@ -344,6 +344,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class,
+               helper = rcu_dereference(help->helper);
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c
+index 791aafe9f396..c42547284f35 100644
+--- a/net/netfilter/nf_conntrack_h323_main.c
++++ b/net/netfilter/nf_conntrack_h323_main.c
+@@ -642,7 +642,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, &nf_conntrack_helper_h245);
++      rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -766,7 +766,7 @@ static int expect_callforwarding(struct sk_buff *skb,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -1233,7 +1233,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct,
+                               &ct->tuplehash[!dir].tuple.src.u3 : NULL,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       exp->flags = NF_CT_EXPECT_PERMANENT;    /* Accept multiple calls */
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+@@ -1305,7 +1305,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_UDP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_ras);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect RAS ");
+@@ -1522,7 +1522,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+@@ -1576,7 +1576,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c
+index 9150bcfd7ca8..ea0cdb7ec915 100644
+--- a/net/netfilter/nf_conntrack_helper.c
++++ b/net/netfilter/nf_conntrack_helper.c
+@@ -419,6 +419,11 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data)
+       this = rcu_dereference_protected(exp->helper,
+                                        lockdep_is_held(&nf_conntrack_expect_lock));
++      if (this == me)
++              return true;
++
++      this = rcu_dereference_protected(exp->assign_helper,
++                                       lockdep_is_held(&nf_conntrack_expect_lock));
+       return this == me;
+ }
+diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
+index eacbbc342c3f..80fdb875c977 100644
+--- a/net/netfilter/nf_conntrack_netlink.c
++++ b/net/netfilter/nf_conntrack_netlink.c
+@@ -2630,6 +2630,7 @@ static const struct nla_policy exp_nla_policy[CTA_EXPECT_MAX+1] = {
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr *const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask);
+@@ -2856,6 +2857,7 @@ static int
+ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+                            u32 portid, u32 report)
+ {
++      struct nf_conntrack_helper *assign_helper = NULL;
+       struct nlattr *cda[CTA_EXPECT_MAX+1];
+       struct nf_conntrack_tuple tuple, mask;
+       struct nf_conntrack_expect *exp;
+@@ -2871,8 +2873,18 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+       if (err < 0)
+               return err;
++      if (cda[CTA_EXPECT_HELP_NAME]) {
++              const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]);
++
++              assign_helper = __nf_conntrack_helper_find(helpname,
++                                                         nf_ct_l3num(ct),
++                                                         tuple.dst.protonum);
++              if (!assign_helper)
++                      return -EOPNOTSUPP;
++      }
++
+       exp = ctnetlink_alloc_expect((const struct nlattr * const *)cda, ct,
+-                                   &tuple, &mask);
++                                   assign_helper, &tuple, &mask);
+       if (IS_ERR(exp))
+               return PTR_ERR(exp);
+@@ -3511,6 +3523,7 @@ ctnetlink_parse_expect_nat(const struct nlattr *attr,
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask)
+ {
+@@ -3564,6 +3577,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
+       exp->zone = ct->zone;
+ #endif
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, assign_helper);
+       exp->tuple = *tuple;
+       exp->mask.src.u3 = mask->src.u3;
+       exp->mask.src.u.all = mask->src.u.all;
+@@ -3619,7 +3633,7 @@ ctnetlink_create_expect(struct net *net,
+       ct = nf_ct_tuplehash_to_ctrack(h);
+       rcu_read_lock();
+-      exp = ctnetlink_alloc_expect(cda, ct, &tuple, &mask);
++      exp = ctnetlink_alloc_expect(cda, ct, NULL, &tuple, &mask);
+       if (IS_ERR(exp)) {
+               err = PTR_ERR(exp);
+               goto err_rcu;
+diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
+index bd91b8b47f4b..852c0b74b8a7 100644
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -1386,7 +1386,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff,
+       nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
+                         saddr, &daddr, proto, NULL, &port);
+       exp->timeout.expires = sip_timeout * HZ;
+-      rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, helper);
+       exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
+       hooks = rcu_dereference(nf_nat_sip_hooks);
+-- 
+2.53.0
+
diff --git a/staging-6.12/series b/staging-6.12/series
new file mode 100644 (file)
index 0000000..8b36f5a
--- /dev/null
@@ -0,0 +1,10 @@
+netfilter-nf_conntrack_expect-restore-helper-propaga.patch
+um-add-os_set_pdeathsig-helper-function.patch
+um-set-parent-death-signal-for-winch-thread-process.patch
+um-use-os_set_pdeathsig-helper-in-winch-thread-proce.patch
+um-set-parent-death-signal-for-ubd-io-thread-process.patch
+um-set-parent-death-signal-for-write_sigio-thread-pr.patch
+um-set-parent-death-signal-for-userspace-process.patch
+kunit-tool-terminate-kernel-under-test-on-sigint.patch
+kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch
+um-preserve-errno-within-signal-handler.patch
diff --git a/staging-6.12/um-add-os_set_pdeathsig-helper-function.patch b/staging-6.12/um-add-os_set_pdeathsig-helper-function.patch
new file mode 100644 (file)
index 0000000..42f1848
--- /dev/null
@@ -0,0 +1,61 @@
+From fc6ea5b0b8cfa038860960f76e22073a5990c799 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:11 -0700
+Subject: um: Add os_set_pdeathsig helper function
+
+From: Tiwei Bie <tiwei.btw@antgroup.com>
+
+commit 4e5adbe447db382cc76e05613581f96aef4f91d2 upstream.
+
+This helper can be used to set the parent-death signal of the calling
+process to SIGKILL to ensure that the process will be killed if the
+UML kernel dies unexpectedly without proper cleanup. This helper will
+be used in the follow-up patches.
+
+Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
+Link: https://patch.msgid.link/20241024142828.2612828-2-tiwei.btw@antgroup.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/include/shared/os.h | 2 ++
+ arch/um/os-Linux/process.c  | 6 ++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
+index 77a8593f219a..2a0843cc2208 100644
+--- a/arch/um/include/shared/os.h
++++ b/arch/um/include/shared/os.h
+@@ -219,6 +219,8 @@ extern int os_unmap_memory(void *addr, int len);
+ extern int os_drop_memory(void *addr, int length);
+ extern int can_drop_memory(void);
++void os_set_pdeathsig(void);
++
+ /* execvp.c */
+ extern int execvp_noalloc(char *buf, const char *file, char *const argv[]);
+ /* helper.c */
+diff --git a/arch/um/os-Linux/process.c b/arch/um/os-Linux/process.c
+index 2686120ab232..4015d3dc3958 100644
+--- a/arch/um/os-Linux/process.c
++++ b/arch/um/os-Linux/process.c
+@@ -12,6 +12,7 @@
+ #include <fcntl.h>
+ #include <sys/mman.h>
+ #include <sys/ptrace.h>
++#include <sys/prctl.h>
+ #include <sys/wait.h>
+ #include <asm/unistd.h>
+ #include <init.h>
+@@ -234,3 +235,8 @@ void init_new_thread_signals(void)
+       set_handler(SIGIO);
+       signal(SIGWINCH, SIG_IGN);
+ }
++
++void os_set_pdeathsig(void)
++{
++      prctl(PR_SET_PDEATHSIG, SIGKILL);
++}
+-- 
+2.53.0
+
diff --git a/staging-6.12/um-preserve-errno-within-signal-handler.patch b/staging-6.12/um-preserve-errno-within-signal-handler.patch
new file mode 100644 (file)
index 0000000..255489a
--- /dev/null
@@ -0,0 +1,51 @@
+From 100199613d07dfe9a0688b62d46bc64f9f11d9e6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:19 -0700
+Subject: um: Preserve errno within signal handler
+
+From: Tiwei Bie <tiwei.btw@antgroup.com>
+
+commit f68b2d5a907b53eed99cf2efcaaae116df73c298 upstream.
+
+We rely on errno to determine whether a syscall has failed, so we
+need to ensure that accessing errno is async-signal-safe. Currently,
+we preserve the errno in sig_handler_common(), but it doesn't cover
+every possible case. Let's do it in hard_handler() instead, which
+is the signal handler we actually register.
+
+Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
+Link: https://patch.msgid.link/20260106001228.1531146-2-tiwei.btw@antgroup.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+[florian: hard_handler() in 6.12 retains the to_irq_stack/from_irq_stack
+loop from before the upstream SMP refactoring; errno save/restore is wrapped
+around that loop rather than replacing sig_handler_common's existing save/restore]
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/os-Linux/signal.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/arch/um/os-Linux/signal.c b/arch/um/os-Linux/signal.c
+index b11ed66c8bb0..94abc9e3ec7e 100644
+--- a/arch/um/os-Linux/signal.c
++++ b/arch/um/os-Linux/signal.c
+@@ -190,6 +190,7 @@ static void hard_handler(int sig, siginfo_t *si, void *p)
+ {
+       ucontext_t *uc = p;
+       mcontext_t *mc = &uc->uc_mcontext;
++      int save_errno = errno;
+       unsigned long pending = 1UL << sig;
+       do {
+@@ -227,6 +228,8 @@ static void hard_handler(int sig, siginfo_t *si, void *p)
+               if (!nested)
+                       pending = from_irq_stack(nested);
+       } while (pending);
++
++      errno = save_errno;
+ }
+ void set_handler(int sig)
+-- 
+2.53.0
+
diff --git a/staging-6.12/um-set-parent-death-signal-for-ubd-io-thread-process.patch b/staging-6.12/um-set-parent-death-signal-for-ubd-io-thread-process.patch
new file mode 100644 (file)
index 0000000..3093a8b
--- /dev/null
@@ -0,0 +1,37 @@
+From 408699d8e13653a3c67c7b46296aa9769d724203 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:14 -0700
+Subject: um: Set parent-death signal for ubd io thread/process
+
+From: Tiwei Bie <tiwei.btw@antgroup.com>
+
+commit 9b5e6c0f5a9199c69af81ac5bedc512ee7dc20b3 upstream.
+
+The ubd io thread is not really a traditional thread. Set the
+parent-death signal for it to ensure that it will be killed if
+the UML kernel dies unexpectedly without proper cleanup.
+
+Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
+Link: https://patch.msgid.link/20241024142828.2612828-3-tiwei.btw@antgroup.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/drivers/ubd_kern.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/arch/um/drivers/ubd_kern.c b/arch/um/drivers/ubd_kern.c
+index 2bfb17373244..66c1a8835e36 100644
+--- a/arch/um/drivers/ubd_kern.c
++++ b/arch/um/drivers/ubd_kern.c
+@@ -1501,6 +1501,7 @@ int io_thread(void *arg)
+ {
+       int n, count, written, res;
++      os_set_pdeathsig();
+       os_fix_helper_signals();
+       while(1){
+-- 
+2.53.0
+
diff --git a/staging-6.12/um-set-parent-death-signal-for-userspace-process.patch b/staging-6.12/um-set-parent-death-signal-for-userspace-process.patch
new file mode 100644 (file)
index 0000000..fbedd9f
--- /dev/null
@@ -0,0 +1,42 @@
+From 3687234019560dee4029a54a0f6972a363126050 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:16 -0700
+Subject: um: Set parent death signal for userspace process
+
+From: Benjamin Berg <benjamin.berg@intel.com>
+
+commit 801e00d3a1b78b7f71675fae79946ff4aa3ee070 upstream.
+
+Enable PR_SET_PDEATHSIG so that the UML userspace process will be killed
+when the kernel exits unexpectedly.
+
+Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
+Link: https://patch.msgid.link/20240919124511.282088-4-benjamin@sipsolutions.net
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+[florian: upstream applies os_set_pdeathsig() in stub_exe.c, a file
+introduced in v6.13 that does not exist in 6.12; applied instead to
+userspace_tramp() in arch/um/os-Linux/skas/process.c, which is the
+equivalent entry-point for the userspace process in 6.12]
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/os-Linux/skas/process.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c
+index b6f656bcffb1..cca016b0b29b 100644
+--- a/arch/um/os-Linux/skas/process.c
++++ b/arch/um/os-Linux/skas/process.c
+@@ -214,6 +214,9 @@ static int userspace_tramp(void *stack)
+                                    (unsigned long) stub_segv_handler -
+                                    (unsigned long) __syscall_stub_start;
++      /* Make sure this process dies if the kernel dies */
++      os_set_pdeathsig();
++
+       ptrace(PTRACE_TRACEME, 0, 0, 0);
+       signal(SIGTERM, SIG_DFL);
+-- 
+2.53.0
+
diff --git a/staging-6.12/um-set-parent-death-signal-for-winch-thread-process.patch b/staging-6.12/um-set-parent-death-signal-for-winch-thread-process.patch
new file mode 100644 (file)
index 0000000..5732a81
--- /dev/null
@@ -0,0 +1,46 @@
+From e647349d6a3269915223107e640d0543c6a7fff1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:12 -0700
+Subject: um: Set parent death signal for winch thread/process
+
+From: Benjamin Berg <benjamin.berg@intel.com>
+
+commit fdb2ecd35d327a1fc6bba69b97f85b494e1f4b6b upstream.
+
+The winch "thread" is really a separate process. Using prctl to set
+PR_SET_PDEATHSIG ensures that this separate thread will be killed if the
+UML kernel itself dies unexpectedly and does not perform proper cleanup.
+
+Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
+Link: https://patch.msgid.link/20240919124511.282088-5-benjamin@sipsolutions.net
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/drivers/chan_user.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/arch/um/drivers/chan_user.c b/arch/um/drivers/chan_user.c
+index a66e556012c4..1434114b2f34 100644
+--- a/arch/um/drivers/chan_user.c
++++ b/arch/um/drivers/chan_user.c
+@@ -10,6 +10,7 @@
+ #include <signal.h>
+ #include <termios.h>
+ #include <sys/ioctl.h>
++#include <sys/prctl.h>
+ #include "chan_user.h"
+ #include <os.h>
+ #include <um_malloc.h>
+@@ -161,6 +162,8 @@ static __noreturn int winch_thread(void *arg)
+       int count;
+       char c = 1;
++      prctl(PR_SET_PDEATHSIG, SIGKILL);
++
+       pty_fd = data->pty_fd;
+       pipe_fd = data->pipe_fd;
+       count = write(pipe_fd, &c, sizeof(c));
+-- 
+2.53.0
+
diff --git a/staging-6.12/um-set-parent-death-signal-for-write_sigio-thread-pr.patch b/staging-6.12/um-set-parent-death-signal-for-write_sigio-thread-pr.patch
new file mode 100644 (file)
index 0000000..4b7b931
--- /dev/null
@@ -0,0 +1,37 @@
+From 673cc33a4e6f912332ee52995f6e502cbbce237c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:15 -0700
+Subject: um: Set parent-death signal for write_sigio thread/process
+
+From: Tiwei Bie <tiwei.btw@antgroup.com>
+
+commit c6c4adee65969218b0b7b13f568fd2c6f2333373 upstream.
+
+The write_sigio thread is not really a traditional thread. Set
+the parent-death signal for it to ensure that it will be killed
+if the UML kernel dies unexpectedly without proper cleanup.
+
+Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
+Link: https://patch.msgid.link/20241024142828.2612828-4-tiwei.btw@antgroup.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/os-Linux/sigio.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/arch/um/os-Linux/sigio.c b/arch/um/os-Linux/sigio.c
+index 9e71794839e8..9aac8def4d63 100644
+--- a/arch/um/os-Linux/sigio.c
++++ b/arch/um/os-Linux/sigio.c
+@@ -55,6 +55,7 @@ static int write_sigio_thread(void *unused)
+       int i, n, respond_fd;
+       char c;
++      os_set_pdeathsig();
+       os_fix_helper_signals();
+       fds = &current_poll;
+       while (1) {
+-- 
+2.53.0
+
diff --git a/staging-6.12/um-use-os_set_pdeathsig-helper-in-winch-thread-proce.patch b/staging-6.12/um-use-os_set_pdeathsig-helper-in-winch-thread-proce.patch
new file mode 100644 (file)
index 0000000..af375f8
--- /dev/null
@@ -0,0 +1,45 @@
+From abd611a92fa1292dd99599b737bd2f4d37d28a6e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 14:41:13 -0700
+Subject: um: Use os_set_pdeathsig helper in winch thread/process
+
+From: Tiwei Bie <tiwei.btw@antgroup.com>
+
+commit 42b8b00c8ab1ac18fccde3f29ee589626a561ea7 upstream.
+
+Since we have a helper now, let's switch to using it. It will make
+the code slightly more consistent.
+
+Signed-off-by: Tiwei Bie <tiwei.btw@antgroup.com>
+Link: https://patch.msgid.link/20241024142828.2612828-5-tiwei.btw@antgroup.com
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Florian Fainelli <florian.fainelli@broadcom.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/um/drivers/chan_user.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/arch/um/drivers/chan_user.c b/arch/um/drivers/chan_user.c
+index 1434114b2f34..35f9beeb19b3 100644
+--- a/arch/um/drivers/chan_user.c
++++ b/arch/um/drivers/chan_user.c
+@@ -10,7 +10,6 @@
+ #include <signal.h>
+ #include <termios.h>
+ #include <sys/ioctl.h>
+-#include <sys/prctl.h>
+ #include "chan_user.h"
+ #include <os.h>
+ #include <um_malloc.h>
+@@ -162,7 +161,7 @@ static __noreturn int winch_thread(void *arg)
+       int count;
+       char c = 1;
+-      prctl(PR_SET_PDEATHSIG, SIGKILL);
++      os_set_pdeathsig();
+       pty_fd = data->pty_fd;
+       pipe_fd = data->pipe_fd;
+-- 
+2.53.0
+
diff --git a/staging-6.18/kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch b/staging-6.18/kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch
new file mode 100644 (file)
index 0000000..5fc6f44
--- /dev/null
@@ -0,0 +1,113 @@
+From 42f59bd030c2a0b67e6c69933b80cb9cb73b7530 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 27 Feb 2026 18:16:36 +0545
+Subject: kunit: tool: skip stty when stdin is not a tty
+
+From: Shuvam Pandey <shuvampandey1@gmail.com>
+
+[ Upstream commit e42c349f4cdfa43cb39a68c8f764f8cafc23a9a9 ]
+
+run_kernel() cleanup and signal_handler() invoke stty unconditionally.
+When stdin is not a tty (for example in CI or unit tests), this writes
+noise to stderr.
+
+Call stty only when stdin is a tty.
+
+Add regression tests for these paths:
+- run_kernel() with non-tty stdin
+- signal_handler() with non-tty stdin
+- signal_handler() with tty stdin
+
+Signed-off-by: Shuvam Pandey <shuvampandey1@gmail.com>
+Reviewed-by: David Gow <david@davidgow.net>
+Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/kunit/kunit_kernel.py    | 10 ++++--
+ tools/testing/kunit/kunit_tool_test.py | 42 ++++++++++++++++++++++++++
+ 2 files changed, 50 insertions(+), 2 deletions(-)
+
+diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py
+index 2998e1bc088b..b610fcf0715a 100644
+--- a/tools/testing/kunit/kunit_kernel.py
++++ b/tools/testing/kunit/kunit_kernel.py
+@@ -345,6 +345,12 @@ class LinuxSourceTree:
+                       return False
+               return self.validate_config(build_dir)
++      def _restore_terminal_if_tty(self) -> None:
++              # stty requires a controlling terminal; skip headless runs.
++              if sys.stdin is None or not sys.stdin.isatty():
++                      return
++              subprocess.call(['stty', 'sane'])
++
+       def run_kernel(self, args: Optional[List[str]]=None, build_dir: str='', filter_glob: str='', filter: str='', filter_action: Optional[str]=None, timeout: Optional[int]=None) -> Iterator[str]:
+               # Copy to avoid mutating the caller-supplied list. exec_tests() reuses
+               # the same args across repeated run_kernel() calls (e.g. --run_isolated),
+@@ -386,8 +392,8 @@ class LinuxSourceTree:
+                       process.stdout.close()
+                       waiter.join()
+-                      subprocess.call(['stty', 'sane'])
++                      self._restore_terminal_if_tty()
+       def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
+               logging.error('Build interruption occurred. Cleaning console.')
+-              subprocess.call(['stty', 'sane'])
++              self._restore_terminal_if_tty()
+diff --git a/tools/testing/kunit/kunit_tool_test.py b/tools/testing/kunit/kunit_tool_test.py
+index ed45bac1548d..0eb61de9abd4 100755
+--- a/tools/testing/kunit/kunit_tool_test.py
++++ b/tools/testing/kunit/kunit_tool_test.py
+@@ -515,6 +515,48 @@ class LinuxSourceTreeTest(unittest.TestCase):
+                               self.assertIn('kunit.filter_glob=suite.test1', start_calls[0])
+                               self.assertIn('kunit.filter_glob=suite.test2', start_calls[1])
++      def test_run_kernel_skips_terminal_reset_without_tty(self):
++              def fake_start(unused_args, unused_build_dir):
++                      return subprocess.Popen(['printf', 'KTAP version 1\n'],
++                                              text=True, stdout=subprocess.PIPE)
++
++              non_tty_stdin = mock.Mock()
++              non_tty_stdin.isatty.return_value = False
++
++              with tempfile.TemporaryDirectory('') as build_dir:
++                      tree = kunit_kernel.LinuxSourceTree(build_dir, kunitconfig_paths=[os.devnull])
++                      with mock.patch.object(tree._ops, 'start', side_effect=fake_start), \
++                           mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
++                           mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call:
++                              for _ in tree.run_kernel(build_dir=build_dir):
++                                      pass
++
++                              mock_call.assert_not_called()
++
++      def test_signal_handler_skips_terminal_reset_without_tty(self):
++              non_tty_stdin = mock.Mock()
++              non_tty_stdin.isatty.return_value = False
++              tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])
++
++              with mock.patch.object(kunit_kernel.sys, 'stdin', non_tty_stdin), \
++                   mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
++                   mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
++                      tree.signal_handler(signal.SIGINT, None)
++                      mock_error.assert_called_once()
++                      mock_call.assert_not_called()
++
++      def test_signal_handler_resets_terminal_with_tty(self):
++              tty_stdin = mock.Mock()
++              tty_stdin.isatty.return_value = True
++              tree = kunit_kernel.LinuxSourceTree('', kunitconfig_paths=[os.devnull])
++
++              with mock.patch.object(kunit_kernel.sys, 'stdin', tty_stdin), \
++                   mock.patch.object(kunit_kernel.subprocess, 'call') as mock_call, \
++                   mock.patch.object(kunit_kernel.logging, 'error') as mock_error:
++                      tree.signal_handler(signal.SIGINT, None)
++                      mock_error.assert_called_once()
++                      mock_call.assert_called_once_with(['stty', 'sane'])
++
+       def test_build_reconfig_no_config(self):
+               with tempfile.TemporaryDirectory('') as build_dir:
+                       with open(kunit_kernel.get_kunitconfig_path(build_dir), 'w') as f:
+-- 
+2.53.0
+
diff --git a/staging-6.18/kunit-tool-terminate-kernel-under-test-on-sigint.patch b/staging-6.18/kunit-tool-terminate-kernel-under-test-on-sigint.patch
new file mode 100644 (file)
index 0000000..4bfcb4d
--- /dev/null
@@ -0,0 +1,108 @@
+From eed15c78086d33e928d7c796782883f2b1a3c3c9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 28 Feb 2026 18:07:22 +0800
+Subject: kunit: tool: Terminate kernel under test on SIGINT
+
+From: David Gow <david@davidgow.net>
+
+[ Upstream commit 8f260b02eeeffbf2263c2b82b6e3e32fd73cde2b ]
+
+kunit.py will attempt to catch SIGINT / ^C in order to ensure the TTY isn't
+messed up, but never actually attempts to terminate the running kernel (be
+it UML or QEMU). This can lead to a bit of frustration if the kernel has
+crashed or hung.
+
+Terminate the kernel process in the signal handler, if it's running. This
+requires plumbing through the process handle in a few more places (and
+having some checks to see if the kernel is still running in places where it
+may have already been killed).
+
+Reported-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Closes: https://lore.kernel.org/all/aaFmiAmg9S18EANA@smile.fi.intel.com/
+Signed-off-by: David Gow <david@davidgow.net>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Tested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
+Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ tools/testing/kunit/kunit_kernel.py | 28 +++++++++++++++++++---------
+ 1 file changed, 19 insertions(+), 9 deletions(-)
+
+diff --git a/tools/testing/kunit/kunit_kernel.py b/tools/testing/kunit/kunit_kernel.py
+index b610fcf0715a..2869fcb199ff 100644
+--- a/tools/testing/kunit/kunit_kernel.py
++++ b/tools/testing/kunit/kunit_kernel.py
+@@ -16,7 +16,7 @@ import shutil
+ import signal
+ import sys
+ import threading
+-from typing import Iterator, List, Optional, Tuple
++from typing import Iterator, List, Optional, Tuple, Any
+ from types import FrameType
+ import kunit_config
+@@ -265,6 +265,7 @@ class LinuxSourceTree:
+               if kconfig_add:
+                       kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add))
+                       self._kconfig.merge_in_entries(kconfig)
++              self._process : Optional[subprocess.Popen[Any]] = None
+       def arch(self) -> str:
+               return self._arch
+@@ -364,36 +365,45 @@ class LinuxSourceTree:
+                       args.append('kunit.filter_action=' + filter_action)
+               args.append('kunit.enable=1')
+-              process = self._ops.start(args, build_dir)
+-              assert process.stdout is not None  # tell mypy it's set
++              self._process = self._ops.start(args, build_dir)
++              assert self._process is not None # tell mypy it's set
++              assert self._process.stdout is not None  # tell mypy it's set
+               # Enforce the timeout in a background thread.
+               def _wait_proc() -> None:
+                       try:
+-                              process.wait(timeout=timeout)
++                              if self._process:
++                                      self._process.wait(timeout=timeout)
+                       except Exception as e:
+                               print(e)
+-                              process.terminate()
+-                              process.wait()
++                              if self._process:
++                                      self._process.terminate()
++                                      self._process.wait()
+               waiter = threading.Thread(target=_wait_proc)
+               waiter.start()
+               output = open(get_outfile_path(build_dir), 'w')
+               try:
+                       # Tee the output to the file and to our caller in real time.
+-                      for line in process.stdout:
++                      for line in self._process.stdout:
+                               output.write(line)
+                               yield line
+               # This runs even if our caller doesn't consume every line.
+               finally:
+                       # Flush any leftover output to the file
+-                      output.write(process.stdout.read())
++                      if self._process:
++                              if self._process.stdout:
++                                      output.write(self._process.stdout.read())
++                                      self._process.stdout.close()
++                              self._process = None
+                       output.close()
+-                      process.stdout.close()
+                       waiter.join()
+                       self._restore_terminal_if_tty()
+       def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
+               logging.error('Build interruption occurred. Cleaning console.')
++              if self._process:
++                              self._process.terminate()
++                              self._process.wait()
+               self._restore_terminal_if_tty()
+-- 
+2.53.0
+
diff --git a/staging-6.18/netfilter-nf_conntrack_expect-restore-helper-propaga.patch b/staging-6.18/netfilter-nf_conntrack_expect-restore-helper-propaga.patch
new file mode 100644 (file)
index 0000000..897b0dc
--- /dev/null
@@ -0,0 +1,267 @@
+From d62208db655c54195997cf6167371005d3b37f05 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 7 May 2026 13:00:28 +0200
+Subject: netfilter: nf_conntrack_expect: restore helper propagation via
+ expectation
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit dcb0f9aefdd604d36710fda53c25bd7cf4a3e37a ]
+
+A recent series to fix expectations broke helper propagation via
+expectation, this mechanism is used by the sip and h323 helper. This
+also propagates the conntrack helper to expected connections. I changed
+semantics of exp->helper which now tells us the actual helper that
+created the expectation.
+
+Add an explicit assign_helper field to expectations for this purpose
+and update helpers to use it.
+
+Restore this feature for userspace conntrack helper via ctnetlink
+nfqueue integration so it is again possible to attach a helper to an
+expectation, where it makes sense. This is not restored via ctnetlink
+expectation creation as there is no client for such feature. Use the
+expectation layer 4 protocol number for the helper lookup for
+consistency.
+
+Make sure the expectation using this helper propagation mechanism also
+go away when the helper is unregistered.
+
+Fixes: 9c42bc9db90a ("netfilter: nf_conntrack_expect: honor expectation helper field")
+Fixes: 917b61fa2042 ("netfilter: ctnetlink: ignore explicit helper on new expectations")
+Reported-by: Ilya Maximets <i.maximets@ovn.org>
+Tested-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/netfilter/nf_conntrack_expect.h |  5 ++++-
+ net/netfilter/nf_conntrack_broadcast.c      |  1 +
+ net/netfilter/nf_conntrack_core.c           |  7 +++++--
+ net/netfilter/nf_conntrack_expect.c         |  1 +
+ net/netfilter/nf_conntrack_h323_main.c      | 12 ++++++------
+ net/netfilter/nf_conntrack_helper.c         |  5 +++++
+ net/netfilter/nf_conntrack_netlink.c        | 18 ++++++++++++++++--
+ net/netfilter/nf_conntrack_sip.c            |  2 +-
+ 8 files changed, 39 insertions(+), 12 deletions(-)
+
+diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
+index e9a8350e7ccf..80f50fd0f7ad 100644
+--- a/include/net/netfilter/nf_conntrack_expect.h
++++ b/include/net/netfilter/nf_conntrack_expect.h
+@@ -45,9 +45,12 @@ struct nf_conntrack_expect {
+       void (*expectfn)(struct nf_conn *new,
+                        struct nf_conntrack_expect *this);
+-      /* Helper to assign to new connection */
++      /* Helper that created this expectation */
+       struct nf_conntrack_helper __rcu *helper;
++      /* Helper to assign to new connection */
++      struct nf_conntrack_helper __rcu *assign_helper;
++
+       /* The conntrack of the master connection */
+       struct nf_conn *master;
+diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c
+index 4f39bf7c843f..75e53fde6b29 100644
+--- a/net/netfilter/nf_conntrack_broadcast.c
++++ b/net/netfilter/nf_conntrack_broadcast.c
+@@ -72,6 +72,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb,
+       exp->flags                = NF_CT_EXPECT_PERMANENT;
+       exp->class                = NF_CT_EXPECT_CLASS_DEFAULT;
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
+index a877f9ad1cf2..44fa3224cc87 100644
+--- a/net/netfilter/nf_conntrack_core.c
++++ b/net/netfilter/nf_conntrack_core.c
+@@ -1771,14 +1771,17 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
+               spin_lock_bh(&nf_conntrack_expect_lock);
+               exp = nf_ct_find_expectation(net, zone, tuple, !tmpl || nf_ct_is_confirmed(tmpl));
+               if (exp) {
++                      struct nf_conntrack_helper *assign_helper;
++
+                       /* Welcome, Mr. Bond.  We've been expecting you... */
+                       __set_bit(IPS_EXPECTED_BIT, &ct->status);
+                       /* exp->master safe, refcnt bumped in nf_ct_find_expectation */
+                       ct->master = exp->master;
+-                      if (exp->helper) {
++                      assign_helper = rcu_dereference(exp->assign_helper);
++                      if (assign_helper) {
+                               help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
+                               if (help)
+-                                      rcu_assign_pointer(help->helper, exp->helper);
++                                      rcu_assign_pointer(help->helper, assign_helper);
+                       }
+ #ifdef CONFIG_NF_CONNTRACK_MARK
+diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
+index 24d0576d84b7..8e943efbdf0a 100644
+--- a/net/netfilter/nf_conntrack_expect.c
++++ b/net/netfilter/nf_conntrack_expect.c
+@@ -344,6 +344,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class,
+               helper = rcu_dereference(help->helper);
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c
+index bd7e9e13e4f6..a116c6629e2b 100644
+--- a/net/netfilter/nf_conntrack_h323_main.c
++++ b/net/netfilter/nf_conntrack_h323_main.c
+@@ -642,7 +642,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, &nf_conntrack_helper_h245);
++      rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -766,7 +766,7 @@ static int expect_callforwarding(struct sk_buff *skb,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -1233,7 +1233,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct,
+                               &ct->tuplehash[!dir].tuple.src.u3 : NULL,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       exp->flags = NF_CT_EXPECT_PERMANENT;    /* Accept multiple calls */
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+@@ -1305,7 +1305,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_UDP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_ras);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect RAS ");
+@@ -1522,7 +1522,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+@@ -1576,7 +1576,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c
+index 9150bcfd7ca8..ea0cdb7ec915 100644
+--- a/net/netfilter/nf_conntrack_helper.c
++++ b/net/netfilter/nf_conntrack_helper.c
+@@ -419,6 +419,11 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data)
+       this = rcu_dereference_protected(exp->helper,
+                                        lockdep_is_held(&nf_conntrack_expect_lock));
++      if (this == me)
++              return true;
++
++      this = rcu_dereference_protected(exp->assign_helper,
++                                       lockdep_is_held(&nf_conntrack_expect_lock));
+       return this == me;
+ }
+diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
+index c8fe284b86db..3df7e5fc76c8 100644
+--- a/net/netfilter/nf_conntrack_netlink.c
++++ b/net/netfilter/nf_conntrack_netlink.c
+@@ -2635,6 +2635,7 @@ static const struct nla_policy exp_nla_policy[CTA_EXPECT_MAX+1] = {
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr *const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask);
+@@ -2861,6 +2862,7 @@ static int
+ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+                            u32 portid, u32 report)
+ {
++      struct nf_conntrack_helper *assign_helper = NULL;
+       struct nlattr *cda[CTA_EXPECT_MAX+1];
+       struct nf_conntrack_tuple tuple, mask;
+       struct nf_conntrack_expect *exp;
+@@ -2876,8 +2878,18 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+       if (err < 0)
+               return err;
++      if (cda[CTA_EXPECT_HELP_NAME]) {
++              const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]);
++
++              assign_helper = __nf_conntrack_helper_find(helpname,
++                                                         nf_ct_l3num(ct),
++                                                         tuple.dst.protonum);
++              if (!assign_helper)
++                      return -EOPNOTSUPP;
++      }
++
+       exp = ctnetlink_alloc_expect((const struct nlattr * const *)cda, ct,
+-                                   &tuple, &mask);
++                                   assign_helper, &tuple, &mask);
+       if (IS_ERR(exp))
+               return PTR_ERR(exp);
+@@ -3516,6 +3528,7 @@ ctnetlink_parse_expect_nat(const struct nlattr *attr,
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask)
+ {
+@@ -3569,6 +3582,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
+       exp->zone = ct->zone;
+ #endif
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, assign_helper);
+       exp->tuple = *tuple;
+       exp->mask.src.u3 = mask->src.u3;
+       exp->mask.src.u.all = mask->src.u.all;
+@@ -3624,7 +3638,7 @@ ctnetlink_create_expect(struct net *net,
+       ct = nf_ct_tuplehash_to_ctrack(h);
+       rcu_read_lock();
+-      exp = ctnetlink_alloc_expect(cda, ct, &tuple, &mask);
++      exp = ctnetlink_alloc_expect(cda, ct, NULL, &tuple, &mask);
+       if (IS_ERR(exp)) {
+               err = PTR_ERR(exp);
+               goto err_rcu;
+diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
+index 147043f19521..4bebf66410ea 100644
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -1386,7 +1386,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff,
+       nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
+                         saddr, &daddr, proto, NULL, &port);
+       exp->timeout.expires = sip_timeout * HZ;
+-      rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, helper);
+       exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
+       hooks = rcu_dereference(nf_nat_sip_hooks);
+-- 
+2.53.0
+
diff --git a/staging-6.18/series b/staging-6.18/series
new file mode 100644 (file)
index 0000000..e31db45
--- /dev/null
@@ -0,0 +1,3 @@
+netfilter-nf_conntrack_expect-restore-helper-propaga.patch
+kunit-tool-skip-stty-when-stdin-is-not-a-tty.patch
+kunit-tool-terminate-kernel-under-test-on-sigint.patch
diff --git a/staging-6.6/netfilter-nf_conntrack_expect-restore-helper-propaga.patch b/staging-6.6/netfilter-nf_conntrack_expect-restore-helper-propaga.patch
new file mode 100644 (file)
index 0000000..8198a09
--- /dev/null
@@ -0,0 +1,267 @@
+From 33f187d9cabd808cf402623dbb83dc37ea899767 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 7 May 2026 13:00:28 +0200
+Subject: netfilter: nf_conntrack_expect: restore helper propagation via
+ expectation
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit dcb0f9aefdd604d36710fda53c25bd7cf4a3e37a ]
+
+A recent series to fix expectations broke helper propagation via
+expectation, this mechanism is used by the sip and h323 helper. This
+also propagates the conntrack helper to expected connections. I changed
+semantics of exp->helper which now tells us the actual helper that
+created the expectation.
+
+Add an explicit assign_helper field to expectations for this purpose
+and update helpers to use it.
+
+Restore this feature for userspace conntrack helper via ctnetlink
+nfqueue integration so it is again possible to attach a helper to an
+expectation, where it makes sense. This is not restored via ctnetlink
+expectation creation as there is no client for such feature. Use the
+expectation layer 4 protocol number for the helper lookup for
+consistency.
+
+Make sure the expectation using this helper propagation mechanism also
+go away when the helper is unregistered.
+
+Fixes: 9c42bc9db90a ("netfilter: nf_conntrack_expect: honor expectation helper field")
+Fixes: 917b61fa2042 ("netfilter: ctnetlink: ignore explicit helper on new expectations")
+Reported-by: Ilya Maximets <i.maximets@ovn.org>
+Tested-by: Ilya Maximets <i.maximets@ovn.org>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ include/net/netfilter/nf_conntrack_expect.h |  5 ++++-
+ net/netfilter/nf_conntrack_broadcast.c      |  1 +
+ net/netfilter/nf_conntrack_core.c           |  7 +++++--
+ net/netfilter/nf_conntrack_expect.c         |  1 +
+ net/netfilter/nf_conntrack_h323_main.c      | 12 ++++++------
+ net/netfilter/nf_conntrack_helper.c         |  5 +++++
+ net/netfilter/nf_conntrack_netlink.c        | 18 ++++++++++++++++--
+ net/netfilter/nf_conntrack_sip.c            |  2 +-
+ 8 files changed, 39 insertions(+), 12 deletions(-)
+
+diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
+index e9a8350e7ccf..80f50fd0f7ad 100644
+--- a/include/net/netfilter/nf_conntrack_expect.h
++++ b/include/net/netfilter/nf_conntrack_expect.h
+@@ -45,9 +45,12 @@ struct nf_conntrack_expect {
+       void (*expectfn)(struct nf_conn *new,
+                        struct nf_conntrack_expect *this);
+-      /* Helper to assign to new connection */
++      /* Helper that created this expectation */
+       struct nf_conntrack_helper __rcu *helper;
++      /* Helper to assign to new connection */
++      struct nf_conntrack_helper __rcu *assign_helper;
++
+       /* The conntrack of the master connection */
+       struct nf_conn *master;
+diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c
+index d44d9379a8a0..ef8a7ca8c116 100644
+--- a/net/netfilter/nf_conntrack_broadcast.c
++++ b/net/netfilter/nf_conntrack_broadcast.c
+@@ -72,6 +72,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb,
+       exp->flags                = NF_CT_EXPECT_PERMANENT;
+       exp->class                = NF_CT_EXPECT_CLASS_DEFAULT;
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
+index b086670ec90d..bed9c370fd70 100644
+--- a/net/netfilter/nf_conntrack_core.c
++++ b/net/netfilter/nf_conntrack_core.c
+@@ -1729,14 +1729,17 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
+               spin_lock_bh(&nf_conntrack_expect_lock);
+               exp = nf_ct_find_expectation(net, zone, tuple, !tmpl || nf_ct_is_confirmed(tmpl));
+               if (exp) {
++                      struct nf_conntrack_helper *assign_helper;
++
+                       /* Welcome, Mr. Bond.  We've been expecting you... */
+                       __set_bit(IPS_EXPECTED_BIT, &ct->status);
+                       /* exp->master safe, refcnt bumped in nf_ct_find_expectation */
+                       ct->master = exp->master;
+-                      if (exp->helper) {
++                      assign_helper = rcu_dereference(exp->assign_helper);
++                      if (assign_helper) {
+                               help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
+                               if (help)
+-                                      rcu_assign_pointer(help->helper, exp->helper);
++                                      rcu_assign_pointer(help->helper, assign_helper);
+                       }
+ #ifdef CONFIG_NF_CONNTRACK_MARK
+diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
+index 379711ea5ab6..34324dece89d 100644
+--- a/net/netfilter/nf_conntrack_expect.c
++++ b/net/netfilter/nf_conntrack_expect.c
+@@ -344,6 +344,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class,
+               helper = rcu_dereference(help->helper);
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, NULL);
+       write_pnet(&exp->net, net);
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+       exp->zone = ct->zone;
+diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c
+index 791aafe9f396..c42547284f35 100644
+--- a/net/netfilter/nf_conntrack_h323_main.c
++++ b/net/netfilter/nf_conntrack_h323_main.c
+@@ -642,7 +642,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, &nf_conntrack_helper_h245);
++      rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -766,7 +766,7 @@ static int expect_callforwarding(struct sk_buff *skb,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+       if (memcmp(&ct->tuplehash[dir].tuple.src.u3,
+@@ -1233,7 +1233,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct,
+                               &ct->tuplehash[!dir].tuple.src.u3 : NULL,
+                         &ct->tuplehash[!dir].tuple.dst.u3,
+                         IPPROTO_TCP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       exp->flags = NF_CT_EXPECT_PERMANENT;    /* Accept multiple calls */
+       nathook = rcu_dereference(nfct_h323_nat_hook);
+@@ -1305,7 +1305,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct,
+       nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct),
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_UDP, NULL, &port);
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_ras);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect RAS ");
+@@ -1522,7 +1522,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+@@ -1576,7 +1576,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct,
+                         &ct->tuplehash[!dir].tuple.src.u3, &addr,
+                         IPPROTO_TCP, NULL, &port);
+       exp->flags = NF_CT_EXPECT_PERMANENT;
+-      rcu_assign_pointer(exp->helper, nf_conntrack_helper_q931);
++      rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931);
+       if (nf_ct_expect_related(exp, 0) == 0) {
+               pr_debug("nf_ct_ras: expect Q.931 ");
+diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c
+index 1d449e825dc6..7174369e5aa6 100644
+--- a/net/netfilter/nf_conntrack_helper.c
++++ b/net/netfilter/nf_conntrack_helper.c
+@@ -424,6 +424,11 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data)
+       this = rcu_dereference_protected(exp->helper,
+                                        lockdep_is_held(&nf_conntrack_expect_lock));
++      if (this == me)
++              return true;
++
++      this = rcu_dereference_protected(exp->assign_helper,
++                                       lockdep_is_held(&nf_conntrack_expect_lock));
+       return this == me;
+ }
+diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
+index 1028ed8ac485..c9bbe25f2d67 100644
+--- a/net/netfilter/nf_conntrack_netlink.c
++++ b/net/netfilter/nf_conntrack_netlink.c
+@@ -2624,6 +2624,7 @@ static const struct nla_policy exp_nla_policy[CTA_EXPECT_MAX+1] = {
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr *const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask);
+@@ -2850,6 +2851,7 @@ static int
+ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+                            u32 portid, u32 report)
+ {
++      struct nf_conntrack_helper *assign_helper = NULL;
+       struct nlattr *cda[CTA_EXPECT_MAX+1];
+       struct nf_conntrack_tuple tuple, mask;
+       struct nf_conntrack_expect *exp;
+@@ -2865,8 +2867,18 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct,
+       if (err < 0)
+               return err;
++      if (cda[CTA_EXPECT_HELP_NAME]) {
++              const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]);
++
++              assign_helper = __nf_conntrack_helper_find(helpname,
++                                                         nf_ct_l3num(ct),
++                                                         tuple.dst.protonum);
++              if (!assign_helper)
++                      return -EOPNOTSUPP;
++      }
++
+       exp = ctnetlink_alloc_expect((const struct nlattr * const *)cda, ct,
+-                                   &tuple, &mask);
++                                   assign_helper, &tuple, &mask);
+       if (IS_ERR(exp))
+               return PTR_ERR(exp);
+@@ -3505,6 +3517,7 @@ ctnetlink_parse_expect_nat(const struct nlattr *attr,
+ static struct nf_conntrack_expect *
+ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
++                     const struct nf_conntrack_helper *assign_helper,
+                      struct nf_conntrack_tuple *tuple,
+                      struct nf_conntrack_tuple *mask)
+ {
+@@ -3558,6 +3571,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct,
+       exp->zone = ct->zone;
+ #endif
+       rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, assign_helper);
+       exp->tuple = *tuple;
+       exp->mask.src.u3 = mask->src.u3;
+       exp->mask.src.u.all = mask->src.u.all;
+@@ -3613,7 +3627,7 @@ ctnetlink_create_expect(struct net *net,
+       ct = nf_ct_tuplehash_to_ctrack(h);
+       rcu_read_lock();
+-      exp = ctnetlink_alloc_expect(cda, ct, &tuple, &mask);
++      exp = ctnetlink_alloc_expect(cda, ct, NULL, &tuple, &mask);
+       if (IS_ERR(exp)) {
+               err = PTR_ERR(exp);
+               goto err_rcu;
+diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
+index bd91b8b47f4b..852c0b74b8a7 100644
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -1386,7 +1386,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff,
+       nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
+                         saddr, &daddr, proto, NULL, &port);
+       exp->timeout.expires = sip_timeout * HZ;
+-      rcu_assign_pointer(exp->helper, helper);
++      rcu_assign_pointer(exp->assign_helper, helper);
+       exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
+       hooks = rcu_dereference(nf_nat_sip_hooks);
+-- 
+2.53.0
+
diff --git a/staging-6.6/series b/staging-6.6/series
new file mode 100644 (file)
index 0000000..9de0fac
--- /dev/null
@@ -0,0 +1 @@
+netfilter-nf_conntrack_expect-restore-helper-propaga.patch