--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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 = ¤t_poll;
+ while (1) {
+--
+2.53.0
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+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
--- /dev/null
+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
+
--- /dev/null
+netfilter-nf_conntrack_expect-restore-helper-propaga.patch