From df706a6846db2f6fa9cab5fa5f531e143805890f Mon Sep 17 00:00:00 2001 From: Andrew Lukoshko Date: Thu, 13 Aug 2026 12:29:18 +0000 Subject: [PATCH] Recreate RHEL kernel-6.12.0-211.47.1.el10_2 from CS10/upstream backports --- ...lit-gc-into-unlink-and-reclaim-phase.patch | 214 ++++++ ...s-walk-all-pending-catchall-elements.patch | 58 ++ ...hash-fix-get-operation-on-big-endian.patch | 57 ++ ...k-h323-fix-oob-read-in-decode-choice.patch | 65 ++ ...lly-bump-set-nelems-before-insertion.patch | 106 +++ ...ck-out-of-bounds-read-in-pipapo-drop.patch | 65 ++ ...ry-leak-in-bridge-verdict-error-path.patch | 52 ++ ...-after-free-in-ctnetlink-dump-exp-ct.patch | 124 ++++ ...d-missing-netlink-policy-validations.patch | 63 ++ ...ength-u32-truncation-in-sip-help-tcp.patch | 66 ++ ...fix-oob-read-in-decode-int-cons-case.patch | 49 ++ ...ful-expression-memleak-in-error-path.patch | 105 +++ ...-return-non-matching-entry-on-expiry.patch | 161 +++++ ...alized-padding-leak-in-nfula-payload.patch | 58 ++ ...xpect-honor-expectation-helper-field.patch | 206 ++++++ ...f-conntrack-expect-use-expect-helper.patch | 144 ++++ ...-store-netns-and-zone-in-expectation.patch | 155 +++++ ...ect-oversized-addrnr-in-rt-mt6-check.patch | 48 ++ ...link-use-netlink-policy-range-checks.patch | 125 ++++ ...-log-account-for-netlink-header-size.patch | 40 ++ ...bles-ensure-names-are-nul-terminated.patch | 65 ++ ...-nla-strcmp-for-ipset-attr-name-attr.patch | 83 +++ ...at-fields-when-cta-expect-nat-absent.patch | 58 ++ ...-explicit-helper-on-new-expectations.patch | 169 +++++ ...t-check-target-extensions-for-nfprot.patch | 101 +++ ...es-reject-immediate-nf-queue-verdict.patch | 52 ++ ...ze-nfgenmsg-in-nlmsg-done-terminator.patch | 52 ++ ...alidate-range-encoding-in-checkentry.patch | 100 +++ ...after-free-in-timeout-object-destroy.patch | 80 +++ ...etfilter-nft-osf-restrict-it-to-ipv4.patch | 47 ++ ...idual-option-lengths-in-fingerprints.patch | 84 +++ ...fix-divide-by-zero-in-osf-wss-modulo.patch | 68 ++ ...ilter-conntrack-remove-sprintf-usage.patch | 181 +++++ ...ter-nat-use-kfree-rcu-to-release-ops.patch | 112 ++++ ...ut-of-bounds-read-on-option-matching.patch | 101 +++ ...ential-null-dereference-in-ttl-check.patch | 75 +++ ...s-use-list-del-rcu-for-netlink-hooks.patch | 137 ++++ ...dd-list-splice-rcu-for-private-lists.patch | 78 +++ ...-via-splice-list-rcu-in-commit-phase.patch | 51 ++ ...ok-transactions-for-device-deletions.patch | 82 +++ ...-strict-mode-inbound-policy-matching.patch | 47 ++ ...nntrack-sip-don-t-use-simple-strtoul.patch | 351 ++++++++++ ...make-writable-by-skb-ensure-writable.patch | 81 +++ ...droom-validate-with-neigh-forwarding.patch | 63 ++ ...cursion-counter-in-neigh-egress-path.patch | 124 ++++ ...trict-several-matches-to-inet-family.patch | 205 ++++++ ...d-check-hooks-to-matches-and-targets.patch | 487 ++++++++++++++ ...eck-hooks-match-target-from-validate.patch | 148 ++++ ...-fix-usersize-for-v1-and-v2-revision.patch | 69 ++ ...location-memleak-with-dormant-tables.patch | 631 ++++++++++++++++++ ...e-helper-propagation-via-expectation.patch | 260 ++++++++ ...-in-expectations-created-via-nfqueue.patch | 37 + ...helper-before-allocating-expectation.patch | 56 ++ ...t-fix-missing-expect-put-in-obj-eval.patch | 39 ++ ...possible-null-deref-during-error-log.patch | 65 ++ ...6t-hbh-reject-oversized-option-lists.patch | 56 ++ ...room-if-necessary-in-neigh-hh-bridge.patch | 141 ++++ ...eue-hold-bridge-skb-dev-while-queued.patch | 97 +++ ...n-invalid-seq-rst-without-direction-.patch | 55 ++ ...esh-tcphdr-after-skb-ensure-writable.patch | 68 ++ ...k-gre-fix-gre-keymap-list-corruption.patch | 246 +++++++ ...r-disable-payload-mangling-in-userns.patch | 69 ++ ...tex-to-guard-hook-reference-counting.patch | 106 +++ ...-irc-fix-possible-out-of-bounds-read.patch | 42 ++ ...fix-use-after-free-on-object-destroy.patch | 40 ++ ...-bail-out-on-template-ct-in-get-eval.patch | 87 +++ 1166-netfilter-revalidate-bridge-ports.patch | 228 +++++++ ...-expectfn-expectations-on-unregister.patch | 138 ++++ ...mac-header-was-set-before-dumping-it.patch | 63 ++ ...register-tracking-for-f-present-flag.patch | 38 ++ ...-stack-leak-via-the-oifname-register.patch | 83 +++ ...-xmit-recursion-helpers-and-use-them.patch | 185 +++++ ...se-nf-ct-exp-net-in-expectation-dump.patch | 37 + ...e-sh-add-check-for-double-create-bug.patch | 116 ++++ ...-add-check-for-overlap-detection-bug.patch | 102 +++ ...ge-sh-add-check-for-flush-reload-bug.patch | 136 ++++ ...ct-store-master-tuple-in-expectation.patch | 128 ++++ ...ed-integer-truncation-in-ipc-receive.patch | 36 + ...ter-fails-due-to-invalid-guest-state.patch | 45 ++ ...oot-after-making-mmu-pages-available.patch | 88 +++ ...hide-shadow-vmcs-right-after-vmclear.patch | 54 ++ ...ot-before-checking-max-mapping-level.patch | 131 ++++ ...alidate-all-gvas-during-pv-tlb-flush.patch | 65 ++ ...-do-a-full-asid-flush-if-npt-enabled.patch | 217 ++++++ ...eferred-freeing-for-action-lifecycle.patch | 85 +++ 1185-perf-aux-fix-page-uaf-in-map-range.patch | 51 ++ ...ncode-text-output-appends-to-rsp-buf.patch | 200 ++++++ Makefile.rhelver | 2 +- kernel.changelog | 90 +++ kernel.spec | 271 +++++++- 90 files changed, 9993 insertions(+), 3 deletions(-) create mode 100644 1100-netfilter-nft-set-pipapo-split-gc-into-unlink-and-reclaim-phase.patch create mode 100644 1101-netfilter-nf-tables-always-walk-all-pending-catchall-elements.patch create mode 100644 1102-netfilter-nft-set-hash-fix-get-operation-on-big-endian.patch create mode 100644 1103-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-choice.patch create mode 100644 1104-netfilter-nf-tables-unconditionally-bump-set-nelems-before-insertion.patch create mode 100644 1105-netfilter-nft-set-pipapo-fix-stack-out-of-bounds-read-in-pipapo-drop.patch create mode 100644 1106-netfilter-nfnetlink-queue-fix-entry-leak-in-bridge-verdict-error-path.patch create mode 100644 1107-netfilter-ctnetlink-fix-use-after-free-in-ctnetlink-dump-exp-ct.patch create mode 100644 1108-netfilter-conntrack-add-missing-netlink-policy-validations.patch create mode 100644 1109-netfilter-nf-conntrack-sip-fix-content-length-u32-truncation-in-sip-help-tcp.patch create mode 100644 1110-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-int-cons-case.patch create mode 100644 1111-nf-tables-nft-dynset-fix-possible-stateful-expression-memleak-in-error-path.patch create mode 100644 1112-netfilter-nft-set-pipapo-avx2-don-t-return-non-matching-entry-on-expiry.patch create mode 100644 1113-netfilter-nfnetlink-log-fix-uninitialized-padding-leak-in-nfula-payload.patch create mode 100644 1114-netfilter-nf-conntrack-expect-honor-expectation-helper-field.patch create mode 100644 1115-netfilter-nf-conntrack-expect-use-expect-helper.patch create mode 100644 1116-netfilter-nf-conntrack-expect-store-netns-and-zone-in-expectation.patch create mode 100644 1117-netfilter-ip6t-rt-reject-oversized-addrnr-in-rt-mt6-check.patch create mode 100644 1118-netfilter-ctnetlink-use-netlink-policy-range-checks.patch create mode 100644 1119-netfilter-nfnetlink-log-account-for-netlink-header-size.patch create mode 100644 1120-netfilter-x-tables-ensure-names-are-nul-terminated.patch create mode 100644 1121-netfilter-ipset-use-nla-strcmp-for-ipset-attr-name-attr.patch create mode 100644 1122-netfilter-ctnetlink-zero-expect-nat-fields-when-cta-expect-nat-absent.patch create mode 100644 1123-netfilter-ctnetlink-ignore-explicit-helper-on-new-expectations.patch create mode 100644 1124-netfilter-x-tables-restrict-xt-check-match-xt-check-target-extensions-for-nfprot.patch create mode 100644 1125-netfilter-nf-tables-reject-immediate-nf-queue-verdict.patch create mode 100644 1126-netfilter-nfnetlink-log-initialize-nfgenmsg-in-nlmsg-done-terminator.patch create mode 100644 1127-netfilter-xt-multiport-validate-range-encoding-in-checkentry.patch create mode 100644 1128-netfilter-nft-ct-fix-use-after-free-in-timeout-object-destroy.patch create mode 100644 1129-netfilter-nft-osf-restrict-it-to-ipv4.patch create mode 100644 1130-nfnetlink-osf-validate-individual-option-lengths-in-fingerprints.patch create mode 100644 1131-netfilter-nfnetlink-osf-fix-divide-by-zero-in-osf-wss-modulo.patch create mode 100644 1132-netfilter-conntrack-remove-sprintf-usage.patch create mode 100644 1133-netfilter-nat-use-kfree-rcu-to-release-ops.patch create mode 100644 1134-netfilter-nfnetlink-osf-fix-out-of-bounds-read-on-option-matching.patch create mode 100644 1135-netfilter-nfnetlink-osf-fix-potential-null-dereference-in-ttl-check.patch create mode 100644 1136-netfilter-nf-tables-use-list-del-rcu-for-netlink-hooks.patch create mode 100644 1137-rculist-add-list-splice-rcu-for-private-lists.patch create mode 100644 1138-netfilter-nf-tables-join-hook-list-via-splice-list-rcu-in-commit-phase.patch create mode 100644 1139-netfilter-nf-tables-add-hook-transactions-for-device-deletions.patch create mode 100644 1140-netfilter-xt-policy-fix-strict-mode-inbound-policy-matching.patch create mode 100644 1141-netfilter-nf-conntrack-sip-don-t-use-simple-strtoul.patch create mode 100644 1142-netfilter-replace-skb-try-make-writable-by-skb-ensure-writable.patch create mode 100644 1143-netfilter-nft-fwd-netdev-add-device-and-headroom-validate-with-neigh-forwarding.patch create mode 100644 1144-netfilter-nft-fwd-netdev-use-recursion-counter-in-neigh-egress-path.patch create mode 100644 1145-netfilter-xtables-restrict-several-matches-to-inet-family.patch create mode 100644 1146-netfilter-x-tables-add-check-hooks-to-matches-and-targets.patch create mode 100644 1147-netfilter-nft-compat-run-xt-check-hooks-match-target-from-validate.patch create mode 100644 1148-netfilter-xt-ct-fix-usersize-for-v1-and-v2-revision.patch create mode 100644 1149-netfilter-nf-tables-fix-netdev-hook-allocation-memleak-with-dormant-tables.patch create mode 100644 1150-netfilter-nf-conntrack-expect-restore-helper-propagation-via-expectation.patch create mode 100644 1151-netfilter-ctnetlink-check-tuple-and-mask-in-expectations-created-via-nfqueue.patch create mode 100644 1152-netfilter-nf-conntrack-sip-get-helper-before-allocating-expectation.patch create mode 100644 1153-netfilter-nft-ct-fix-missing-expect-put-in-obj-eval.patch create mode 100644 1154-netfilter-nf-conntrack-helper-fix-possible-null-deref-during-error-log.patch create mode 100644 1155-netfilter-ip6t-hbh-reject-oversized-option-lists.patch create mode 100644 1156-netfilter-br-netfilter-reallocate-headroom-if-necessary-in-neigh-hh-bridge.patch create mode 100644 1157-netfilter-nf-queue-hold-bridge-skb-dev-while-queued.patch create mode 100644 1158-netfilter-conntrack-tcp-do-not-force-close-on-invalid-seq-rst-without-direction-.patch create mode 100644 1159-netfilter-synproxy-refresh-tcphdr-after-skb-ensure-writable.patch create mode 100644 1160-netfilter-nf-conntrack-gre-fix-gre-keymap-list-corruption.patch create mode 100644 1161-netfilter-disable-payload-mangling-in-userns.patch create mode 100644 1162-netfilter-synproxy-add-mutex-to-guard-hook-reference-counting.patch create mode 100644 1163-netfilter-conntrack-irc-fix-possible-out-of-bounds-read.patch create mode 100644 1164-netfilter-nft-tunnel-fix-use-after-free-on-object-destroy.patch create mode 100644 1165-netfilter-nft-ct-bail-out-on-template-ct-in-get-eval.patch create mode 100644 1166-netfilter-revalidate-bridge-ports.patch create mode 100644 1167-netfilter-nf-conntrack-destroy-stale-expectfn-expectations-on-unregister.patch create mode 100644 1168-netfilter-nf-log-validate-mac-header-was-set-before-dumping-it.patch create mode 100644 1169-netfilter-nft-exthdr-fix-register-tracking-for-f-present-flag.patch create mode 100644 1170-netfilter-nft-fib-fix-stale-stack-leak-via-the-oifname-register.patch create mode 100644 1171-netfilter-nf-dup-netdev-add-nf-dev-xmit-recursion-helpers-and-use-them.patch create mode 100644 1172-netfilter-ctnetlink-use-nf-ct-exp-net-in-expectation-dump.patch create mode 100644 1173-selftests-netfilter-nft-concat-range-sh-add-check-for-double-create-bug.patch create mode 100644 1174-selftests-netfilter-nft-concat-range-sh-add-check-for-overlap-detection-bug.patch create mode 100644 1175-selftests-netfilter-nft-concat-range-sh-add-check-for-flush-reload-bug.patch create mode 100644 1176-netfilter-nf-conntrack-expect-store-master-tuple-in-expectation.patch create mode 100644 1177-accel-ivpu-fix-signed-integer-truncation-in-ipc-receive.patch create mode 100644 1178-kvm-nvmx-put-vmcs12-pages-if-nested-vm-enter-fails-due-to-invalid-guest-state.patch create mode 100644 1179-kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch create mode 100644 1180-kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch create mode 100644 1181-kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-checking-max-mapping-level.patch create mode 100644 1182-kvm-x86-hyper-v-validate-all-gvas-during-pv-tlb-flush.patch create mode 100644 1183-kvm-svm-make-svm-flush-tlb-gva-do-a-full-asid-flush-if-npt-enabled.patch create mode 100644 1184-net-sched-act-api-use-rcu-with-deferred-freeing-for-action-lifecycle.patch create mode 100644 1185-perf-aux-fix-page-uaf-in-map-range.patch create mode 100644 1186-scsi-target-iscsi-bound-iscsi-encode-text-output-appends-to-rsp-buf.patch diff --git a/1100-netfilter-nft-set-pipapo-split-gc-into-unlink-and-reclaim-phase.patch b/1100-netfilter-nft-set-pipapo-split-gc-into-unlink-and-reclaim-phase.patch new file mode 100644 index 000000000..75d354f29 --- /dev/null +++ b/1100-netfilter-nft-set-pipapo-split-gc-into-unlink-and-reclaim-phase.patch @@ -0,0 +1,214 @@ +From 4655e3eac77838a9e585416ef176e1d1799287c0 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:52 +0200 +Subject: [PATCH] netfilter: nft_set_pipapo: split gc into unlink and reclaim + phase + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 9df95785d3d8 + +commit 9df95785d3d8302f7c066050117b04cd3c2048c2 +Author: Florian Westphal +Date: Tue Mar 3 16:31:32 2026 +0100 + + netfilter: nft_set_pipapo: split gc into unlink and reclaim phase + + Yiming Qian reports Use-after-free in the pipapo set type: + Under a large number of expired elements, commit-time GC can run for a very + long time in a non-preemptible context, triggering soft lockup warnings and + RCU stall reports (local denial of service). + + We must split GC in an unlink and a reclaim phase. + + We cannot queue elements for freeing until pointers have been swapped. + Expired elements are still exposed to both the packet path and userspace + dumpers via the live copy of the data structure. + + call_rcu() does not protect us: dump operations or element lookups starting + after call_rcu has fired can still observe the free'd element, unless the + commit phase has made enough progress to swap the clone and live pointers + before any new reader has picked up the old version. + + This a similar approach as done recently for the rbtree backend in commit + 35f83a75529a ("netfilter: nft_set_rbtree: don't gc elements on insert"). + + Fixes: 3c4287f62044 ("nf_tables: Add set type for arbitrary concatenation of ranges") + Reported-by: Yiming Qian + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index 38b3be3..81b52cd 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1856,6 +1856,11 @@ struct nft_trans_gc { + struct rcu_head rcu; + }; + ++static inline int nft_trans_gc_space(const struct nft_trans_gc *trans) ++{ ++ return NFT_TRANS_GC_BATCHCOUNT - trans->count; ++} ++ + static inline void nft_ctx_update(struct nft_ctx *ctx, + const struct nft_trans *trans) + { +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 6411001..5d244d3 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -10623,11 +10623,6 @@ static void nft_trans_gc_queue_work(struct nft_trans_gc *trans) + schedule_work(&trans_gc_work); + } + +-static int nft_trans_gc_space(struct nft_trans_gc *trans) +-{ +- return NFT_TRANS_GC_BATCHCOUNT - trans->count; +-} +- + struct nft_trans_gc *nft_trans_gc_queue_async(struct nft_trans_gc *gc, + unsigned int gc_seq, gfp_t gfp) + { +diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c +index 2824944..308af52 100644 +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -1666,11 +1666,11 @@ static void nft_pipapo_gc_deactivate(struct net *net, struct nft_set *set, + } + + /** +- * pipapo_gc() - Drop expired entries from set, destroy start and end elements ++ * pipapo_gc_scan() - Drop expired entries from set and link them to gc list + * @set: nftables API set representation + * @m: Matching data + */ +-static void pipapo_gc(struct nft_set *set, struct nft_pipapo_match *m) ++static void pipapo_gc_scan(struct nft_set *set, struct nft_pipapo_match *m) + { + struct nft_pipapo *priv = nft_set_priv(set); + struct net *net = read_pnet(&set->net); +@@ -1683,6 +1683,8 @@ static void pipapo_gc(struct nft_set *set, struct nft_pipapo_match *m) + if (!gc) + return; + ++ list_add(&gc->list, &priv->gc_head); ++ + while ((rules_f0 = pipapo_rules_same_key(m->f, first_rule))) { + union nft_pipapo_map_bucket rulemap[NFT_PIPAPO_MAX_FIELDS]; + const struct nft_pipapo_field *f; +@@ -1710,9 +1712,13 @@ static void pipapo_gc(struct nft_set *set, struct nft_pipapo_match *m) + * NFT_SET_ELEM_DEAD_BIT. + */ + if (__nft_set_elem_expired(&e->ext, tstamp)) { +- gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL); +- if (!gc) +- return; ++ if (!nft_trans_gc_space(gc)) { ++ gc = nft_trans_gc_alloc(set, 0, GFP_KERNEL); ++ if (!gc) ++ return; ++ ++ list_add(&gc->list, &priv->gc_head); ++ } + + nft_pipapo_gc_deactivate(net, set, e); + pipapo_drop(m, rulemap); +@@ -1726,10 +1732,30 @@ static void pipapo_gc(struct nft_set *set, struct nft_pipapo_match *m) + } + } + +- gc = nft_trans_gc_catchall_sync(gc); ++ priv->last_gc = jiffies; ++} ++ ++/** ++ * pipapo_gc_queue() - Free expired elements ++ * @set: nftables API set representation ++ */ ++static void pipapo_gc_queue(struct nft_set *set) ++{ ++ struct nft_pipapo *priv = nft_set_priv(set); ++ struct nft_trans_gc *gc, *next; ++ ++ /* always do a catchall cycle: */ ++ gc = nft_trans_gc_alloc(set, 0, GFP_KERNEL); + if (gc) { ++ gc = nft_trans_gc_catchall_sync(gc); ++ if (gc) ++ nft_trans_gc_queue_sync_done(gc); ++ } ++ ++ /* always purge queued gc elements. */ ++ list_for_each_entry_safe(gc, next, &priv->gc_head, list) { ++ list_del(&gc->list); + nft_trans_gc_queue_sync_done(gc); +- priv->last_gc = jiffies; + } + } + +@@ -1783,6 +1809,10 @@ static void pipapo_reclaim_match(struct rcu_head *rcu) + * + * We also need to create a new working copy for subsequent insertions and + * deletions. ++ * ++ * After the live copy has been replaced by the clone, we can safely queue ++ * expired elements that have been collected by pipapo_gc_scan() for ++ * memory reclaim. + */ + static void nft_pipapo_commit(struct nft_set *set) + { +@@ -1793,7 +1823,7 @@ static void nft_pipapo_commit(struct nft_set *set) + return; + + if (time_after_eq(jiffies, priv->last_gc + nft_set_gc_interval(set))) +- pipapo_gc(set, priv->clone); ++ pipapo_gc_scan(set, priv->clone); + + old = rcu_replace_pointer(priv->match, priv->clone, + nft_pipapo_transaction_mutex_held(set)); +@@ -1801,6 +1831,8 @@ static void nft_pipapo_commit(struct nft_set *set) + + if (old) + call_rcu(&old->rcu, pipapo_reclaim_match); ++ ++ pipapo_gc_queue(set); + } + + static void nft_pipapo_abort(const struct nft_set *set) +@@ -2258,6 +2290,7 @@ static int nft_pipapo_init(const struct nft_set *set, + f->mt = NULL; + } + ++ INIT_LIST_HEAD(&priv->gc_head); + rcu_assign_pointer(priv->match, m); + + return 0; +@@ -2307,6 +2340,8 @@ static void nft_pipapo_destroy(const struct nft_ctx *ctx, + struct nft_pipapo *priv = nft_set_priv(set); + struct nft_pipapo_match *m; + ++ WARN_ON_ONCE(!list_empty(&priv->gc_head)); ++ + m = rcu_dereference_protected(priv->match, true); + + if (priv->clone) { +diff --git a/net/netfilter/nft_set_pipapo.h b/net/netfilter/nft_set_pipapo.h +index 4a2ff85..49000f5 100644 +--- a/net/netfilter/nft_set_pipapo.h ++++ b/net/netfilter/nft_set_pipapo.h +@@ -156,12 +156,14 @@ struct nft_pipapo_match { + * @clone: Copy where pending insertions and deletions are kept + * @width: Total bytes to be matched for one packet, including padding + * @last_gc: Timestamp of last garbage collection run, jiffies ++ * @gc_head: list of nft_trans_gc to queue up for mem reclaim + */ + struct nft_pipapo { + struct nft_pipapo_match __rcu *match; + struct nft_pipapo_match *clone; + int width; + unsigned long last_gc; ++ struct list_head gc_head; + }; + + struct nft_pipapo_elem; diff --git a/1101-netfilter-nf-tables-always-walk-all-pending-catchall-elements.patch b/1101-netfilter-nf-tables-always-walk-all-pending-catchall-elements.patch new file mode 100644 index 000000000..49013c0c1 --- /dev/null +++ b/1101-netfilter-nf-tables-always-walk-all-pending-catchall-elements.patch @@ -0,0 +1,58 @@ +From ee1d70170aaa417e064d9f98e029e5aef0dd4397 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:53 +0200 +Subject: [PATCH] netfilter: nf_tables: always walk all pending catchall + elements + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 7cb9a23d7ae4 + +commit 7cb9a23d7ae40a702577d3d8bacb7026f04ac2a9 +Author: Florian Westphal +Date: Thu Mar 5 21:32:00 2026 +0100 + + netfilter: nf_tables: always walk all pending catchall elements + + During transaction processing we might have more than one catchall element: + 1 live catchall element and 1 pending element that is coming as part of the + new batch. + + If the map holding the catchall elements is also going away, its + required to toggle all catchall elements and not just the first viable + candidate. + + Otherwise, we get: + WARNING: ./include/net/netfilter/nf_tables.h:1281 at nft_data_release+0xb7/0xe0 [nf_tables], CPU#2: nft/1404 + RIP: 0010:nft_data_release+0xb7/0xe0 [nf_tables] + [..] + __nft_set_elem_destroy+0x106/0x380 [nf_tables] + nf_tables_abort_release+0x348/0x8d0 [nf_tables] + nf_tables_abort+0xcf2/0x3ac0 [nf_tables] + nfnetlink_rcv_batch+0x9c9/0x20e0 [..] + + Fixes: 628bd3e49cba ("netfilter: nf_tables: drop map element references from preparation phase") + Reported-by: Yiming Qian + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 5d244d3..55434ea 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -828,7 +828,6 @@ static void nft_map_catchall_deactivate(const struct nft_ctx *ctx, + + nft_set_elem_change_active(ctx->net, set, ext); + nft_setelem_data_deactivate(ctx->net, set, catchall->elem); +- break; + } + } + +@@ -5918,7 +5917,6 @@ static void nft_map_catchall_activate(const struct nft_ctx *ctx, + + nft_clear(ctx->net, ext); + nft_setelem_data_activate(ctx->net, set, catchall->elem); +- break; + } + } + diff --git a/1102-netfilter-nft-set-hash-fix-get-operation-on-big-endian.patch b/1102-netfilter-nft-set-hash-fix-get-operation-on-big-endian.patch new file mode 100644 index 000000000..032e23aa7 --- /dev/null +++ b/1102-netfilter-nft-set-hash-fix-get-operation-on-big-endian.patch @@ -0,0 +1,57 @@ +From bb3bbb9f38362d2f5f47f38b354cc935cadea8ec Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:10:08 +0200 +Subject: [PATCH] netfilter: nft_set_hash: fix get operation on big endian + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 2f635adbe264 + +commit 2f635adbe2642d398a0be3ab245accd2987be0c3 +Author: Florian Westphal +Date: Tue Jan 27 20:13:45 2026 +0100 + + netfilter: nft_set_hash: fix get operation on big endian + + tests/shell/testcases/packetpath/set_match_nomatch_hash_fast + fails on big endian with: + + Error: Could not process rule: No such file or directory + reset element ip test s { 244.147.90.126 } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Fatal: Cannot fetch element "244.147.90.126" + + ... because the wrong bucket is searched, jhash() and jhash1_word are + not interchangeable on big endian. + + Fixes: 3b02b0adc242 ("netfilter: nft_set_hash: fix lookups with fixed size hash on big endian") + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nft_set_hash.c b/net/netfilter/nft_set_hash.c +index ba01ce7..739b992 100644 +--- a/net/netfilter/nft_set_hash.c ++++ b/net/netfilter/nft_set_hash.c +@@ -619,15 +619,20 @@ static struct nft_elem_priv * + nft_hash_get(const struct net *net, const struct nft_set *set, + const struct nft_set_elem *elem, unsigned int flags) + { ++ const u32 *key = (const u32 *)&elem->key.val; + struct nft_hash *priv = nft_set_priv(set); + u8 genmask = nft_genmask_cur(net); + struct nft_hash_elem *he; + u32 hash; + +- hash = jhash(elem->key.val.data, set->klen, priv->seed); ++ if (set->klen == 4) ++ hash = jhash_1word(*key, priv->seed); ++ else ++ hash = jhash(key, set->klen, priv->seed); ++ + hash = reciprocal_scale(hash, priv->buckets); + hlist_for_each_entry_rcu(he, &priv->table[hash], node) { +- if (!memcmp(nft_set_ext_key(&he->ext), elem->key.val.data, set->klen) && ++ if (!memcmp(nft_set_ext_key(&he->ext), key, set->klen) && + nft_set_elem_active(&he->ext, genmask)) + return &he->priv; + } diff --git a/1103-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-choice.patch b/1103-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-choice.patch new file mode 100644 index 000000000..cdec031d7 --- /dev/null +++ b/1103-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-choice.patch @@ -0,0 +1,65 @@ +From 812cb15e9cc9b8002c24e507a9004edbccb000c3 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:50 +0200 +Subject: [PATCH] netfilter: nf_conntrack_h323: fix OOB read in decode_choice() + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit baed0d9ba91d + +commit baed0d9ba91d4f390da12d5039128ee897253d60 +Author: Vahagn Vardanian +Date: Wed Feb 25 14:06:18 2026 +0100 + + netfilter: nf_conntrack_h323: fix OOB read in decode_choice() + + In decode_choice(), the boundary check before get_len() uses the + variable `len`, which is still 0 from its initialization at the top of + the function: + + unsigned int type, ext, len = 0; + ... + if (ext || (son->attr & OPEN)) { + BYTE_ALIGN(bs); + if (nf_h323_error_boundary(bs, len, 0)) /* len is 0 here */ + return H323_ERROR_BOUND; + len = get_len(bs); /* OOB read */ + + When the bitstream is exactly consumed (bs->cur == bs->end), the check + nf_h323_error_boundary(bs, 0, 0) evaluates to (bs->cur + 0 > bs->end), + which is false. The subsequent get_len() call then dereferences + *bs->cur++, reading 1 byte past the end of the buffer. If that byte + has bit 7 set, get_len() reads a second byte as well. + + This can be triggered remotely by sending a crafted Q.931 SETUP message + with a User-User Information Element containing exactly 2 bytes of + PER-encoded data ({0x08, 0x00}) to port 1720 through a firewall with + the nf_conntrack_h323 helper active. The decoder fully consumes the + PER buffer before reaching this code path, resulting in a 1-2 byte + heap-buffer-overflow read confirmed by AddressSanitizer. + + Fix this by checking for 2 bytes (the maximum that get_len() may read) + instead of the uninitialized `len`. This matches the pattern used at + every other get_len() call site in the same file, where the caller + checks for 2 bytes of available data before calling get_len(). + + Fixes: ec8a8f3c31dd ("netfilter: nf_ct_h323: Extend nf_h323_error_boundary to work on bits as well") + Signed-off-by: Vahagn Vardanian + Signed-off-by: Florian Westphal + Link: https://patch.msgid.link/20260225130619.1248-2-fw@strlen.de + Signed-off-by: Paolo Abeni + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c +index ca103c9..456bf3a 100644 +--- a/net/netfilter/nf_conntrack_h323_asn1.c ++++ b/net/netfilter/nf_conntrack_h323_asn1.c +@@ -796,7 +796,7 @@ static int decode_choice(struct bitstr *bs, const struct field_t *f, + + if (ext || (son->attr & OPEN)) { + BYTE_ALIGN(bs); +- if (nf_h323_error_boundary(bs, len, 0)) ++ if (nf_h323_error_boundary(bs, 2, 0)) + return H323_ERROR_BOUND; + len = get_len(bs); + if (nf_h323_error_boundary(bs, len, 0)) diff --git a/1104-netfilter-nf-tables-unconditionally-bump-set-nelems-before-insertion.patch b/1104-netfilter-nf-tables-unconditionally-bump-set-nelems-before-insertion.patch new file mode 100644 index 000000000..522821adc --- /dev/null +++ b/1104-netfilter-nf-tables-unconditionally-bump-set-nelems-before-insertion.patch @@ -0,0 +1,106 @@ +From 573d852abb5341047873d1257065ea6ad9174da4 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:52 +0200 +Subject: [PATCH] netfilter: nf_tables: unconditionally bump set->nelems before + insertion + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit def602e498a4 + +commit def602e498a4f951da95c95b1b8ce8ae68aa733a +Author: Pablo Neira Ayuso +Date: Mon Mar 2 23:12:37 2026 +0100 + + netfilter: nf_tables: unconditionally bump set->nelems before insertion + + In case that the set is full, a new element gets published then removed + without waiting for the RCU grace period, while RCU reader can be + walking over it already. + + To address this issue, add the element transaction even if set is full, + but toggle the set_full flag to report -ENFILE so the abort path safely + unwinds the set to its previous state. + + As for element updates, decrement set->nelems to restore it. + + A simpler fix is to call synchronize_rcu() in the error path. + However, with a large batch adding elements to already maxed-out set, + this could cause noticeable slowdown of such batches. + + Fixes: 35d0ac9070ef ("netfilter: nf_tables: fix set->nelems counting with no NLM_F_EXCL") + Reported-by: Inseo An + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 55434ea..f2d6ac3 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -7281,6 +7281,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + struct nft_data_desc desc; + enum nft_registers dreg; + struct nft_trans *trans; ++ bool set_full = false; + u64 expiration; + u64 timeout; + int err, i; +@@ -7567,10 +7568,18 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + if (err < 0) + goto err_elem_free; + ++ if (!(flags & NFT_SET_ELEM_CATCHALL)) { ++ unsigned int max = nft_set_maxsize(set), nelems; ++ ++ nelems = atomic_inc_return(&set->nelems); ++ if (nelems > max) ++ set_full = true; ++ } ++ + trans = nft_trans_elem_alloc(ctx, NFT_MSG_NEWSETELEM, set); + if (trans == NULL) { + err = -ENOMEM; +- goto err_elem_free; ++ goto err_set_size; + } + + ext->genmask = nft_genmask_cur(ctx->net); +@@ -7622,7 +7631,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + + ue->priv = elem_priv; + nft_trans_commit_list_add_elem(ctx->net, trans); +- goto err_elem_free; ++ goto err_set_size; + } + } + } +@@ -7635,23 +7644,16 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + goto err_element_clash; + } + +- if (!(flags & NFT_SET_ELEM_CATCHALL)) { +- unsigned int max = nft_set_maxsize(set); +- +- if (!atomic_add_unless(&set->nelems, 1, max)) { +- err = -ENFILE; +- goto err_set_full; +- } +- } +- + nft_trans_container_elem(trans)->elems[0].priv = elem.priv; + nft_trans_commit_list_add_elem(ctx->net, trans); +- return 0; + +-err_set_full: +- nft_setelem_remove(ctx->net, set, elem.priv); ++ return set_full ? -ENFILE : 0; ++ + err_element_clash: + kfree(trans); ++err_set_size: ++ if (!(flags & NFT_SET_ELEM_CATCHALL)) ++ atomic_dec(&set->nelems); + err_elem_free: + nf_tables_set_elem_destroy(ctx, set, elem.priv); + err_parse_data: diff --git a/1105-netfilter-nft-set-pipapo-fix-stack-out-of-bounds-read-in-pipapo-drop.patch b/1105-netfilter-nft-set-pipapo-fix-stack-out-of-bounds-read-in-pipapo-drop.patch new file mode 100644 index 000000000..408849d67 --- /dev/null +++ b/1105-netfilter-nft-set-pipapo-fix-stack-out-of-bounds-read-in-pipapo-drop.patch @@ -0,0 +1,65 @@ +From 24f89c19d096999b6911538f085cd7d45d5086fe Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:54 +0200 +Subject: [PATCH] netfilter: nft_set_pipapo: fix stack out-of-bounds read in + pipapo_drop() + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit d6d8cd2db236 + +commit d6d8cd2db236a9dd13dbc2d05843b3445cc964b5 +Author: Jenny Guanni Qu +Date: Fri Mar 6 19:12:38 2026 +0000 + + netfilter: nft_set_pipapo: fix stack out-of-bounds read in pipapo_drop() + + pipapo_drop() passes rulemap[i + 1].n to pipapo_unmap() as the + to_offset argument on every iteration, including the last one where + i == m->field_count - 1. This reads one element past the end of the + stack-allocated rulemap array (declared as rulemap[NFT_PIPAPO_MAX_FIELDS] + with NFT_PIPAPO_MAX_FIELDS == 16). + + Although pipapo_unmap() returns early when is_last is true without + using the to_offset value, the argument is evaluated at the call site + before the function body executes, making this a genuine out-of-bounds + stack read confirmed by KASAN: + + BUG: KASAN: stack-out-of-bounds in pipapo_drop+0x50c/0x57c [nf_tables] + Read of size 4 at addr ffff8000810e71a4 + + This frame has 1 object: + [32, 160) 'rulemap' + + The buggy address is at offset 164 -- exactly 4 bytes past the end + of the rulemap array. + + Pass 0 instead of rulemap[i + 1].n on the last iteration to avoid + the out-of-bounds read. + + Fixes: 3c4287f62044 ("nf_tables: Add set type for arbitrary concatenation of ranges") + Signed-off-by: Jenny Guanni Qu + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c +index 308af52..f2d9442 100644 +--- a/net/netfilter/nft_set_pipapo.c ++++ b/net/netfilter/nft_set_pipapo.c +@@ -1626,6 +1626,7 @@ static void pipapo_drop(struct nft_pipapo_match *m, + int i; + + nft_pipapo_for_each_field(f, i, m) { ++ bool last = i == m->field_count - 1; + int g; + + for (g = 0; g < f->groups; g++) { +@@ -1645,7 +1646,7 @@ static void pipapo_drop(struct nft_pipapo_match *m, + } + + pipapo_unmap(f->mt, f->rules, rulemap[i].to, rulemap[i].n, +- rulemap[i + 1].n, i == m->field_count - 1); ++ last ? 0 : rulemap[i + 1].n, last); + if (pipapo_resize(f, f->rules, f->rules - rulemap[i].n)) { + /* We can ignore this, a failure to shrink tables down + * doesn't make tables invalid. diff --git a/1106-netfilter-nfnetlink-queue-fix-entry-leak-in-bridge-verdict-error-path.patch b/1106-netfilter-nfnetlink-queue-fix-entry-leak-in-bridge-verdict-error-path.patch new file mode 100644 index 000000000..a5e09db9d --- /dev/null +++ b/1106-netfilter-nfnetlink-queue-fix-entry-leak-in-bridge-verdict-error-path.patch @@ -0,0 +1,52 @@ +From 6a8f494794e402fec53e71745f5afb1c34c7621a Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:55 +0200 +Subject: [PATCH] netfilter: nfnetlink_queue: fix entry leak in bridge verdict + error path + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f1ba83755d81 + +commit f1ba83755d81c6fc66ac7acd723d238f974091e9 +Author: Hyunwoo Kim +Date: Sun Mar 8 02:24:06 2026 +0900 + + netfilter: nfnetlink_queue: fix entry leak in bridge verdict error path + + nfqnl_recv_verdict() calls find_dequeue_entry() to remove the queue + entry from the queue data structures, taking ownership of the entry. + For PF_BRIDGE packets, it then calls nfqa_parse_bridge() to parse VLAN + attributes. If nfqa_parse_bridge() returns an error (e.g. NFQA_VLAN + present but NFQA_VLAN_TCI missing), the function returns immediately + without freeing the dequeued entry or its sk_buff. + + This leaks the nf_queue_entry, its associated sk_buff, and all held + references (net_device refcounts, struct net refcount). Repeated + triggering exhausts kernel memory. + + Fix this by dropping the entry via nfqnl_reinject() with NF_DROP verdict + on the error path, consistent with other error handling in this file. + + Fixes: 8d45ff22f1b4 ("netfilter: bridge: nf queue verdict to use NFQA_VLAN and NFQA_L2HDR") + Reviewed-by: David Dull + Signed-off-by: Hyunwoo Kim + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c +index d2773ce..0bb1656 100644 +--- a/net/netfilter/nfnetlink_queue.c ++++ b/net/netfilter/nfnetlink_queue.c +@@ -1443,8 +1443,10 @@ static int nfqnl_recv_verdict(struct sk_buff *skb, const struct nfnl_info *info, + + if (entry->state.pf == PF_BRIDGE) { + err = nfqa_parse_bridge(entry, nfqa); +- if (err < 0) ++ if (err < 0) { ++ nfqnl_reinject(entry, NF_DROP); + return err; ++ } + } + + if (nfqa[NFQA_PAYLOAD]) { diff --git a/1107-netfilter-ctnetlink-fix-use-after-free-in-ctnetlink-dump-exp-ct.patch b/1107-netfilter-ctnetlink-fix-use-after-free-in-ctnetlink-dump-exp-ct.patch new file mode 100644 index 000000000..9bc5f3b6f --- /dev/null +++ b/1107-netfilter-ctnetlink-fix-use-after-free-in-ctnetlink-dump-exp-ct.patch @@ -0,0 +1,124 @@ +From 8d7ea526ff2d5ac74a722765b1eb02c37bea4d06 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:58 +0200 +Subject: [PATCH] netfilter: ctnetlink: fix use-after-free in + ctnetlink_dump_exp_ct() + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 5cb81eeda909 + +commit 5cb81eeda909dbb2def209dd10636b51549a3f8a +Author: Hyunwoo Kim +Date: Sun Mar 8 02:21:37 2026 +0900 + + netfilter: ctnetlink: fix use-after-free in ctnetlink_dump_exp_ct() + + ctnetlink_dump_exp_ct() stores a conntrack pointer in cb->data for the + netlink dump callback ctnetlink_exp_ct_dump_table(), but drops the + conntrack reference immediately after netlink_dump_start(). When the + dump spans multiple rounds, the second recvmsg() triggers the dump + callback which dereferences the now-freed conntrack via nfct_help(ct), + leading to a use-after-free on ct->ext. + + The bug is that the netlink_dump_control has no .start or .done + callbacks to manage the conntrack reference across dump rounds. Other + dump functions in the same file (e.g. ctnetlink_get_conntrack) properly + use .start/.done callbacks for this purpose. + + Fix this by adding .start and .done callbacks that hold and release the + conntrack reference for the duration of the dump, and move the + nfct_help() call after the cb->args[0] early-return check in the dump + callback to avoid dereferencing ct->ext unnecessarily. + + BUG: KASAN: slab-use-after-free in ctnetlink_exp_ct_dump_table+0x4f/0x2e0 + Read of size 8 at addr ffff88810597ebf0 by task ctnetlink_poc/133 + + CPU: 1 UID: 0 PID: 133 Comm: ctnetlink_poc Not tainted 7.0.0-rc2+ #3 PREEMPTLAZY + Call Trace: + + ctnetlink_exp_ct_dump_table+0x4f/0x2e0 + netlink_dump+0x333/0x880 + netlink_recvmsg+0x3e2/0x4b0 + ? aa_sk_perm+0x184/0x450 + sock_recvmsg+0xde/0xf0 + + Allocated by task 133: + kmem_cache_alloc_noprof+0x134/0x440 + __nf_conntrack_alloc+0xa8/0x2b0 + ctnetlink_create_conntrack+0xa1/0x900 + ctnetlink_new_conntrack+0x3cf/0x7d0 + nfnetlink_rcv_msg+0x48e/0x510 + netlink_rcv_skb+0xc9/0x1f0 + nfnetlink_rcv+0xdb/0x220 + netlink_unicast+0x3ec/0x590 + netlink_sendmsg+0x397/0x690 + __sys_sendmsg+0xf4/0x180 + + Freed by task 0: + slab_free_after_rcu_debug+0xad/0x1e0 + rcu_core+0x5c3/0x9c0 + + Fixes: e844a928431f ("netfilter: ctnetlink: allow to dump expectation per master conntrack") + Signed-off-by: Hyunwoo Kim + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 633c57f..399227e 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3231,7 +3231,7 @@ ctnetlink_exp_ct_dump_table(struct sk_buff *skb, struct netlink_callback *cb) + { + struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh); + struct nf_conn *ct = cb->data; +- struct nf_conn_help *help = nfct_help(ct); ++ struct nf_conn_help *help; + u_int8_t l3proto = nfmsg->nfgen_family; + unsigned long last_id = cb->args[1]; + struct nf_conntrack_expect *exp; +@@ -3239,6 +3239,10 @@ ctnetlink_exp_ct_dump_table(struct sk_buff *skb, struct netlink_callback *cb) + if (cb->args[0]) + return 0; + ++ help = nfct_help(ct); ++ if (!help) ++ return 0; ++ + rcu_read_lock(); + + restart: +@@ -3268,6 +3272,24 @@ ctnetlink_exp_ct_dump_table(struct sk_buff *skb, struct netlink_callback *cb) + return skb->len; + } + ++static int ctnetlink_dump_exp_ct_start(struct netlink_callback *cb) ++{ ++ struct nf_conn *ct = cb->data; ++ ++ if (!refcount_inc_not_zero(&ct->ct_general.use)) ++ return -ENOENT; ++ return 0; ++} ++ ++static int ctnetlink_dump_exp_ct_done(struct netlink_callback *cb) ++{ ++ struct nf_conn *ct = cb->data; ++ ++ if (ct) ++ nf_ct_put(ct); ++ return 0; ++} ++ + static int ctnetlink_dump_exp_ct(struct net *net, struct sock *ctnl, + struct sk_buff *skb, + const struct nlmsghdr *nlh, +@@ -3283,6 +3305,8 @@ static int ctnetlink_dump_exp_ct(struct net *net, struct sock *ctnl, + struct nf_conntrack_zone zone; + struct netlink_dump_control c = { + .dump = ctnetlink_exp_ct_dump_table, ++ .start = ctnetlink_dump_exp_ct_start, ++ .done = ctnetlink_dump_exp_ct_done, + }; + + err = ctnetlink_parse_tuple(cda, &tuple, CTA_EXPECT_MASTER, diff --git a/1108-netfilter-conntrack-add-missing-netlink-policy-validations.patch b/1108-netfilter-conntrack-add-missing-netlink-policy-validations.patch new file mode 100644 index 000000000..ab9297a3c --- /dev/null +++ b/1108-netfilter-conntrack-add-missing-netlink-policy-validations.patch @@ -0,0 +1,63 @@ +From 3579bfdd048b5391909e01eae2e7bfb5d742b97b Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:58 +0200 +Subject: [PATCH] netfilter: conntrack: add missing netlink policy validations + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f900e1d77ee0 + +commit f900e1d77ee0ef87bfb5ab3fe60f0b3d8ad5ba05 +Author: Florian Westphal +Date: Tue Mar 10 00:28:29 2026 +0100 + + netfilter: conntrack: add missing netlink policy validations + + Hyunwoo Kim reports out-of-bounds access in sctp and ctnetlink. + + These attributes are used by the kernel without any validation. + Extend the netlink policies accordingly. + + Quoting the reporter: + nlattr_to_sctp() assigns the user-supplied CTA_PROTOINFO_SCTP_STATE + value directly to ct->proto.sctp.state without checking that it is + within the valid range. [..] + + and: ... with exp->dir = 100, the access at + ct->master->tuplehash[100] reads 5600 bytes past the start of a + 320-byte nf_conn object, causing a slab-out-of-bounds read confirmed by + UBSAN. + + Fixes: 076a0ca02644 ("netfilter: ctnetlink: add NAT support for expectations") + Fixes: a258860e01b8 ("netfilter: ctnetlink: add full support for SCTP to ctnetlink") + Reported-by: Hyunwoo Kim + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 399227e..527bf07 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3518,7 +3518,7 @@ ctnetlink_change_expect(struct nf_conntrack_expect *x, + + #if IS_ENABLED(CONFIG_NF_NAT) + static const struct nla_policy exp_nat_nla_policy[CTA_EXPECT_NAT_MAX+1] = { +- [CTA_EXPECT_NAT_DIR] = { .type = NLA_U32 }, ++ [CTA_EXPECT_NAT_DIR] = NLA_POLICY_MAX(NLA_BE32, IP_CT_DIR_REPLY), + [CTA_EXPECT_NAT_TUPLE] = { .type = NLA_NESTED }, + }; + #endif +diff --git a/net/netfilter/nf_conntrack_proto_sctp.c b/net/netfilter/nf_conntrack_proto_sctp.c +index 4cc97f9..fabb2c1 100644 +--- a/net/netfilter/nf_conntrack_proto_sctp.c ++++ b/net/netfilter/nf_conntrack_proto_sctp.c +@@ -587,7 +587,8 @@ static int sctp_to_nlattr(struct sk_buff *skb, struct nlattr *nla, + } + + static const struct nla_policy sctp_nla_policy[CTA_PROTOINFO_SCTP_MAX+1] = { +- [CTA_PROTOINFO_SCTP_STATE] = { .type = NLA_U8 }, ++ [CTA_PROTOINFO_SCTP_STATE] = NLA_POLICY_MAX(NLA_U8, ++ SCTP_CONNTRACK_HEARTBEAT_SENT), + [CTA_PROTOINFO_SCTP_VTAG_ORIGINAL] = { .type = NLA_U32 }, + [CTA_PROTOINFO_SCTP_VTAG_REPLY] = { .type = NLA_U32 }, + }; diff --git a/1109-netfilter-nf-conntrack-sip-fix-content-length-u32-truncation-in-sip-help-tcp.patch b/1109-netfilter-nf-conntrack-sip-fix-content-length-u32-truncation-in-sip-help-tcp.patch new file mode 100644 index 000000000..9e630f1ef --- /dev/null +++ b/1109-netfilter-nf-conntrack-sip-fix-content-length-u32-truncation-in-sip-help-tcp.patch @@ -0,0 +1,66 @@ +From 2f1682c1275edb5ac6139158d930d43be0717378 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:15:59 +0200 +Subject: [PATCH] netfilter: nf_conntrack_sip: fix Content-Length u32 + truncation in sip_help_tcp() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit fbce58e719a1 + +commit fbce58e719a17aa215c724473fd5baaa4a8dc57c +Author: Lukas Johannes Möller +Date: Tue Mar 10 21:49:01 2026 +0000 + + netfilter: nf_conntrack_sip: fix Content-Length u32 truncation in sip_help_tcp() + + sip_help_tcp() parses the SIP Content-Length header with + simple_strtoul(), which returns unsigned long, but stores the result in + unsigned int clen. On 64-bit systems, values exceeding UINT_MAX are + silently truncated before computing the SIP message boundary. + + For example, Content-Length 4294967328 (2^32 + 32) is truncated to 32, + causing the parser to miscalculate where the current message ends. The + loop then treats trailing data in the TCP segment as a second SIP + message and processes it through the SDP parser. + + Fix this by changing clen to unsigned long to match the return type of + simple_strtoul(), and reject Content-Length values that exceed the + remaining TCP payload length. + + Fixes: f5b321bd37fb ("netfilter: nf_conntrack_sip: add TCP support") + Signed-off-by: Lukas Johannes Möller + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c +index ca748f8..4ab5ef7 100644 +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -1534,11 +1534,12 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, + { + struct tcphdr *th, _tcph; + unsigned int dataoff, datalen; +- unsigned int matchoff, matchlen, clen; ++ unsigned int matchoff, matchlen; + unsigned int msglen, origlen; + const char *dptr, *end; + s16 diff, tdiff = 0; + int ret = NF_ACCEPT; ++ unsigned long clen; + bool term; + + if (ctinfo != IP_CT_ESTABLISHED && +@@ -1573,6 +1574,9 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, + if (dptr + matchoff == end) + break; + ++ if (clen > datalen) ++ break; ++ + term = false; + for (; end + strlen("\r\n\r\n") <= dptr + datalen; end++) { + if (end[0] == '\r' && end[1] == '\n' && diff --git a/1110-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-int-cons-case.patch b/1110-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-int-cons-case.patch new file mode 100644 index 000000000..202d51f83 --- /dev/null +++ b/1110-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-int-cons-case.patch @@ -0,0 +1,49 @@ +From 6ad52cd472dace258bf7e2970ece287cfa2668b1 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:02 +0200 +Subject: [PATCH] netfilter: nf_conntrack_h323: fix OOB read in decode_int() + CONS case +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 1e3a3593162c + +commit 1e3a3593162c96e8a8de48b1e14f60c3b57fca8a +Author: Jenny Guanni Qu +Date: Thu Mar 12 02:29:32 2026 +0000 + + netfilter: nf_conntrack_h323: fix OOB read in decode_int() CONS case + + In decode_int(), the CONS case calls get_bits(bs, 2) to read a length + value, then calls get_uint(bs, len) without checking that len bytes + remain in the buffer. The existing boundary check only validates the + 2 bits for get_bits(), not the subsequent 1-4 bytes that get_uint() + reads. This allows a malformed H.323/RAS packet to cause a 1-4 byte + slab-out-of-bounds read. + + Add a boundary check for len bytes after get_bits() and before + get_uint(). + + Fixes: 5e35941d9901 ("[NETFILTER]: Add H.323 conntrack/NAT helper") + Reported-by: Klaudia Kloc + Reported-by: Dawid Moczadło + Signed-off-by: Jenny Guanni Qu + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_h323_asn1.c b/net/netfilter/nf_conntrack_h323_asn1.c +index 456bf3a..7b1497e 100644 +--- a/net/netfilter/nf_conntrack_h323_asn1.c ++++ b/net/netfilter/nf_conntrack_h323_asn1.c +@@ -331,6 +331,8 @@ static int decode_int(struct bitstr *bs, const struct field_t *f, + if (nf_h323_error_boundary(bs, 0, 2)) + return H323_ERROR_BOUND; + len = get_bits(bs, 2) + 1; ++ if (nf_h323_error_boundary(bs, len, 0)) ++ return H323_ERROR_BOUND; + BYTE_ALIGN(bs); + if (base && (f->attr & DECODE)) { /* timeToLive */ + unsigned int v = get_uint(bs, len) + f->lb; diff --git a/1111-nf-tables-nft-dynset-fix-possible-stateful-expression-memleak-in-error-path.patch b/1111-nf-tables-nft-dynset-fix-possible-stateful-expression-memleak-in-error-path.patch new file mode 100644 index 000000000..afb62f6dd --- /dev/null +++ b/1111-nf-tables-nft-dynset-fix-possible-stateful-expression-memleak-in-error-path.patch @@ -0,0 +1,105 @@ +From d5cb2ee377e40a8ab981e46c0f83ec60239785ba Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:03 +0200 +Subject: [PATCH] nf_tables: nft_dynset: fix possible stateful expression + memleak in error path +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 0548a13b5a14 + +commit 0548a13b5a145b16e4da0628b5936baf35f51b43 +Author: Pablo Neira Ayuso +Date: Thu Mar 12 12:38:59 2026 +0100 + + nf_tables: nft_dynset: fix possible stateful expression memleak in error path + + If cloning the second stateful expression in the element via GFP_ATOMIC + fails, then the first stateful expression remains in place without being + released. + +   unreferenced object (percpu) 0x607b97e9cab8 (size 16): +     comm "softirq", pid 0, jiffies 4294931867 +     hex dump (first 16 bytes on cpu 3): +       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +     backtrace (crc 0): +       pcpu_alloc_noprof+0x453/0xd80 +       nft_counter_clone+0x9c/0x190 [nf_tables] +       nft_expr_clone+0x8f/0x1b0 [nf_tables] +       nft_dynset_new+0x2cb/0x5f0 [nf_tables] +       nft_rhash_update+0x236/0x11c0 [nf_tables] +       nft_dynset_eval+0x11f/0x670 [nf_tables] +       nft_do_chain+0x253/0x1700 [nf_tables] +       nft_do_chain_ipv4+0x18d/0x270 [nf_tables] +       nf_hook_slow+0xaa/0x1e0 +       ip_local_deliver+0x209/0x330 + + Fixes: 563125a73ac3 ("netfilter: nftables: generalize set extension to support for several expressions") + Reported-by: Gurpreet Shergill + Signed-off-by: Pablo Neira Ayuso + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index 81b52cd..5fe20cc 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -871,6 +871,8 @@ struct nft_elem_priv *nft_set_elem_init(const struct nft_set *set, + u64 timeout, u64 expiration, gfp_t gfp); + int nft_set_elem_expr_clone(const struct nft_ctx *ctx, struct nft_set *set, + struct nft_expr *expr_array[]); ++void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, ++ struct nft_set_elem_expr *elem_expr); + void nft_set_elem_destroy(const struct nft_set *set, + const struct nft_elem_priv *elem_priv, + bool destroy_expr); +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index f2d6ac3..81164ad 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -6853,8 +6853,8 @@ static void __nft_set_elem_expr_destroy(const struct nft_ctx *ctx, + } + } + +-static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, +- struct nft_set_elem_expr *elem_expr) ++void nft_set_elem_expr_destroy(const struct nft_ctx *ctx, ++ struct nft_set_elem_expr *elem_expr) + { + struct nft_expr *expr; + u32 size; +diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c +index 7807d81..9123277 100644 +--- a/net/netfilter/nft_dynset.c ++++ b/net/netfilter/nft_dynset.c +@@ -30,18 +30,26 @@ static int nft_dynset_expr_setup(const struct nft_dynset *priv, + const struct nft_set_ext *ext) + { + struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext); ++ struct nft_ctx ctx = { ++ .net = read_pnet(&priv->set->net), ++ .family = priv->set->table->family, ++ }; + struct nft_expr *expr; + int i; + + for (i = 0; i < priv->num_exprs; i++) { + expr = nft_setelem_expr_at(elem_expr, elem_expr->size); + if (nft_expr_clone(expr, priv->expr_array[i], GFP_ATOMIC) < 0) +- return -1; ++ goto err_out; + + elem_expr->size += priv->expr_array[i]->ops->size; + } + + return 0; ++err_out: ++ nft_set_elem_expr_destroy(&ctx, elem_expr); ++ ++ return -1; + } + + struct nft_elem_priv *nft_dynset_new(struct nft_set *set, diff --git a/1112-netfilter-nft-set-pipapo-avx2-don-t-return-non-matching-entry-on-expiry.patch b/1112-netfilter-nft-set-pipapo-avx2-don-t-return-non-matching-entry-on-expiry.patch new file mode 100644 index 000000000..3746a52b2 --- /dev/null +++ b/1112-netfilter-nft-set-pipapo-avx2-don-t-return-non-matching-entry-on-expiry.patch @@ -0,0 +1,161 @@ +From 84e748a8ba9c8453579b6335a098f4efdcd8fd6d Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:13 +0200 +Subject: [PATCH] netfilter: nft_set_pipapo_avx2: don't return non-matching + entry on expiry + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit d3c0037ffe12 + +commit d3c0037ffe1273fa1961e779ff6906234d6cf53c +Author: Florian Westphal +Date: Wed Mar 25 14:10:55 2026 +0100 + + netfilter: nft_set_pipapo_avx2: don't return non-matching entry on expiry + + New test case fails unexpectedly when avx2 matching functions are used. + + The test first loads a ranomly generated pipapo set + with 'ipv4 . port' key, i.e. nft -f foo. + + This works. Then, it reloads the set after a flush: + (echo flush set t s; cat foo) | nft -f - + + This is expected to work, because its the same set after all and it was + already loaded once. + + But with avx2, this fails: nft reports a clashing element. + + The reported clash is of following form: + + We successfully re-inserted + a . b + c . d + + Then we try to insert a . d + + avx2 finds the already existing a . d, which (due to 'flush set') is marked + as invalid in the new generation. It skips the element and moves to next. + + Due to incorrect masking, the skip-step finds the next matching + element *only considering the first field*, + + i.e. we return the already reinserted "a . b", even though the + last field is different and the entry should not have been matched. + + No such error is reported for the generic c implementation (no avx2) or when + the last field has to use the 'nft_pipapo_avx2_lookup_slow' fallback. + + Bisection points to + 7711f4bb4b36 ("netfilter: nft_set_pipapo: fix range overlap detection") + but that fix merely uncovers this bug. + + Before this commit, the wrong element is returned, but erronously + reported as a full, identical duplicate. + + The root-cause is too early return in the avx2 match functions. + When we process the last field, we should continue to process data + until the entire input size has been consumed to make sure no stale + bits remain in the map. + + Link: https://lore.kernel.org/netfilter-devel/20260321152506.037f68c0@elisabeth/ + Signed-off-by: Florian Westphal + Reviewed-by: Stefano Brivio + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nft_set_pipapo_avx2.c b/net/netfilter/nft_set_pipapo_avx2.c +index bf7a0f6..bb0917e 100644 +--- a/net/netfilter/nft_set_pipapo_avx2.c ++++ b/net/netfilter/nft_set_pipapo_avx2.c +@@ -242,7 +242,7 @@ static int nft_pipapo_avx2_lookup_4b_2(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -319,7 +319,7 @@ static int nft_pipapo_avx2_lookup_4b_4(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -414,7 +414,7 @@ static int nft_pipapo_avx2_lookup_4b_8(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -505,7 +505,7 @@ static int nft_pipapo_avx2_lookup_4b_12(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -641,7 +641,7 @@ static int nft_pipapo_avx2_lookup_4b_32(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -699,7 +699,7 @@ static int nft_pipapo_avx2_lookup_8b_1(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -764,7 +764,7 @@ static int nft_pipapo_avx2_lookup_8b_2(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -839,7 +839,7 @@ static int nft_pipapo_avx2_lookup_8b_4(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -925,7 +925,7 @@ static int nft_pipapo_avx2_lookup_8b_6(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; +@@ -1019,7 +1019,7 @@ static int nft_pipapo_avx2_lookup_8b_16(unsigned long *map, unsigned long *fill, + + b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); + if (last) +- return b; ++ ret = b; + + if (unlikely(ret == -1)) + ret = b / XSAVE_YMM_SIZE; diff --git a/1113-netfilter-nfnetlink-log-fix-uninitialized-padding-leak-in-nfula-payload.patch b/1113-netfilter-nfnetlink-log-fix-uninitialized-padding-leak-in-nfula-payload.patch new file mode 100644 index 000000000..7c6fa2d7f --- /dev/null +++ b/1113-netfilter-nfnetlink-log-fix-uninitialized-padding-leak-in-nfula-payload.patch @@ -0,0 +1,58 @@ +From 95bac469e4bcdb05093eec68ee002e8d5fd79bc2 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:15 +0200 +Subject: [PATCH] netfilter: nfnetlink_log: fix uninitialized padding leak in + NFULA_PAYLOAD + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 52025ebaa29f + +commit 52025ebaa29f4eb4ed8bf92ce83a68f24ab7fdf7 +Author: Weiming Shi +Date: Wed Mar 25 14:10:58 2026 +0100 + + netfilter: nfnetlink_log: fix uninitialized padding leak in NFULA_PAYLOAD + + __build_packet_message() manually constructs the NFULA_PAYLOAD netlink + attribute using skb_put() and skb_copy_bits(), bypassing the standard + nla_reserve()/nla_put() helpers. While nla_total_size(data_len) bytes + are allocated (including NLA alignment padding), only data_len bytes + of actual packet data are copied. The trailing nla_padlen(data_len) + bytes (1-3 when data_len is not 4-byte aligned) are never initialized, + leaking stale heap contents to userspace via the NFLOG netlink socket. + + Replace the manual attribute construction with nla_reserve(), which + handles the tailroom check, header setup, and padding zeroing via + __nla_reserve(). The subsequent skb_copy_bits() fills in the payload + data on top of the properly initialized attribute. + + Fixes: df6fb868d611 ("[NETFILTER]: nfnetlink: convert to generic netlink attribute functions") + Reported-by: Xiang Mei + Signed-off-by: Weiming Shi + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c +index bfcb9cd..27dd352 100644 +--- a/net/netfilter/nfnetlink_log.c ++++ b/net/netfilter/nfnetlink_log.c +@@ -647,15 +647,11 @@ __build_packet_message(struct nfnl_log_net *log, + + if (data_len) { + struct nlattr *nla; +- int size = nla_attr_size(data_len); + +- if (skb_tailroom(inst->skb) < nla_total_size(data_len)) ++ nla = nla_reserve(inst->skb, NFULA_PAYLOAD, data_len); ++ if (!nla) + goto nla_put_failure; + +- nla = skb_put(inst->skb, nla_total_size(data_len)); +- nla->nla_type = NFULA_PAYLOAD; +- nla->nla_len = size; +- + if (skb_copy_bits(skb, 0, nla_data(nla), data_len)) + BUG(); + } diff --git a/1114-netfilter-nf-conntrack-expect-honor-expectation-helper-field.patch b/1114-netfilter-nf-conntrack-expect-honor-expectation-helper-field.patch new file mode 100644 index 000000000..b8e70918b --- /dev/null +++ b/1114-netfilter-nf-conntrack-expect-honor-expectation-helper-field.patch @@ -0,0 +1,206 @@ +From 258cc75af0f0db2881d8c1940733dfd2d57dc95e Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:19 +0200 +Subject: [PATCH] netfilter: nf_conntrack_expect: honor expectation helper + field + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 9c42bc9db90a + +commit 9c42bc9db90a154bc61ae337a070465f3393485a +Author: Pablo Neira Ayuso +Date: Wed Mar 25 14:11:02 2026 +0100 + + netfilter: nf_conntrack_expect: honor expectation helper field + + The expectation helper field is mostly unused. As a result, the + netfilter codebase relies on accessing the helper through exp->master. + + Always set on the expectation helper field so it can be used to reach + the helper. + + nf_ct_expect_init() is called from packet path where the skb owns + the ct object, therefore accessing exp->master for the newly created + expectation is safe. This saves a lot of updates in all callsites + to pass the ct object as parameter to nf_ct_expect_init(). + + This is a preparation patches for follow up fixes. + + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h +index 165e7a0..1b01400 100644 +--- a/include/net/netfilter/nf_conntrack_expect.h ++++ b/include/net/netfilter/nf_conntrack_expect.h +@@ -40,7 +40,7 @@ struct nf_conntrack_expect { + struct nf_conntrack_expect *this); + + /* Helper to assign to new connection */ +- struct nf_conntrack_helper *helper; ++ struct nf_conntrack_helper __rcu *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 a7552a4..1964c59 100644 +--- a/net/netfilter/nf_conntrack_broadcast.c ++++ b/net/netfilter/nf_conntrack_broadcast.c +@@ -70,7 +70,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, + exp->expectfn = NULL; + exp->flags = NF_CT_EXPECT_PERMANENT; + exp->class = NF_CT_EXPECT_CLASS_DEFAULT; +- exp->helper = NULL; ++ rcu_assign_pointer(exp->helper, helper); + + nf_ct_expect_related(exp, 0); + nf_ct_expect_put(exp); +diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c +index f9e65f0..de5e2ae 100644 +--- a/net/netfilter/nf_conntrack_expect.c ++++ b/net/netfilter/nf_conntrack_expect.c +@@ -314,12 +314,19 @@ struct nf_conntrack_expect *nf_ct_expect_alloc(struct nf_conn *me) + } + EXPORT_SYMBOL_GPL(nf_ct_expect_alloc); + ++/* This function can only be used from packet path, where accessing ++ * master's helper is safe, because the packet holds a reference on ++ * the conntrack object. Never use it from control plane. ++ */ + void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, + u_int8_t family, + const union nf_inet_addr *saddr, + const union nf_inet_addr *daddr, + u_int8_t proto, const __be16 *src, const __be16 *dst) + { ++ struct nf_conntrack_helper *helper = NULL; ++ struct nf_conn *ct = exp->master; ++ struct nf_conn_help *help; + int len; + + if (family == AF_INET) +@@ -330,7 +337,12 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, + exp->flags = 0; + exp->class = class; + exp->expectfn = NULL; +- exp->helper = NULL; ++ ++ help = nfct_help(ct); ++ if (help) ++ helper = rcu_dereference(help->helper); ++ ++ rcu_assign_pointer(exp->helper, helper); + exp->tuple.src.l3num = family; + exp->tuple.dst.protonum = proto; + +diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c +index 14f7387..fbf69d4 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); +- exp->helper = &nf_conntrack_helper_h245; ++ rcu_assign_pointer(exp->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); +- exp->helper = nf_conntrack_helper_q931; ++ rcu_assign_pointer(exp->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); +- exp->helper = nf_conntrack_helper_q931; ++ rcu_assign_pointer(exp->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); +- exp->helper = nf_conntrack_helper_ras; ++ rcu_assign_pointer(exp->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; +- exp->helper = nf_conntrack_helper_q931; ++ rcu_assign_pointer(exp->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; +- exp->helper = nf_conntrack_helper_q931; ++ rcu_assign_pointer(exp->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 9d7d36a..a21c976 100644 +--- a/net/netfilter/nf_conntrack_helper.c ++++ b/net/netfilter/nf_conntrack_helper.c +@@ -399,7 +399,7 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) + const struct nf_conntrack_helper *me = data; + const struct nf_conntrack_helper *this; + +- if (exp->helper == me) ++ if (rcu_access_pointer(exp->helper) == me) + return true; + + this = rcu_dereference_protected(help->helper, +@@ -421,6 +421,11 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) + + nf_ct_expect_iterate_destroy(expect_iter_me, me); + nf_ct_iterate_destroy(unhelp, me); ++ ++ /* nf_ct_iterate_destroy() does an unconditional synchronize_rcu() as ++ * last step, this ensures rcu readers of exp->helper are done. ++ * No need for another synchronize_rcu() here. ++ */ + } + EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 527bf07..1da2757 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3602,7 +3602,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + + exp->class = class; + exp->master = ct; +- exp->helper = helper; ++ rcu_assign_pointer(exp->helper, helper); + exp->tuple = *tuple; + exp->mask.src.u3 = mask->src.u3; + exp->mask.src.u.all = mask->src.u.all; +diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c +index 4ab5ef7..106b2f4 100644 +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -1297,7 +1297,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; +- exp->helper = helper; ++ rcu_assign_pointer(exp->helper, helper); + exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE; + + hooks = rcu_dereference(nf_nat_sip_hooks); diff --git a/1115-netfilter-nf-conntrack-expect-use-expect-helper.patch b/1115-netfilter-nf-conntrack-expect-use-expect-helper.patch new file mode 100644 index 000000000..d317eb767 --- /dev/null +++ b/1115-netfilter-nf-conntrack-expect-use-expect-helper.patch @@ -0,0 +1,144 @@ +From 7214a2db7b72aa16525b5583488ae15b790d20bc Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:20 +0200 +Subject: [PATCH] netfilter: nf_conntrack_expect: use expect->helper + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f01794106042 + +commit f01794106042ee27e54af6fdf5b319a2fe3df94d +Author: Pablo Neira Ayuso +Date: Wed Mar 25 14:11:03 2026 +0100 + + netfilter: nf_conntrack_expect: use expect->helper + + Use expect->helper in ctnetlink and /proc to dump the helper name. + Using nfct_help() without holding a reference to the master conntrack + is unsafe. + + Use exp->master->helper in ctnetlink path if userspace does not provide + an explicit helper when creating an expectation to retain the existing + behaviour. The ctnetlink expectation path holds the reference on the + master conntrack and nf_conntrack_expect lock and the nfnetlink glue + path refers to the master ct that is attached to the skb. + + Reported-by: Hyunwoo Kim + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c +index de5e2ae..1cbe5f1 100644 +--- a/net/netfilter/nf_conntrack_expect.c ++++ b/net/netfilter/nf_conntrack_expect.c +@@ -674,7 +674,7 @@ static int exp_seq_show(struct seq_file *s, void *v) + if (expect->flags & NF_CT_EXPECT_USERSPACE) + seq_printf(s, "%sUSERSPACE", delim); + +- helper = rcu_dereference(nfct_help(expect->master)->helper); ++ helper = rcu_dereference(expect->helper); + if (helper) { + seq_printf(s, "%s%s", expect->flags ? " " : "", helper->name); + if (helper->expect_policy[expect->class].name[0]) +diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c +index a21c976..a715304 100644 +--- a/net/netfilter/nf_conntrack_helper.c ++++ b/net/netfilter/nf_conntrack_helper.c +@@ -395,14 +395,10 @@ EXPORT_SYMBOL_GPL(nf_conntrack_helper_register); + + static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) + { +- struct nf_conn_help *help = nfct_help(exp->master); + const struct nf_conntrack_helper *me = data; + const struct nf_conntrack_helper *this; + +- if (rcu_access_pointer(exp->helper) == me) +- return true; +- +- this = rcu_dereference_protected(help->helper, ++ this = rcu_dereference_protected(exp->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 1da2757..479acad 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3031,7 +3031,7 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb, + { + struct nf_conn *master = exp->master; + long timeout = ((long)exp->timeout.expires - (long)jiffies) / HZ; +- struct nf_conn_help *help; ++ struct nf_conntrack_helper *helper; + #if IS_ENABLED(CONFIG_NF_NAT) + struct nlattr *nest_parms; + struct nf_conntrack_tuple nat_tuple = {}; +@@ -3076,15 +3076,12 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb, + nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags)) || + nla_put_be32(skb, CTA_EXPECT_CLASS, htonl(exp->class))) + goto nla_put_failure; +- help = nfct_help(master); +- if (help) { +- struct nf_conntrack_helper *helper; + +- helper = rcu_dereference(help->helper); +- if (helper && +- nla_put_string(skb, CTA_EXPECT_HELP_NAME, helper->name)) +- goto nla_put_failure; +- } ++ helper = rcu_dereference(exp->helper); ++ if (helper && ++ nla_put_string(skb, CTA_EXPECT_HELP_NAME, helper->name)) ++ goto nla_put_failure; ++ + expfn = nf_ct_helper_expectfn_find_by_symbol(exp->expectfn); + if (expfn != NULL && + nla_put_string(skb, CTA_EXPECT_FN, expfn->name)) +@@ -3419,12 +3416,9 @@ static int ctnetlink_get_expect(struct sk_buff *skb, + static bool expect_iter_name(struct nf_conntrack_expect *exp, void *data) + { + struct nf_conntrack_helper *helper; +- const struct nf_conn_help *m_help; + const char *name = data; + +- m_help = nfct_help(exp->master); +- +- helper = rcu_dereference(m_help->helper); ++ helper = rcu_dereference(exp->helper); + if (!helper) + return false; + +@@ -3563,9 +3557,9 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + struct nf_conntrack_tuple *tuple, + struct nf_conntrack_tuple *mask) + { +- u_int32_t class = 0; + struct nf_conntrack_expect *exp; + struct nf_conn_help *help; ++ u32 class = 0; + int err; + + help = nfct_help(ct); +@@ -3602,6 +3596,8 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + + exp->class = class; + exp->master = ct; ++ if (!helper) ++ helper = rcu_dereference(help->helper); + rcu_assign_pointer(exp->helper, helper); + exp->tuple = *tuple; + exp->mask.src.u3 = mask->src.u3; +diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c +index 106b2f4..20e57cf 100644 +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -924,7 +924,7 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff, + exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple); + + if (!exp || exp->master == ct || +- nfct_help(exp->master)->helper != nfct_help(ct)->helper || ++ exp->helper != nfct_help(ct)->helper || + exp->class != class) + break; + #if IS_ENABLED(CONFIG_NF_NAT) diff --git a/1116-netfilter-nf-conntrack-expect-store-netns-and-zone-in-expectation.patch b/1116-netfilter-nf-conntrack-expect-store-netns-and-zone-in-expectation.patch new file mode 100644 index 000000000..9484617e7 --- /dev/null +++ b/1116-netfilter-nf-conntrack-expect-store-netns-and-zone-in-expectation.patch @@ -0,0 +1,155 @@ +From 1ef310863ab448530889c2f720436ef846da997a Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:23 +0200 +Subject: [PATCH] netfilter: nf_conntrack_expect: store netns and zone in + expectation + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 02a3231b6d82 + +commit 02a3231b6d82efe750da6554ebf280e4a6f78756 +Author: Pablo Neira Ayuso +Date: Wed Mar 25 22:39:55 2026 +0100 + + netfilter: nf_conntrack_expect: store netns and zone in expectation + + __nf_ct_expect_find() and nf_ct_expect_find_get() are called under + rcu_read_lock() but they dereference the master conntrack via + exp->master. + + Since the expectation does not hold a reference on the master conntrack, + this could be dying conntrack or different recycled conntrack than the + real master due to SLAB_TYPESAFE_RCU. + + Store the netns, the master_tuple and the zone in struct + nf_conntrack_expect as a safety measure. + + This patch is required by the follow up fix not to dump expectations + that do not belong to this netns. + + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h +index 1b01400..e9a8350 100644 +--- a/include/net/netfilter/nf_conntrack_expect.h ++++ b/include/net/netfilter/nf_conntrack_expect.h +@@ -22,10 +22,16 @@ struct nf_conntrack_expect { + /* Hash member */ + struct hlist_node hnode; + ++ /* Network namespace */ ++ possible_net_t net; ++ + /* We expect this tuple, with the following mask */ + struct nf_conntrack_tuple tuple; + struct nf_conntrack_tuple_mask mask; + ++#ifdef CONFIG_NF_CONNTRACK_ZONES ++ struct nf_conntrack_zone zone; ++#endif + /* Usage count. */ + refcount_t use; + +@@ -62,7 +68,17 @@ struct nf_conntrack_expect { + + static inline struct net *nf_ct_exp_net(struct nf_conntrack_expect *exp) + { +- return nf_ct_net(exp->master); ++ return read_pnet(&exp->net); ++} ++ ++static inline bool nf_ct_exp_zone_equal_any(const struct nf_conntrack_expect *a, ++ const struct nf_conntrack_zone *b) ++{ ++#ifdef CONFIG_NF_CONNTRACK_ZONES ++ return a->zone.id == b->id; ++#else ++ return true; ++#endif + } + + #define NF_CT_EXP_POLICY_NAME_LEN 16 +diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c +index 1964c59..4f39bf7 100644 +--- a/net/netfilter/nf_conntrack_broadcast.c ++++ b/net/netfilter/nf_conntrack_broadcast.c +@@ -21,6 +21,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, + unsigned int timeout) + { + const struct nf_conntrack_helper *helper; ++ struct net *net = read_pnet(&ct->ct_net); + struct nf_conntrack_expect *exp; + struct iphdr *iph = ip_hdr(skb); + struct rtable *rt = skb_rtable(skb); +@@ -71,7 +72,10 @@ 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); +- ++ write_pnet(&exp->net, net); ++#ifdef CONFIG_NF_CONNTRACK_ZONES ++ exp->zone = ct->zone; ++#endif + nf_ct_expect_related(exp, 0); + nf_ct_expect_put(exp); + +diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c +index 1cbe5f1..db28801 100644 +--- a/net/netfilter/nf_conntrack_expect.c ++++ b/net/netfilter/nf_conntrack_expect.c +@@ -113,8 +113,8 @@ nf_ct_exp_equal(const struct nf_conntrack_tuple *tuple, + const struct net *net) + { + return nf_ct_tuple_mask_cmp(tuple, &i->tuple, &i->mask) && +- net_eq(net, nf_ct_net(i->master)) && +- nf_ct_zone_equal_any(i->master, zone); ++ net_eq(net, read_pnet(&i->net)) && ++ nf_ct_exp_zone_equal_any(i, zone); + } + + bool nf_ct_remove_expect(struct nf_conntrack_expect *exp) +@@ -326,6 +326,7 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, + { + struct nf_conntrack_helper *helper = NULL; + struct nf_conn *ct = exp->master; ++ struct net *net = read_pnet(&ct->ct_net); + struct nf_conn_help *help; + int len; + +@@ -343,6 +344,10 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, + helper = rcu_dereference(help->helper); + + rcu_assign_pointer(exp->helper, helper); ++ write_pnet(&exp->net, net); ++#ifdef CONFIG_NF_CONNTRACK_ZONES ++ exp->zone = ct->zone; ++#endif + exp->tuple.src.l3num = family; + exp->tuple.dst.protonum = proto; + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 479acad..844236c 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3557,6 +3557,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + struct nf_conntrack_tuple *tuple, + struct nf_conntrack_tuple *mask) + { ++ struct net *net = read_pnet(&ct->ct_net); + struct nf_conntrack_expect *exp; + struct nf_conn_help *help; + u32 class = 0; +@@ -3596,6 +3597,10 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + + exp->class = class; + exp->master = ct; ++ write_pnet(&exp->net, net); ++#ifdef CONFIG_NF_CONNTRACK_ZONES ++ exp->zone = ct->zone; ++#endif + if (!helper) + helper = rcu_dereference(help->helper); + rcu_assign_pointer(exp->helper, helper); diff --git a/1117-netfilter-ip6t-rt-reject-oversized-addrnr-in-rt-mt6-check.patch b/1117-netfilter-ip6t-rt-reject-oversized-addrnr-in-rt-mt6-check.patch new file mode 100644 index 000000000..5c4f67da5 --- /dev/null +++ b/1117-netfilter-ip6t-rt-reject-oversized-addrnr-in-rt-mt6-check.patch @@ -0,0 +1,48 @@ +From 7769732787489525396da7af990f9f8fe0d3edd3 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:16 +0200 +Subject: [PATCH] netfilter: ip6t_rt: reject oversized addrnr in rt_mt6_check() + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 9d3f027327c2 + +commit 9d3f027327c2fa265f7f85ead41294792c3296ed +Author: Ren Wei +Date: Wed Mar 25 14:11:00 2026 +0100 + + netfilter: ip6t_rt: reject oversized addrnr in rt_mt6_check() + + Reject rt match rules whose addrnr exceeds IP6T_RT_HOPS. + + rt_mt6() expects addrnr to stay within the bounds of rtinfo->addrs[]. + Validate addrnr during rule installation so malformed rules are rejected + before the match logic can use an out-of-range value. + + Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") + Reported-by: Yifan Wu + Reported-by: Juefei Pu + Co-developed-by: Yuan Tan + Signed-off-by: Yuan Tan + Suggested-by: Xin Liu + Tested-by: Yuhang Zheng + Signed-off-by: Ren Wei + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/ipv6/netfilter/ip6t_rt.c b/net/ipv6/netfilter/ip6t_rt.c +index 4ad8b20..5561bd9 100644 +--- a/net/ipv6/netfilter/ip6t_rt.c ++++ b/net/ipv6/netfilter/ip6t_rt.c +@@ -157,6 +157,10 @@ static int rt_mt6_check(const struct xt_mtchk_param *par) + pr_debug("unknown flags %X\n", rtinfo->invflags); + return -EINVAL; + } ++ if (rtinfo->addrnr > IP6T_RT_HOPS) { ++ pr_debug("too many addresses specified\n"); ++ return -EINVAL; ++ } + if ((rtinfo->flags & (IP6T_RT_RES | IP6T_RT_FST_MASK)) && + (!(rtinfo->flags & IP6T_RT_TYP) || + (rtinfo->rt_type != 0) || diff --git a/1118-netfilter-ctnetlink-use-netlink-policy-range-checks.patch b/1118-netfilter-ctnetlink-use-netlink-policy-range-checks.patch new file mode 100644 index 000000000..201821cc1 --- /dev/null +++ b/1118-netfilter-ctnetlink-use-netlink-policy-range-checks.patch @@ -0,0 +1,125 @@ +From bb977545d63209b172be316ac61e3787f769a085 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:27 +0200 +Subject: [PATCH] netfilter: ctnetlink: use netlink policy range checks + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 8f15b5071b45 + +commit 8f15b5071b4548b0aafc03b366eb45c9c6566704 +Author: David Carlier +Date: Wed Mar 25 14:11:08 2026 +0100 + + netfilter: ctnetlink: use netlink policy range checks + + Replace manual range and mask validations with netlink policy + annotations in ctnetlink code paths, so that the netlink core rejects + invalid values early and can generate extack errors. + + - CTA_PROTOINFO_TCP_STATE: reject values > TCP_CONNTRACK_SYN_SENT2 at + policy level, removing the manual >= TCP_CONNTRACK_MAX check. + - CTA_PROTOINFO_TCP_WSCALE_ORIGINAL/REPLY: reject values > TCP_MAX_WSCALE + (14). The normal TCP option parsing path already clamps to this value, + but the ctnetlink path accepted 0-255, causing undefined behavior when + used as a u32 shift count. + - CTA_FILTER_ORIG_FLAGS/REPLY_FLAGS: use NLA_POLICY_MASK with + CTA_FILTER_F_ALL, removing the manual mask checks. + - CTA_EXPECT_FLAGS: use NLA_POLICY_MASK with NF_CT_EXPECT_MASK, adding + a new mask define grouping all valid expect flags. + + Extracted from a broader nf-next patch by Florian Westphal, scoped to + ctnetlink for the fixes tree. + + Fixes: c8e2078cfe41 ("[NETFILTER]: ctnetlink: add support for internal tcp connection tracking flags handling") + Signed-off-by: David Carlier + Co-developed-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/uapi/linux/netfilter/nf_conntrack_common.h b/include/uapi/linux/netfilter/nf_conntrack_common.h +index 2607102..56b6b60 100644 +--- a/include/uapi/linux/netfilter/nf_conntrack_common.h ++++ b/include/uapi/linux/netfilter/nf_conntrack_common.h +@@ -159,5 +159,9 @@ enum ip_conntrack_expect_events { + #define NF_CT_EXPECT_INACTIVE 0x2 + #define NF_CT_EXPECT_USERSPACE 0x4 + ++#ifdef __KERNEL__ ++#define NF_CT_EXPECT_MASK (NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE | \ ++ NF_CT_EXPECT_USERSPACE) ++#endif + + #endif /* _UAPI_NF_CONNTRACK_COMMON_H */ +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 844236c..edc6045 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -909,8 +909,8 @@ struct ctnetlink_filter { + }; + + static const struct nla_policy cta_filter_nla_policy[CTA_FILTER_MAX + 1] = { +- [CTA_FILTER_ORIG_FLAGS] = { .type = NLA_U32 }, +- [CTA_FILTER_REPLY_FLAGS] = { .type = NLA_U32 }, ++ [CTA_FILTER_ORIG_FLAGS] = NLA_POLICY_MASK(NLA_U32, CTA_FILTER_F_ALL), ++ [CTA_FILTER_REPLY_FLAGS] = NLA_POLICY_MASK(NLA_U32, CTA_FILTER_F_ALL), + }; + + static int ctnetlink_parse_filter(const struct nlattr *attr, +@@ -924,17 +924,11 @@ static int ctnetlink_parse_filter(const struct nlattr *attr, + if (ret) + return ret; + +- if (tb[CTA_FILTER_ORIG_FLAGS]) { ++ if (tb[CTA_FILTER_ORIG_FLAGS]) + filter->orig_flags = nla_get_u32(tb[CTA_FILTER_ORIG_FLAGS]); +- if (filter->orig_flags & ~CTA_FILTER_F_ALL) +- return -EOPNOTSUPP; +- } + +- if (tb[CTA_FILTER_REPLY_FLAGS]) { ++ if (tb[CTA_FILTER_REPLY_FLAGS]) + filter->reply_flags = nla_get_u32(tb[CTA_FILTER_REPLY_FLAGS]); +- if (filter->reply_flags & ~CTA_FILTER_F_ALL) +- return -EOPNOTSUPP; +- } + + return 0; + } +@@ -2653,7 +2647,7 @@ static const struct nla_policy exp_nla_policy[CTA_EXPECT_MAX+1] = { + [CTA_EXPECT_HELP_NAME] = { .type = NLA_NUL_STRING, + .len = NF_CT_HELPER_NAME_LEN - 1 }, + [CTA_EXPECT_ZONE] = { .type = NLA_U16 }, +- [CTA_EXPECT_FLAGS] = { .type = NLA_U32 }, ++ [CTA_EXPECT_FLAGS] = NLA_POLICY_MASK(NLA_BE32, NF_CT_EXPECT_MASK), + [CTA_EXPECT_CLASS] = { .type = NLA_U32 }, + [CTA_EXPECT_NAT] = { .type = NLA_NESTED }, + [CTA_EXPECT_FN] = { .type = NLA_NUL_STRING }, +diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c +index 0c1d086..b67426c 100644 +--- a/net/netfilter/nf_conntrack_proto_tcp.c ++++ b/net/netfilter/nf_conntrack_proto_tcp.c +@@ -1385,9 +1385,9 @@ static int tcp_to_nlattr(struct sk_buff *skb, struct nlattr *nla, + } + + static const struct nla_policy tcp_nla_policy[CTA_PROTOINFO_TCP_MAX+1] = { +- [CTA_PROTOINFO_TCP_STATE] = { .type = NLA_U8 }, +- [CTA_PROTOINFO_TCP_WSCALE_ORIGINAL] = { .type = NLA_U8 }, +- [CTA_PROTOINFO_TCP_WSCALE_REPLY] = { .type = NLA_U8 }, ++ [CTA_PROTOINFO_TCP_STATE] = NLA_POLICY_MAX(NLA_U8, TCP_CONNTRACK_SYN_SENT2), ++ [CTA_PROTOINFO_TCP_WSCALE_ORIGINAL] = NLA_POLICY_MAX(NLA_U8, TCP_MAX_WSCALE), ++ [CTA_PROTOINFO_TCP_WSCALE_REPLY] = NLA_POLICY_MAX(NLA_U8, TCP_MAX_WSCALE), + [CTA_PROTOINFO_TCP_FLAGS_ORIGINAL] = { .len = sizeof(struct nf_ct_tcp_flags) }, + [CTA_PROTOINFO_TCP_FLAGS_REPLY] = { .len = sizeof(struct nf_ct_tcp_flags) }, + }; +@@ -1414,10 +1414,6 @@ static int nlattr_to_tcp(struct nlattr *cda[], struct nf_conn *ct) + if (err < 0) + return err; + +- if (tb[CTA_PROTOINFO_TCP_STATE] && +- nla_get_u8(tb[CTA_PROTOINFO_TCP_STATE]) >= TCP_CONNTRACK_MAX) +- return -EINVAL; +- + spin_lock_bh(&ct->lock); + if (tb[CTA_PROTOINFO_TCP_STATE]) + ct->proto.tcp.state = nla_get_u8(tb[CTA_PROTOINFO_TCP_STATE]); diff --git a/1119-netfilter-nfnetlink-log-account-for-netlink-header-size.patch b/1119-netfilter-nfnetlink-log-account-for-netlink-header-size.patch new file mode 100644 index 000000000..22d0feabf --- /dev/null +++ b/1119-netfilter-nfnetlink-log-account-for-netlink-header-size.patch @@ -0,0 +1,40 @@ +From 216247489e80cd8001356ed24e13323803cce985 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:30 +0200 +Subject: [PATCH] netfilter: nfnetlink_log: account for netlink header size + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 6d52a4a0520a + +commit 6d52a4a0520a6696bdde51caa11f2d6821cd0c01 +Author: Florian Westphal +Date: Thu Mar 26 16:17:24 2026 +0100 + + netfilter: nfnetlink_log: account for netlink header size + + This is a followup to an old bug fix: NLMSG_DONE needs to account + for the netlink header size, not just the attribute size. + + This can result in a WARN splat + drop of the netlink message, + but other than this there are no ill effects. + + Fixes: 9dfa1dfe4d5e ("netfilter: nf_log: account for size of NLMSG_DONE attribute") + Reported-by: Yiming Qian + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c +index 27dd352..dcd2493 100644 +--- a/net/netfilter/nfnetlink_log.c ++++ b/net/netfilter/nfnetlink_log.c +@@ -726,7 +726,7 @@ nfulnl_log_packet(struct net *net, + + nla_total_size(plen) /* prefix */ + + nla_total_size(sizeof(struct nfulnl_msg_packet_hw)) + + nla_total_size(sizeof(struct nfulnl_msg_packet_timestamp)) +- + nla_total_size(sizeof(struct nfgenmsg)); /* NLMSG_DONE */ ++ + nlmsg_total_size(sizeof(struct nfgenmsg)); /* NLMSG_DONE */ + + if (in && skb_mac_header_was_set(skb)) { + size += nla_total_size(skb->dev->hard_header_len) diff --git a/1120-netfilter-x-tables-ensure-names-are-nul-terminated.patch b/1120-netfilter-x-tables-ensure-names-are-nul-terminated.patch new file mode 100644 index 000000000..96f17fd49 --- /dev/null +++ b/1120-netfilter-x-tables-ensure-names-are-nul-terminated.patch @@ -0,0 +1,65 @@ +From fb3e41e38a172b76e67ec5c97f4db2d3a9407c40 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:31 +0200 +Subject: [PATCH] netfilter: x_tables: ensure names are nul-terminated + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit a958a4f90ddd + +commit a958a4f90ddd7de0800b33ca9d7b886b7d40f74e +Author: Florian Westphal +Date: Tue Mar 31 23:13:36 2026 +0200 + + netfilter: x_tables: ensure names are nul-terminated + + Reject names that lack a \0 character before feeding them + to functions that expect c-strings. + + Fixes tag is the most recent commit that needs this change. + + Fixes: c38c4597e4bf ("netfilter: implement xt_cgroup cgroup2 path match") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/xt_cgroup.c b/net/netfilter/xt_cgroup.c +index c0f5e9a..bfc9871 100644 +--- a/net/netfilter/xt_cgroup.c ++++ b/net/netfilter/xt_cgroup.c +@@ -53,6 +53,9 @@ static int cgroup_mt_check_v1(const struct xt_mtchk_param *par) + + info->priv = NULL; + if (info->has_path) { ++ if (strnlen(info->path, sizeof(info->path)) >= sizeof(info->path)) ++ return -ENAMETOOLONG; ++ + cgrp = cgroup_get_from_path(info->path); + if (IS_ERR(cgrp)) { + pr_info_ratelimited("invalid path, errno=%ld\n", +@@ -85,6 +88,9 @@ static int cgroup_mt_check_v2(const struct xt_mtchk_param *par) + + info->priv = NULL; + if (info->has_path) { ++ if (strnlen(info->path, sizeof(info->path)) >= sizeof(info->path)) ++ return -ENAMETOOLONG; ++ + cgrp = cgroup_get_from_path(info->path); + if (IS_ERR(cgrp)) { + pr_info_ratelimited("invalid path, errno=%ld\n", +diff --git a/net/netfilter/xt_rateest.c b/net/netfilter/xt_rateest.c +index 72324bd..b1d736c 100644 +--- a/net/netfilter/xt_rateest.c ++++ b/net/netfilter/xt_rateest.c +@@ -91,6 +91,11 @@ static int xt_rateest_mt_checkentry(const struct xt_mtchk_param *par) + goto err1; + } + ++ if (strnlen(info->name1, sizeof(info->name1)) >= sizeof(info->name1)) ++ return -ENAMETOOLONG; ++ if (strnlen(info->name2, sizeof(info->name2)) >= sizeof(info->name2)) ++ return -ENAMETOOLONG; ++ + ret = -ENOENT; + est1 = xt_rateest_lookup(par->net, info->name1); + if (!est1) diff --git a/1121-netfilter-ipset-use-nla-strcmp-for-ipset-attr-name-attr.patch b/1121-netfilter-ipset-use-nla-strcmp-for-ipset-attr-name-attr.patch new file mode 100644 index 000000000..4d0cac9b9 --- /dev/null +++ b/1121-netfilter-ipset-use-nla-strcmp-for-ipset-attr-name-attr.patch @@ -0,0 +1,83 @@ +From 7563a383e063cf6be846f150a25d85098adad32b Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:33 +0200 +Subject: [PATCH] netfilter: ipset: use nla_strcmp for IPSET_ATTR_NAME attr + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit b7e8590987aa + +commit b7e8590987aa94c9dc51518fad0e58cb887b1db5 +Author: Florian Westphal +Date: Mon Mar 30 14:16:34 2026 +0200 + + netfilter: ipset: use nla_strcmp for IPSET_ATTR_NAME attr + + IPSET_ATTR_NAME and IPSET_ATTR_NAMEREF are of NLA_STRING type, they + cannot be treated like a c-string. + + They either have to be switched to NLA_NUL_STRING, or the compare + operations need to use the nla functions. + + Fixes: f830837f0eed ("netfilter: ipset: list:set set type support") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/linux/netfilter/ipset/ip_set.h b/include/linux/netfilter/ipset/ip_set.h +index e9f4f84..b983315 100644 +--- a/include/linux/netfilter/ipset/ip_set.h ++++ b/include/linux/netfilter/ipset/ip_set.h +@@ -309,7 +309,7 @@ enum { + + /* register and unregister set references */ + extern ip_set_id_t ip_set_get_byname(struct net *net, +- const char *name, struct ip_set **set); ++ const struct nlattr *name, struct ip_set **set); + extern void ip_set_put_byindex(struct net *net, ip_set_id_t index); + extern void ip_set_name_byindex(struct net *net, ip_set_id_t index, char *name); + extern ip_set_id_t ip_set_nfnl_get_byindex(struct net *net, ip_set_id_t index); +diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c +index cc20e6d..a4e1d79 100644 +--- a/net/netfilter/ipset/ip_set_core.c ++++ b/net/netfilter/ipset/ip_set_core.c +@@ -821,7 +821,7 @@ EXPORT_SYMBOL_GPL(ip_set_del); + * + */ + ip_set_id_t +-ip_set_get_byname(struct net *net, const char *name, struct ip_set **set) ++ip_set_get_byname(struct net *net, const struct nlattr *name, struct ip_set **set) + { + ip_set_id_t i, index = IPSET_INVALID_ID; + struct ip_set *s; +@@ -830,7 +830,7 @@ ip_set_get_byname(struct net *net, const char *name, struct ip_set **set) + rcu_read_lock(); + for (i = 0; i < inst->ip_set_max; i++) { + s = rcu_dereference(inst->ip_set_list)[i]; +- if (s && STRNCMP(s->name, name)) { ++ if (s && nla_strcmp(name, s->name) == 0) { + __ip_set_get(s); + index = i; + *set = s; +diff --git a/net/netfilter/ipset/ip_set_list_set.c b/net/netfilter/ipset/ip_set_list_set.c +index 13c7a08..34bb84d 100644 +--- a/net/netfilter/ipset/ip_set_list_set.c ++++ b/net/netfilter/ipset/ip_set_list_set.c +@@ -367,7 +367,7 @@ list_set_uadt(struct ip_set *set, struct nlattr *tb[], + ret = ip_set_get_extensions(set, tb, &ext); + if (ret) + return ret; +- e.id = ip_set_get_byname(map->net, nla_data(tb[IPSET_ATTR_NAME]), &s); ++ e.id = ip_set_get_byname(map->net, tb[IPSET_ATTR_NAME], &s); + if (e.id == IPSET_INVALID_ID) + return -IPSET_ERR_NAME; + /* "Loop detection" */ +@@ -389,7 +389,7 @@ list_set_uadt(struct ip_set *set, struct nlattr *tb[], + + if (tb[IPSET_ATTR_NAMEREF]) { + e.refid = ip_set_get_byname(map->net, +- nla_data(tb[IPSET_ATTR_NAMEREF]), ++ tb[IPSET_ATTR_NAMEREF], + &s); + if (e.refid == IPSET_INVALID_ID) { + ret = -IPSET_ERR_NAMEREF; diff --git a/1122-netfilter-ctnetlink-zero-expect-nat-fields-when-cta-expect-nat-absent.patch b/1122-netfilter-ctnetlink-zero-expect-nat-fields-when-cta-expect-nat-absent.patch new file mode 100644 index 000000000..bda94e949 --- /dev/null +++ b/1122-netfilter-ctnetlink-zero-expect-nat-fields-when-cta-expect-nat-absent.patch @@ -0,0 +1,58 @@ +From fc4bd898b84a9af433a40544389691829990d4b2 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:35 +0200 +Subject: [PATCH] netfilter: ctnetlink: zero expect NAT fields when + CTA_EXPECT_NAT absent + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 35177c687713 + +commit 35177c6877134a21315f37d57a5577846225623e +Author: Qi Tang +Date: Tue Mar 31 14:17:12 2026 +0800 + + netfilter: ctnetlink: zero expect NAT fields when CTA_EXPECT_NAT absent + + ctnetlink_alloc_expect() allocates expectations from a non-zeroing + slab cache via nf_ct_expect_alloc(). When CTA_EXPECT_NAT is not + present in the netlink message, saved_addr and saved_proto are + never initialized. Stale data from a previous slab occupant can + then be dumped to userspace by ctnetlink_exp_dump_expect(), which + checks these fields to decide whether to emit CTA_EXPECT_NAT. + + The safe sibling nf_ct_expect_init(), used by the packet path, + explicitly zeroes these fields. + + Zero saved_addr, saved_proto and dir in the else branch, guarded + by IS_ENABLED(CONFIG_NF_NAT) since these fields only exist when + NAT is enabled. + + Confirmed by priming the expect slab with NAT-bearing expectations, + freeing them, creating a new expectation without CTA_EXPECT_NAT, + and observing that the ctnetlink dump emits a spurious + CTA_EXPECT_NAT containing stale data from the prior allocation. + + Fixes: 076a0ca02644 ("netfilter: ctnetlink: add NAT support for expectations") + Reported-by: kernel test robot + Signed-off-by: Qi Tang + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index edc6045..208e26a 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3607,6 +3607,12 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + exp, nf_ct_l3num(ct)); + if (err < 0) + goto err_out; ++#if IS_ENABLED(CONFIG_NF_NAT) ++ } else { ++ memset(&exp->saved_addr, 0, sizeof(exp->saved_addr)); ++ memset(&exp->saved_proto, 0, sizeof(exp->saved_proto)); ++ exp->dir = 0; ++#endif + } + return exp; + err_out: diff --git a/1123-netfilter-ctnetlink-ignore-explicit-helper-on-new-expectations.patch b/1123-netfilter-ctnetlink-ignore-explicit-helper-on-new-expectations.patch new file mode 100644 index 000000000..2390edb05 --- /dev/null +++ b/1123-netfilter-ctnetlink-ignore-explicit-helper-on-new-expectations.patch @@ -0,0 +1,169 @@ +From dbc7553b9ac766a071e3fdd0467041890bf514da Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:37 +0200 +Subject: [PATCH] netfilter: ctnetlink: ignore explicit helper on new + expectations + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 917b61fa2042 + +commit 917b61fa2042f11e2af4c428e43f08199586633a +Author: Pablo Neira Ayuso +Date: Mon Mar 30 11:26:22 2026 +0200 + + netfilter: ctnetlink: ignore explicit helper on new expectations + + Use the existing master conntrack helper, anything else is not really + supported and it just makes validation more complicated, so just ignore + what helper userspace suggests for this expectation. + + This was uncovered when validating CTA_EXPECT_CLASS via different helper + provided by userspace than the existing master conntrack helper: + + BUG: KASAN: slab-out-of-bounds in nf_ct_expect_related_report+0x2479/0x27c0 + Read of size 4 at addr ffff8880043fe408 by task poc/102 + Call Trace: + nf_ct_expect_related_report+0x2479/0x27c0 + ctnetlink_create_expect+0x22b/0x3b0 + ctnetlink_new_expect+0x4bd/0x5c0 + nfnetlink_rcv_msg+0x67a/0x950 + netlink_rcv_skb+0x120/0x350 + + Allowing to read kernel memory bytes off the expectation boundary. + + CTA_EXPECT_HELP_NAME is still used to offer the helper name to userspace + via netlink dump. + + Fixes: bd0779370588 ("netfilter: nfnetlink_queue: allow to attach expectations to conntracks") + Reported-by: Qi Tang + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 208e26a..dcef3ef 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -2655,7 +2655,6 @@ 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, +- struct nf_conntrack_helper *helper, + struct nf_conntrack_tuple *tuple, + struct nf_conntrack_tuple *mask); + +@@ -2884,7 +2883,6 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct, + { + struct nlattr *cda[CTA_EXPECT_MAX+1]; + struct nf_conntrack_tuple tuple, mask; +- struct nf_conntrack_helper *helper = NULL; + struct nf_conntrack_expect *exp; + int err; + +@@ -2898,17 +2896,8 @@ 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]); +- +- helper = __nf_conntrack_helper_find(helpname, nf_ct_l3num(ct), +- nf_ct_protonum(ct)); +- if (helper == NULL) +- return -EOPNOTSUPP; +- } +- + exp = ctnetlink_alloc_expect((const struct nlattr * const *)cda, ct, +- helper, &tuple, &mask); ++ &tuple, &mask); + if (IS_ERR(exp)) + return PTR_ERR(exp); + +@@ -3547,11 +3536,11 @@ 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, +- struct nf_conntrack_helper *helper, + struct nf_conntrack_tuple *tuple, + struct nf_conntrack_tuple *mask) + { + struct net *net = read_pnet(&ct->ct_net); ++ struct nf_conntrack_helper *helper; + struct nf_conntrack_expect *exp; + struct nf_conn_help *help; + u32 class = 0; +@@ -3561,7 +3550,11 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + if (!help) + return ERR_PTR(-EOPNOTSUPP); + +- if (cda[CTA_EXPECT_CLASS] && helper) { ++ helper = rcu_dereference(help->helper); ++ if (!helper) ++ return ERR_PTR(-EOPNOTSUPP); ++ ++ if (cda[CTA_EXPECT_CLASS]) { + class = ntohl(nla_get_be32(cda[CTA_EXPECT_CLASS])); + if (class > helper->expect_class_max) + return ERR_PTR(-EINVAL); +@@ -3595,8 +3588,6 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + #ifdef CONFIG_NF_CONNTRACK_ZONES + exp->zone = ct->zone; + #endif +- if (!helper) +- helper = rcu_dereference(help->helper); + rcu_assign_pointer(exp->helper, helper); + exp->tuple = *tuple; + exp->mask.src.u3 = mask->src.u3; +@@ -3628,7 +3619,6 @@ ctnetlink_create_expect(struct net *net, + { + struct nf_conntrack_tuple tuple, mask, master_tuple; + struct nf_conntrack_tuple_hash *h = NULL; +- struct nf_conntrack_helper *helper = NULL; + struct nf_conntrack_expect *exp; + struct nf_conn *ct; + int err; +@@ -3654,33 +3644,7 @@ ctnetlink_create_expect(struct net *net, + ct = nf_ct_tuplehash_to_ctrack(h); + + rcu_read_lock(); +- if (cda[CTA_EXPECT_HELP_NAME]) { +- const char *helpname = nla_data(cda[CTA_EXPECT_HELP_NAME]); +- +- helper = __nf_conntrack_helper_find(helpname, u3, +- nf_ct_protonum(ct)); +- if (helper == NULL) { +- rcu_read_unlock(); +-#ifdef CONFIG_MODULES +- if (request_module("nfct-helper-%s", helpname) < 0) { +- err = -EOPNOTSUPP; +- goto err_ct; +- } +- rcu_read_lock(); +- helper = __nf_conntrack_helper_find(helpname, u3, +- nf_ct_protonum(ct)); +- if (helper) { +- err = -EAGAIN; +- goto err_rcu; +- } +- rcu_read_unlock(); +-#endif +- err = -EOPNOTSUPP; +- goto err_ct; +- } +- } +- +- exp = ctnetlink_alloc_expect(cda, ct, helper, &tuple, &mask); ++ exp = ctnetlink_alloc_expect(cda, ct, &tuple, &mask); + if (IS_ERR(exp)) { + err = PTR_ERR(exp); + goto err_rcu; +@@ -3690,8 +3654,8 @@ ctnetlink_create_expect(struct net *net, + nf_ct_expect_put(exp); + err_rcu: + rcu_read_unlock(); +-err_ct: + nf_ct_put(ct); ++ + return err; + } + diff --git a/1124-netfilter-x-tables-restrict-xt-check-match-xt-check-target-extensions-for-nfprot.patch b/1124-netfilter-x-tables-restrict-xt-check-match-xt-check-target-extensions-for-nfprot.patch new file mode 100644 index 000000000..9b8d5bce0 --- /dev/null +++ b/1124-netfilter-x-tables-restrict-xt-check-match-xt-check-target-extensions-for-nfprot.patch @@ -0,0 +1,101 @@ +From 55107f6cfe5236b5522a7b83bdc908ea36acf54c Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:40 +0200 +Subject: [PATCH] netfilter: x_tables: restrict xt_check_match/xt_check_target + extensions for NFPROTO_ARP + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 3d5d488f1177 + +commit 3d5d488f11776738deab9da336038add95d342d1 +Author: Pablo Neira Ayuso +Date: Tue Mar 31 16:41:25 2026 +0200 + + netfilter: x_tables: restrict xt_check_match/xt_check_target extensions for NFPROTO_ARP + + Weiming Shi says: + + xt_match and xt_target structs registered with NFPROTO_UNSPEC can be + loaded by any protocol family through nft_compat. When such a + match/target sets .hooks to restrict which hooks it may run on, the + bitmask uses NF_INET_* constants. This is only correct for families + whose hook layout matches NF_INET_*: IPv4, IPv6, INET, and bridge + all share the same five hooks (PRE_ROUTING ... POST_ROUTING). + + ARP only has three hooks (IN=0, OUT=1, FORWARD=2) with different + semantics. Because NF_ARP_OUT == 1 == NF_INET_LOCAL_IN, the .hooks + validation silently passes for the wrong reasons, allowing matches to + run on ARP chains where the hook assumptions (e.g. state->in being + set on input hooks) do not hold. This leads to NULL pointer + dereferences; xt_devgroup is one concrete example: + + Oops: general protection fault, probably for non-canonical address 0xdffffc0000000044: 0000 [#1] SMP KASAN NOPTI + KASAN: null-ptr-deref in range [0x0000000000000220-0x0000000000000227] + RIP: 0010:devgroup_mt+0xff/0x350 + Call Trace: + + nft_match_eval (net/netfilter/nft_compat.c:407) + nft_do_chain (net/netfilter/nf_tables_core.c:285) + nft_do_chain_arp (net/netfilter/nft_chain_filter.c:61) + nf_hook_slow (net/netfilter/core.c:623) + arp_xmit (net/ipv4/arp.c:666) + + Kernel panic - not syncing: Fatal exception in interrupt + + Fix it by restricting arptables to NFPROTO_ARP extensions only. + Note that arptables-legacy only supports: + + - arpt_CLASSIFY + - arpt_mangle + - arpt_MARK + + that provide explicit NFPROTO_ARP match/target declarations. + + Fixes: 9291747f118d ("netfilter: xtables: add device group match") + Reported-by: Xiang Mei + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c +index 7098406..10530a0 100644 +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -501,6 +501,17 @@ int xt_check_match(struct xt_mtchk_param *par, + par->match->table, par->table); + return -EINVAL; + } ++ ++ /* NFPROTO_UNSPEC implies NF_INET_* hooks which do not overlap with ++ * NF_ARP_IN,OUT,FORWARD, allow explicit extensions with NFPROTO_ARP ++ * support. ++ */ ++ if (par->family == NFPROTO_ARP && ++ par->match->family != NFPROTO_ARP) { ++ pr_info_ratelimited("%s_tables: %s match: not valid for this family\n", ++ xt_prefix[par->family], par->match->name); ++ return -EINVAL; ++ } + if (par->match->hooks && (par->hook_mask & ~par->match->hooks) != 0) { + char used[64], allow[64]; + +@@ -1016,6 +1027,18 @@ int xt_check_target(struct xt_tgchk_param *par, + par->target->table, par->table); + return -EINVAL; + } ++ ++ /* NFPROTO_UNSPEC implies NF_INET_* hooks which do not overlap with ++ * NF_ARP_IN,OUT,FORWARD, allow explicit extensions with NFPROTO_ARP ++ * support. ++ */ ++ if (par->family == NFPROTO_ARP && ++ par->target->family != NFPROTO_ARP) { ++ pr_info_ratelimited("%s_tables: %s target: not valid for this family\n", ++ xt_prefix[par->family], par->target->name); ++ return -EINVAL; ++ } ++ + if (par->target->hooks && (par->hook_mask & ~par->target->hooks) != 0) { + char used[64], allow[64]; + diff --git a/1125-netfilter-nf-tables-reject-immediate-nf-queue-verdict.patch b/1125-netfilter-nf-tables-reject-immediate-nf-queue-verdict.patch new file mode 100644 index 000000000..638498b02 --- /dev/null +++ b/1125-netfilter-nf-tables-reject-immediate-nf-queue-verdict.patch @@ -0,0 +1,52 @@ +From 94a7ba900e7ea7da14a796edc10401a700d3b894 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:43 +0200 +Subject: [PATCH] netfilter: nf_tables: reject immediate NF_QUEUE verdict + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit da107398cbd4 + +commit da107398cbd4bbdb6bffecb2ce86d5c9384f4cec +Author: Pablo Neira Ayuso +Date: Tue Mar 31 23:08:02 2026 +0200 + + netfilter: nf_tables: reject immediate NF_QUEUE verdict + + nft_queue is always used from userspace nftables to deliver the NF_QUEUE + verdict. Immediately emitting an NF_QUEUE verdict is never used by the + userspace nft tools, so reject immediate NF_QUEUE verdicts. + + The arp family does not provide queue support, but such an immediate + verdict is still reachable. Globally reject NF_QUEUE immediate verdicts + to address this issue. + + Fixes: f342de4e2f33 ("netfilter: nf_tables: reject QUEUE/DROP verdict parameters") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 81164ad..7cc4163 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -11804,8 +11804,6 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, + switch (data->verdict.code) { + case NF_ACCEPT: + case NF_DROP: +- case NF_QUEUE: +- break; + case NFT_CONTINUE: + case NFT_BREAK: + case NFT_RETURN: +@@ -11840,6 +11838,11 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, + + data->verdict.chain = chain; + break; ++ case NF_QUEUE: ++ /* The nft_queue expression is used for this purpose, an ++ * immediate NF_QUEUE verdict should not ever be seen here. ++ */ ++ fallthrough; + default: + return -EINVAL; + } diff --git a/1126-netfilter-nfnetlink-log-initialize-nfgenmsg-in-nlmsg-done-terminator.patch b/1126-netfilter-nfnetlink-log-initialize-nfgenmsg-in-nlmsg-done-terminator.patch new file mode 100644 index 000000000..d99667614 --- /dev/null +++ b/1126-netfilter-nfnetlink-log-initialize-nfgenmsg-in-nlmsg-done-terminator.patch @@ -0,0 +1,52 @@ +From a9469302e0f5583c2d4a0796bf8e723f2a90e01c Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:20:51 +0200 +Subject: [PATCH] netfilter: nfnetlink_log: initialize nfgenmsg in NLMSG_DONE + terminator + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 1f3083aec883 + +commit 1f3083aec8836213da441270cdb1ab612dd82cf4 +Author: Xiang Mei +Date: Wed Apr 1 14:20:57 2026 -0700 + + netfilter: nfnetlink_log: initialize nfgenmsg in NLMSG_DONE terminator + + When batching multiple NFLOG messages (inst->qlen > 1), __nfulnl_send() + appends an NLMSG_DONE terminator with sizeof(struct nfgenmsg) payload via + nlmsg_put(), but never initializes the nfgenmsg bytes. The nlmsg_put() + helper only zeroes alignment padding after the payload, not the payload + itself, so four bytes of stale kernel heap data are leaked to userspace + in the NLMSG_DONE message body. + + Use nfnl_msg_put() to build the NLMSG_DONE terminator, which initializes + the nfgenmsg payload via nfnl_fill_hdr(), consistent with how + __build_packet_message() already constructs NFULNL_MSG_PACKET headers. + + Fixes: 29c5d4afba51 ("[NETFILTER]: nfnetlink_log: fix sending of multipart messages") + Reported-by: Weiming Shi + Signed-off-by: Xiang Mei + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c +index dcd2493..b1f3eda 100644 +--- a/net/netfilter/nfnetlink_log.c ++++ b/net/netfilter/nfnetlink_log.c +@@ -361,10 +361,10 @@ static void + __nfulnl_send(struct nfulnl_instance *inst) + { + if (inst->qlen > 1) { +- struct nlmsghdr *nlh = nlmsg_put(inst->skb, 0, 0, +- NLMSG_DONE, +- sizeof(struct nfgenmsg), +- 0); ++ struct nlmsghdr *nlh = nfnl_msg_put(inst->skb, 0, 0, ++ NLMSG_DONE, 0, ++ AF_UNSPEC, NFNETLINK_V0, ++ htons(inst->group_num)); + if (WARN_ONCE(!nlh, "bad nlskb size: %u, tailroom %d\n", + inst->skb->len, skb_tailroom(inst->skb))) { + kfree_skb(inst->skb); diff --git a/1127-netfilter-xt-multiport-validate-range-encoding-in-checkentry.patch b/1127-netfilter-xt-multiport-validate-range-encoding-in-checkentry.patch new file mode 100644 index 000000000..b5482d0f7 --- /dev/null +++ b/1127-netfilter-xt-multiport-validate-range-encoding-in-checkentry.patch @@ -0,0 +1,100 @@ +From 8fa17e23a20d7981a2193301408f1f339ea2d057 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:20:52 +0200 +Subject: [PATCH] netfilter: xt_multiport: validate range encoding in + checkentry + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit ff64c5bfef12 + +commit ff64c5bfef12461df8450e0f50bb693b5269c720 +Author: Ren Wei +Date: Fri Apr 3 23:52:52 2026 +0800 + + netfilter: xt_multiport: validate range encoding in checkentry + + ports_match_v1() treats any non-zero pflags entry as the start of a + port range and unconditionally consumes the next ports[] element as + the range end. + + The checkentry path currently validates protocol, flags and count, but + it does not validate the range encoding itself. As a result, malformed + rules can mark the last slot as a range start or place two range starts + back to back, leaving ports_match_v1() to step past the last valid + ports[] element while interpreting the rule. + + Reject malformed multiport v1 rules in checkentry by validating that + each range start has a following element and that the following element + is not itself marked as another range start. + + Fixes: a89ecb6a2ef7 ("[NETFILTER]: x_tables: unify IPv4/IPv6 multiport match") + Reported-by: Yifan Wu + Reported-by: Juefei Pu + Co-developed-by: Yuan Tan + Signed-off-by: Yuan Tan + Suggested-by: Xin Liu + Tested-by: Yuhang Zheng + Signed-off-by: Ren Wei + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/xt_multiport.c b/net/netfilter/xt_multiport.c +index 44a00f5..a1691ff 100644 +--- a/net/netfilter/xt_multiport.c ++++ b/net/netfilter/xt_multiport.c +@@ -105,6 +105,28 @@ multiport_mt(const struct sk_buff *skb, struct xt_action_param *par) + return ports_match_v1(multiinfo, ntohs(pptr[0]), ntohs(pptr[1])); + } + ++static bool ++multiport_valid_ranges(const struct xt_multiport_v1 *multiinfo) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < multiinfo->count; i++) { ++ if (!multiinfo->pflags[i]) ++ continue; ++ ++ if (++i >= multiinfo->count) ++ return false; ++ ++ if (multiinfo->pflags[i]) ++ return false; ++ ++ if (multiinfo->ports[i - 1] > multiinfo->ports[i]) ++ return false; ++ } ++ ++ return true; ++} ++ + static inline bool + check(u_int16_t proto, + u_int8_t ip_invflags, +@@ -127,8 +149,10 @@ static int multiport_mt_check(const struct xt_mtchk_param *par) + const struct ipt_ip *ip = par->entryinfo; + const struct xt_multiport_v1 *multiinfo = par->matchinfo; + +- return check(ip->proto, ip->invflags, multiinfo->flags, +- multiinfo->count) ? 0 : -EINVAL; ++ if (!check(ip->proto, ip->invflags, multiinfo->flags, multiinfo->count)) ++ return -EINVAL; ++ ++ return multiport_valid_ranges(multiinfo) ? 0 : -EINVAL; + } + + static int multiport_mt6_check(const struct xt_mtchk_param *par) +@@ -136,8 +160,10 @@ static int multiport_mt6_check(const struct xt_mtchk_param *par) + const struct ip6t_ip6 *ip = par->entryinfo; + const struct xt_multiport_v1 *multiinfo = par->matchinfo; + +- return check(ip->proto, ip->invflags, multiinfo->flags, +- multiinfo->count) ? 0 : -EINVAL; ++ if (!check(ip->proto, ip->invflags, multiinfo->flags, multiinfo->count)) ++ return -EINVAL; ++ ++ return multiport_valid_ranges(multiinfo) ? 0 : -EINVAL; + } + + static struct xt_match multiport_mt_reg[] __read_mostly = { diff --git a/1128-netfilter-nft-ct-fix-use-after-free-in-timeout-object-destroy.patch b/1128-netfilter-nft-ct-fix-use-after-free-in-timeout-object-destroy.patch new file mode 100644 index 000000000..6c6244795 --- /dev/null +++ b/1128-netfilter-nft-ct-fix-use-after-free-in-timeout-object-destroy.patch @@ -0,0 +1,80 @@ +From b79e7ff0af28845109dc3fcd964751f0391ceebf Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:20:53 +0200 +Subject: [PATCH] netfilter: nft_ct: fix use-after-free in timeout object + destroy + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f8dca15a1b19 + +commit f8dca15a1b190787bbd03285304b569631160eda +Author: Tuan Do +Date: Fri Apr 3 00:33:17 2026 -0700 + + netfilter: nft_ct: fix use-after-free in timeout object destroy + + nft_ct_timeout_obj_destroy() frees the timeout object with kfree() + immediately after nf_ct_untimeout(), without waiting for an RCU grace + period. Concurrent packet processing on other CPUs may still hold + RCU-protected references to the timeout object obtained via + rcu_dereference() in nf_ct_timeout_data(). + + Add an rcu_head to struct nf_ct_timeout and use kfree_rcu() to defer + freeing until after an RCU grace period, matching the approach already + used in nfnetlink_cttimeout.c. + + KASAN report: + BUG: KASAN: slab-use-after-free in nf_conntrack_tcp_packet+0x1381/0x29d0 + Read of size 4 at addr ffff8881035fe19c by task exploit/80 + + Call Trace: + nf_conntrack_tcp_packet+0x1381/0x29d0 + nf_conntrack_in+0x612/0x8b0 + nf_hook_slow+0x70/0x100 + __ip_local_out+0x1b2/0x210 + tcp_sendmsg_locked+0x722/0x1580 + __sys_sendto+0x2d8/0x320 + + Allocated by task 75: + nft_ct_timeout_obj_init+0xf6/0x290 + nft_obj_init+0x107/0x1b0 + nf_tables_newobj+0x680/0x9c0 + nfnetlink_rcv_batch+0xc29/0xe00 + + Freed by task 26: + nft_obj_destroy+0x3f/0xa0 + nf_tables_trans_destroy_work+0x51c/0x5c0 + process_one_work+0x2c4/0x5a0 + + Fixes: 7e0b2b57f01d ("netfilter: nft_ct: add ct timeout support") + Cc: stable@vger.kernel.org + Signed-off-by: Tuan Do + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_conntrack_timeout.h b/include/net/netfilter/nf_conntrack_timeout.h +index 9fdaba9..3a66d4a 100644 +--- a/include/net/netfilter/nf_conntrack_timeout.h ++++ b/include/net/netfilter/nf_conntrack_timeout.h +@@ -14,6 +14,7 @@ + struct nf_ct_timeout { + __u16 l3num; + const struct nf_conntrack_l4proto *l4proto; ++ struct rcu_head rcu; + char data[]; + }; + +diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c +index 6f2ae7c..d0090d0 100644 +--- a/net/netfilter/nft_ct.c ++++ b/net/netfilter/nft_ct.c +@@ -1018,7 +1018,7 @@ static void nft_ct_timeout_obj_destroy(const struct nft_ctx *ctx, + + nf_ct_untimeout(ctx->net, timeout); + nf_ct_netns_put(ctx->net, ctx->family); +- kfree(priv->timeout); ++ kfree_rcu(priv->timeout, rcu); + } + + static int nft_ct_timeout_obj_dump(struct sk_buff *skb, diff --git a/1129-netfilter-nft-osf-restrict-it-to-ipv4.patch b/1129-netfilter-nft-osf-restrict-it-to-ipv4.patch new file mode 100644 index 000000000..ca8e75e17 --- /dev/null +++ b/1129-netfilter-nft-osf-restrict-it-to-ipv4.patch @@ -0,0 +1,47 @@ +From dc881297140fa0d80136180c1e2e1d66c5adac44 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:15 +0200 +Subject: [PATCH] netfilter: nft_osf: restrict it to ipv4 + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit b336fdbb7103 + +commit b336fdbb7103fb1484e1dcb6741151d4b5a41e35 +Author: Pablo Neira Ayuso +Date: Tue Apr 14 13:06:38 2026 +0200 + + netfilter: nft_osf: restrict it to ipv4 + + This expression only supports for ipv4, restrict it. + + Fixes: b96af92d6eaf ("netfilter: nf_tables: implement Passive OS fingerprint module in nft_osf") + Acked-by: Florian Westphal + Reviewed-by: Fernando Fernandez Mancera + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nft_osf.c b/net/netfilter/nft_osf.c +index 1c0b493..bdc2f6c 100644 +--- a/net/netfilter/nft_osf.c ++++ b/net/netfilter/nft_osf.c +@@ -28,6 +28,11 @@ static void nft_osf_eval(const struct nft_expr *expr, struct nft_regs *regs, + struct nf_osf_data data; + struct tcphdr _tcph; + ++ if (nft_pf(pkt) != NFPROTO_IPV4) { ++ regs->verdict.code = NFT_BREAK; ++ return; ++ } ++ + if (pkt->tprot != IPPROTO_TCP) { + regs->verdict.code = NFT_BREAK; + return; +@@ -114,7 +119,6 @@ static int nft_osf_validate(const struct nft_ctx *ctx, + + switch (ctx->family) { + case NFPROTO_IPV4: +- case NFPROTO_IPV6: + case NFPROTO_INET: + hooks = (1 << NF_INET_LOCAL_IN) | + (1 << NF_INET_PRE_ROUTING) | diff --git a/1130-nfnetlink-osf-validate-individual-option-lengths-in-fingerprints.patch b/1130-nfnetlink-osf-validate-individual-option-lengths-in-fingerprints.patch new file mode 100644 index 000000000..fd08fa22b --- /dev/null +++ b/1130-nfnetlink-osf-validate-individual-option-lengths-in-fingerprints.patch @@ -0,0 +1,84 @@ +From 05577640875b9759b4511a3cbe9cbcd2f92e27f3 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:11 +0200 +Subject: [PATCH] nfnetlink_osf: validate individual option lengths in + fingerprints + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit dbdfaae96096 + +commit dbdfaae9609629a9569362e3b8f33d0a20fd783c +Author: Weiming Shi +Date: Thu Mar 19 15:32:44 2026 +0800 + + nfnetlink_osf: validate individual option lengths in fingerprints + + nfnl_osf_add_callback() validates opt_num bounds and string + NUL-termination but does not check individual option length fields. + A zero-length option causes nf_osf_match_one() to enter the option + matching loop even when foptsize sums to zero, which matches packets + with no TCP options where ctx->optp is NULL: + + Oops: general protection fault + KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007] + RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98) + Call Trace: + nf_osf_match (net/netfilter/nfnetlink_osf.c:227) + xt_osf_match_packet (net/netfilter/xt_osf.c:32) + ipt_do_table (net/ipv4/netfilter/ip_tables.c:293) + nf_hook_slow (net/netfilter/core.c:623) + ip_local_deliver (net/ipv4/ip_input.c:262) + ip_rcv (net/ipv4/ip_input.c:573) + + Additionally, an MSS option (kind=2) with length < 4 causes + out-of-bounds reads when nf_osf_match_one() unconditionally accesses + optp[2] and optp[3] for MSS value extraction. While RFC 9293 + section 3.2 specifies that the MSS option is always exactly 4 + bytes (Kind=2, Length=4), the check uses "< 4" rather than + "!= 4" because lengths greater than 4 do not cause memory + safety issues -- the buffer is guaranteed to be at least + foptsize bytes by the ctx->optsize == foptsize check. + + Reject fingerprints where any option has zero length, or where an MSS + option has length less than 4, at add time rather than trusting these + values in the packet matching hot path. + + Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") + Reported-by: Xiang Mei + Signed-off-by: Weiming Shi + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c +index c0fc431..9fc9544 100644 +--- a/net/netfilter/nfnetlink_osf.c ++++ b/net/netfilter/nfnetlink_osf.c +@@ -302,7 +302,9 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, + { + struct nf_osf_user_finger *f; + struct nf_osf_finger *kf = NULL, *sf; ++ unsigned int tot_opt_len = 0; + int err = 0; ++ int i; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; +@@ -318,6 +320,17 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, + if (f->opt_num > ARRAY_SIZE(f->opt)) + return -EINVAL; + ++ for (i = 0; i < f->opt_num; i++) { ++ if (!f->opt[i].length || f->opt[i].length > MAX_IPOPTLEN) ++ return -EINVAL; ++ if (f->opt[i].kind == OSFOPT_MSS && f->opt[i].length < 4) ++ return -EINVAL; ++ ++ tot_opt_len += f->opt[i].length; ++ if (tot_opt_len > MAX_IPOPTLEN) ++ return -EINVAL; ++ } ++ + if (!memchr(f->genre, 0, MAXGENRELEN) || + !memchr(f->subtype, 0, MAXGENRELEN) || + !memchr(f->version, 0, MAXGENRELEN)) diff --git a/1131-netfilter-nfnetlink-osf-fix-divide-by-zero-in-osf-wss-modulo.patch b/1131-netfilter-nfnetlink-osf-fix-divide-by-zero-in-osf-wss-modulo.patch new file mode 100644 index 000000000..b7108c94f --- /dev/null +++ b/1131-netfilter-nfnetlink-osf-fix-divide-by-zero-in-osf-wss-modulo.patch @@ -0,0 +1,68 @@ +From aea9be3048d15f93e770970a6206c38bdaa11cb2 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:16 +0200 +Subject: [PATCH] netfilter: nfnetlink_osf: fix divide-by-zero in + OSF_WSS_MODULO + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 2195574dc6d9 + +commit 2195574dc6d9017d32ac346987e12659f931d932 +Author: Xiang Mei +Date: Tue Apr 14 15:14:01 2026 -0700 + + netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO + + nf_osf_match_one() computes ctx->window % f->wss.val in the + OSF_WSS_MODULO branch with no guard for f->wss.val == 0. A + CAP_NET_ADMIN user can add such a fingerprint via nfnetlink; a + subsequent matching TCP SYN divides by zero and panics the kernel. + + Reject the bogus fingerprint in nfnl_osf_add_callback() above the + per-option for-loop. f->wss is per-fingerprint, not per-option, so + the check must run regardless of f->opt_num (including 0). Also + reject wss.wc >= OSF_WSS_MAX; nf_osf_match_one() already treats that + as "should not happen". + + Crash: + Oops: divide error: 0000 [#1] SMP KASAN NOPTI + RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98) + Call Trace: + + nf_osf_match (net/netfilter/nfnetlink_osf.c:220) + xt_osf_match_packet (net/netfilter/xt_osf.c:32) + ipt_do_table (net/ipv4/netfilter/ip_tables.c:348) + nf_hook_slow (net/netfilter/core.c:622) + ip_local_deliver (net/ipv4/ip_input.c:265) + ip_rcv (include/linux/skbuff.h:1162) + __netif_receive_skb_one_core (net/core/dev.c:6181) + process_backlog (net/core/dev.c:6642) + __napi_poll (net/core/dev.c:7710) + net_rx_action (net/core/dev.c:7945) + handle_softirqs (kernel/softirq.c:622) + + Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") + Reported-by: Weiming Shi + Suggested-by: Florian Westphal + Suggested-by: Pablo Neira Ayuso + Signed-off-by: Xiang Mei + Reviewed-by: Fernando Fernandez Mancera + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c +index 9fc9544..2305c7d 100644 +--- a/net/netfilter/nfnetlink_osf.c ++++ b/net/netfilter/nfnetlink_osf.c +@@ -320,6 +320,10 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, + if (f->opt_num > ARRAY_SIZE(f->opt)) + return -EINVAL; + ++ if (f->wss.wc >= OSF_WSS_MAX || ++ (f->wss.wc == OSF_WSS_MODULO && f->wss.val == 0)) ++ return -EINVAL; ++ + for (i = 0; i < f->opt_num; i++) { + if (!f->opt[i].length || f->opt[i].length > MAX_IPOPTLEN) + return -EINVAL; diff --git a/1132-netfilter-conntrack-remove-sprintf-usage.patch b/1132-netfilter-conntrack-remove-sprintf-usage.patch new file mode 100644 index 000000000..bb924b32b --- /dev/null +++ b/1132-netfilter-conntrack-remove-sprintf-usage.patch @@ -0,0 +1,181 @@ +From 1fa34c650b5f6cf04b877fa6cb5337ee834c826d Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:16 +0200 +Subject: [PATCH] netfilter: conntrack: remove sprintf usage + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 6e7066bdb481 + +commit 6e7066bdb481a87fe88c4fa563e348c03b2d373d +Author: Florian Westphal +Date: Tue Apr 14 19:13:46 2026 +0200 + + netfilter: conntrack: remove sprintf usage + + Replace it with scnprintf, the buffer sizes are expected to be large enough + to hold the result, no need for snprintf+overflow check. + + Increase buffer size in mangle_content_len() while at it. + + BUG: KASAN: stack-out-of-bounds in vsnprintf+0xea5/0x1270 + Write of size 1 at addr [..] + vsnprintf+0xea5/0x1270 + sprintf+0xb1/0xe0 + mangle_content_len+0x1ac/0x280 + nf_nat_sdp_session+0x1cc/0x240 + process_sdp+0x8f8/0xb80 + process_invite_request+0x108/0x2b0 + process_sip_msg+0x5da/0xf50 + sip_help_tcp+0x45e/0x780 + nf_confirm+0x34d/0x990 + [..] + + Fixes: 9fafcd7b2032 ("[NETFILTER]: nf_conntrack/nf_nat: add SIP helper port") + Reported-by: Yiming Qian + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_nat_amanda.c b/net/netfilter/nf_nat_amanda.c +index 98deef6..8f10549 100644 +--- a/net/netfilter/nf_nat_amanda.c ++++ b/net/netfilter/nf_nat_amanda.c +@@ -50,7 +50,7 @@ static unsigned int help(struct sk_buff *skb, + return NF_DROP; + } + +- sprintf(buffer, "%u", port); ++ snprintf(buffer, sizeof(buffer), "%u", port); + if (!nf_nat_mangle_udp_packet(skb, exp->master, ctinfo, + protoff, matchoff, matchlen, + buffer, strlen(buffer))) { +diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c +index cf4aeb2..c845b6d 100644 +--- a/net/netfilter/nf_nat_sip.c ++++ b/net/netfilter/nf_nat_sip.c +@@ -68,25 +68,27 @@ static unsigned int mangle_packet(struct sk_buff *skb, unsigned int protoff, + } + + static int sip_sprintf_addr(const struct nf_conn *ct, char *buffer, ++ size_t size, + const union nf_inet_addr *addr, bool delim) + { + if (nf_ct_l3num(ct) == NFPROTO_IPV4) +- return sprintf(buffer, "%pI4", &addr->ip); ++ return scnprintf(buffer, size, "%pI4", &addr->ip); + else { + if (delim) +- return sprintf(buffer, "[%pI6c]", &addr->ip6); ++ return scnprintf(buffer, size, "[%pI6c]", &addr->ip6); + else +- return sprintf(buffer, "%pI6c", &addr->ip6); ++ return scnprintf(buffer, size, "%pI6c", &addr->ip6); + } + } + + static int sip_sprintf_addr_port(const struct nf_conn *ct, char *buffer, ++ size_t size, + const union nf_inet_addr *addr, u16 port) + { + if (nf_ct_l3num(ct) == NFPROTO_IPV4) +- return sprintf(buffer, "%pI4:%u", &addr->ip, port); ++ return scnprintf(buffer, size, "%pI4:%u", &addr->ip, port); + else +- return sprintf(buffer, "[%pI6c]:%u", &addr->ip6, port); ++ return scnprintf(buffer, size, "[%pI6c]:%u", &addr->ip6, port); + } + + static int map_addr(struct sk_buff *skb, unsigned int protoff, +@@ -119,7 +121,7 @@ static int map_addr(struct sk_buff *skb, unsigned int protoff, + if (nf_inet_addr_cmp(&newaddr, addr) && newport == port) + return 1; + +- buflen = sip_sprintf_addr_port(ct, buffer, &newaddr, ntohs(newport)); ++ buflen = sip_sprintf_addr_port(ct, buffer, sizeof(buffer), &newaddr, ntohs(newport)); + return mangle_packet(skb, protoff, dataoff, dptr, datalen, + matchoff, matchlen, buffer, buflen); + } +@@ -212,7 +214,7 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, + &addr, true) > 0 && + nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.src.u3) && + !nf_inet_addr_cmp(&addr, &ct->tuplehash[!dir].tuple.dst.u3)) { +- buflen = sip_sprintf_addr(ct, buffer, ++ buflen = sip_sprintf_addr(ct, buffer, sizeof(buffer), + &ct->tuplehash[!dir].tuple.dst.u3, + true); + if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, +@@ -229,7 +231,7 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, + &addr, false) > 0 && + nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.dst.u3) && + !nf_inet_addr_cmp(&addr, &ct->tuplehash[!dir].tuple.src.u3)) { +- buflen = sip_sprintf_addr(ct, buffer, ++ buflen = sip_sprintf_addr(ct, buffer, sizeof(buffer), + &ct->tuplehash[!dir].tuple.src.u3, + false); + if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, +@@ -247,7 +249,7 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, + htons(n) == ct->tuplehash[dir].tuple.dst.u.udp.port && + htons(n) != ct->tuplehash[!dir].tuple.src.u.udp.port) { + __be16 p = ct->tuplehash[!dir].tuple.src.u.udp.port; +- buflen = sprintf(buffer, "%u", ntohs(p)); ++ buflen = scnprintf(buffer, sizeof(buffer), "%u", ntohs(p)); + if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, + poff, plen, buffer, buflen)) { + nf_ct_helper_log(skb, ct, "cannot mangle rport"); +@@ -418,7 +420,8 @@ static unsigned int nf_nat_sip_expect(struct sk_buff *skb, unsigned int protoff, + + if (!nf_inet_addr_cmp(&exp->tuple.dst.u3, &exp->saved_addr) || + exp->tuple.dst.u.udp.port != exp->saved_proto.udp.port) { +- buflen = sip_sprintf_addr_port(ct, buffer, &newaddr, port); ++ buflen = sip_sprintf_addr_port(ct, buffer, sizeof(buffer), ++ &newaddr, port); + if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, + matchoff, matchlen, buffer, buflen)) { + nf_ct_helper_log(skb, ct, "cannot mangle packet"); +@@ -438,8 +441,8 @@ static int mangle_content_len(struct sk_buff *skb, unsigned int protoff, + { + enum ip_conntrack_info ctinfo; + struct nf_conn *ct = nf_ct_get(skb, &ctinfo); ++ char buffer[sizeof("4294967295")]; + unsigned int matchoff, matchlen; +- char buffer[sizeof("65536")]; + int buflen, c_len; + + /* Get actual SDP length */ +@@ -454,7 +457,7 @@ static int mangle_content_len(struct sk_buff *skb, unsigned int protoff, + &matchoff, &matchlen) <= 0) + return 0; + +- buflen = sprintf(buffer, "%u", c_len); ++ buflen = scnprintf(buffer, sizeof(buffer), "%u", c_len); + return mangle_packet(skb, protoff, dataoff, dptr, datalen, + matchoff, matchlen, buffer, buflen); + } +@@ -491,7 +494,7 @@ static unsigned int nf_nat_sdp_addr(struct sk_buff *skb, unsigned int protoff, + char buffer[INET6_ADDRSTRLEN]; + unsigned int buflen; + +- buflen = sip_sprintf_addr(ct, buffer, addr, false); ++ buflen = sip_sprintf_addr(ct, buffer, sizeof(buffer), addr, false); + if (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, + sdpoff, type, term, buffer, buflen)) + return 0; +@@ -509,7 +512,7 @@ static unsigned int nf_nat_sdp_port(struct sk_buff *skb, unsigned int protoff, + char buffer[sizeof("nnnnn")]; + unsigned int buflen; + +- buflen = sprintf(buffer, "%u", port); ++ buflen = scnprintf(buffer, sizeof(buffer), "%u", port); + if (!mangle_packet(skb, protoff, dataoff, dptr, datalen, + matchoff, matchlen, buffer, buflen)) + return 0; +@@ -529,7 +532,7 @@ static unsigned int nf_nat_sdp_session(struct sk_buff *skb, unsigned int protoff + unsigned int buflen; + + /* Mangle session description owner and contact addresses */ +- buflen = sip_sprintf_addr(ct, buffer, addr, false); ++ buflen = sip_sprintf_addr(ct, buffer, sizeof(buffer), addr, false); + if (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, sdpoff, + SDP_HDR_OWNER, SDP_HDR_MEDIA, buffer, buflen)) + return 0; diff --git a/1133-netfilter-nat-use-kfree-rcu-to-release-ops.patch b/1133-netfilter-nat-use-kfree-rcu-to-release-ops.patch new file mode 100644 index 000000000..bd4559cd4 --- /dev/null +++ b/1133-netfilter-nat-use-kfree-rcu-to-release-ops.patch @@ -0,0 +1,112 @@ +From 827d950e20067e03f0bdc4dda3c48d1162e1077c Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:17 +0200 +Subject: [PATCH] netfilter: nat: use kfree_rcu to release ops + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 6eda0d771f94 + +commit 6eda0d771f94267f73f57c94630aa47e90957915 +Author: Pablo Neira Ayuso +Date: Wed Apr 15 17:29:45 2026 +0200 + + netfilter: nat: use kfree_rcu to release ops + + Florian Westphal says: + + "Historically this is not an issue, even for normal base hooks: the data + path doesn't use the original nf_hook_ops that are used to register the + callbacks. + + However, in v5.14 I added the ability to dump the active netfilter + hooks from userspace. + + This code will peek back into the nf_hook_ops that are available + at the tail of the pointer-array blob used by the datapath. + + The nat hooks are special, because they are called indirectly from + the central nat dispatcher hook. They are currently invisible to + the nfnl hook dump subsystem though. + + But once that changes the nat ops structures have to be deferred too." + + Update nf_nat_register_fn() to deal with partial exposition of the hooks + from error path which can be also an issue for nfnetlink_hook. + + Fixes: e2cf17d3774c ("netfilter: add new hook nfnl subsystem") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/ipv4/netfilter/iptable_nat.c b/net/ipv4/netfilter/iptable_nat.c +index a5db7c6..625a1ca 100644 +--- a/net/ipv4/netfilter/iptable_nat.c ++++ b/net/ipv4/netfilter/iptable_nat.c +@@ -79,7 +79,7 @@ static int ipt_nat_register_lookups(struct net *net) + while (i) + nf_nat_ipv4_unregister_fn(net, &ops[--i]); + +- kfree(ops); ++ kfree_rcu(ops, rcu); + return ret; + } + } +@@ -100,7 +100,7 @@ static void ipt_nat_unregister_lookups(struct net *net) + for (i = 0; i < ARRAY_SIZE(nf_nat_ipv4_ops); i++) + nf_nat_ipv4_unregister_fn(net, &ops[i]); + +- kfree(ops); ++ kfree_rcu(ops, rcu); + } + + static int iptable_nat_table_init(struct net *net) +diff --git a/net/ipv6/netfilter/ip6table_nat.c b/net/ipv6/netfilter/ip6table_nat.c +index e119d4f..5be7232 100644 +--- a/net/ipv6/netfilter/ip6table_nat.c ++++ b/net/ipv6/netfilter/ip6table_nat.c +@@ -81,7 +81,7 @@ static int ip6t_nat_register_lookups(struct net *net) + while (i) + nf_nat_ipv6_unregister_fn(net, &ops[--i]); + +- kfree(ops); ++ kfree_rcu(ops, rcu); + return ret; + } + } +@@ -102,7 +102,7 @@ static void ip6t_nat_unregister_lookups(struct net *net) + for (i = 0; i < ARRAY_SIZE(nf_nat_ipv6_ops); i++) + nf_nat_ipv6_unregister_fn(net, &ops[i]); + +- kfree(ops); ++ kfree_rcu(ops, rcu); + } + + static int ip6table_nat_table_init(struct net *net) +diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c +index 746acd1..d380e1a 100644 +--- a/net/netfilter/nf_nat_core.c ++++ b/net/netfilter/nf_nat_core.c +@@ -1236,9 +1236,11 @@ int nf_nat_register_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops, + ret = nf_register_net_hooks(net, nat_ops, ops_count); + if (ret < 0) { + mutex_unlock(&nf_nat_proto_mutex); +- for (i = 0; i < ops_count; i++) +- kfree(nat_ops[i].priv); +- kfree(nat_ops); ++ for (i = 0; i < ops_count; i++) { ++ priv = nat_ops[i].priv; ++ kfree_rcu(priv, rcu_head); ++ } ++ kfree_rcu(nat_ops, rcu); + return ret; + } + +@@ -1302,7 +1304,7 @@ void nf_nat_unregister_fn(struct net *net, u8 pf, const struct nf_hook_ops *ops, + } + + nat_proto_net->nat_hook_ops = NULL; +- kfree(nat_ops); ++ kfree_rcu(nat_ops, rcu); + } + unlock: + mutex_unlock(&nf_nat_proto_mutex); diff --git a/1134-netfilter-nfnetlink-osf-fix-out-of-bounds-read-on-option-matching.patch b/1134-netfilter-nfnetlink-osf-fix-out-of-bounds-read-on-option-matching.patch new file mode 100644 index 000000000..4da577d23 --- /dev/null +++ b/1134-netfilter-nfnetlink-osf-fix-out-of-bounds-read-on-option-matching.patch @@ -0,0 +1,101 @@ +From 08043ad607850a39c83dec4e06a59532552a0c96 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:18 +0200 +Subject: [PATCH] netfilter: nfnetlink_osf: fix out-of-bounds read on option + matching + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f5ca450087c3 + +commit f5ca450087c3baf3651055e7a6de92600f827af3 +Author: Fernando Fernandez Mancera +Date: Fri Apr 17 18:20:56 2026 +0200 + + netfilter: nfnetlink_osf: fix out-of-bounds read on option matching + + In nf_osf_match(), the nf_osf_hdr_ctx structure is initialized once + and passed by reference to nf_osf_match_one() for each fingerprint + checked. During TCP option parsing, nf_osf_match_one() advances the + shared ctx->optp pointer. + + If a fingerprint perfectly matches, the function returns early without + restoring ctx->optp to its initial state. If the user has configured + NF_OSF_LOGLEVEL_ALL, the loop continues to the next fingerprint. + However, because ctx->optp was not restored, the next call to + nf_osf_match_one() starts parsing from the end of the options buffer. + This causes subsequent matches to read garbage data and fail + immediately, making it impossible to log more than one match or logging + incorrect matches. + + Instead of using a shared ctx->optp pointer, pass the context as a + constant pointer and use a local pointer (optp) for TCP option + traversal. This makes nf_osf_match_one() strictly stateless from the + caller's perspective, ensuring every fingerprint check starts at the + correct option offset. + + Fixes: 1a6a0951fc00 ("netfilter: nfnetlink_osf: add missing fmatch check") + Suggested-by: Florian Westphal + Signed-off-by: Fernando Fernandez Mancera + Reviewed-by: Pablo Neira Ayuso + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c +index 2305c7d..832a973 100644 +--- a/net/netfilter/nfnetlink_osf.c ++++ b/net/netfilter/nfnetlink_osf.c +@@ -64,9 +64,9 @@ struct nf_osf_hdr_ctx { + static bool nf_osf_match_one(const struct sk_buff *skb, + const struct nf_osf_user_finger *f, + int ttl_check, +- struct nf_osf_hdr_ctx *ctx) ++ const struct nf_osf_hdr_ctx *ctx) + { +- const __u8 *optpinit = ctx->optp; ++ const __u8 *optp = ctx->optp; + unsigned int check_WSS = 0; + int fmatch = FMATCH_WRONG; + int foptsize, optnum; +@@ -95,17 +95,17 @@ static bool nf_osf_match_one(const struct sk_buff *skb, + check_WSS = f->wss.wc; + + for (optnum = 0; optnum < f->opt_num; ++optnum) { +- if (f->opt[optnum].kind == *ctx->optp) { ++ if (f->opt[optnum].kind == *optp) { + __u32 len = f->opt[optnum].length; +- const __u8 *optend = ctx->optp + len; ++ const __u8 *optend = optp + len; + + fmatch = FMATCH_OK; + +- switch (*ctx->optp) { ++ switch (*optp) { + case OSFOPT_MSS: +- mss = ctx->optp[3]; ++ mss = optp[3]; + mss <<= 8; +- mss |= ctx->optp[2]; ++ mss |= optp[2]; + + mss = ntohs((__force __be16)mss); + break; +@@ -113,7 +113,7 @@ static bool nf_osf_match_one(const struct sk_buff *skb, + break; + } + +- ctx->optp = optend; ++ optp = optend; + } else + fmatch = FMATCH_OPT_WRONG; + +@@ -156,9 +156,6 @@ static bool nf_osf_match_one(const struct sk_buff *skb, + } + } + +- if (fmatch != FMATCH_OK) +- ctx->optp = optpinit; +- + return fmatch == FMATCH_OK; + } + diff --git a/1135-netfilter-nfnetlink-osf-fix-potential-null-dereference-in-ttl-check.patch b/1135-netfilter-nfnetlink-osf-fix-potential-null-dereference-in-ttl-check.patch new file mode 100644 index 000000000..2f36ffaba --- /dev/null +++ b/1135-netfilter-nfnetlink-osf-fix-potential-null-dereference-in-ttl-check.patch @@ -0,0 +1,75 @@ +From 7fce69f7fa9f3555eec125a06c235b5664b059c8 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:18 +0200 +Subject: [PATCH] netfilter: nfnetlink_osf: fix potential NULL dereference in + ttl check + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 711987ba281f + +commit 711987ba281fd806322a7cd244e98e2a81903114 +Author: Fernando Fernandez Mancera +Date: Fri Apr 17 18:20:57 2026 +0200 + + netfilter: nfnetlink_osf: fix potential NULL dereference in ttl check + + The nf_osf_ttl() function accessed skb->dev to perform a local interface + address lookup without verifying that the device pointer was valid. + + Additionally, the implementation utilized an in_dev_for_each_ifa_rcu + loop to match the packet source address against local interface + addresses. It assumed that packets from the same subnet should not see a + decrement on the initial TTL. A packet might appear it is from the same + subnet but it actually isn't especially in modern environments with + containers and virtual switching. + + Remove the device dereference and interface loop. Replace the logic with + a switch statement that evaluates the TTL according to the ttl_check. + + Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") + Reported-by: Kito Xu (veritas501) + Closes: https://lore.kernel.org/netfilter-devel/20260414074556.2512750-1-hxzene@gmail.com/ + Signed-off-by: Fernando Fernandez Mancera + Reviewed-by: Pablo Neira Ayuso + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c +index 832a973..c89efb9 100644 +--- a/net/netfilter/nfnetlink_osf.c ++++ b/net/netfilter/nfnetlink_osf.c +@@ -31,26 +31,18 @@ EXPORT_SYMBOL_GPL(nf_osf_fingers); + static inline int nf_osf_ttl(const struct sk_buff *skb, + int ttl_check, unsigned char f_ttl) + { +- struct in_device *in_dev = __in_dev_get_rcu(skb->dev); + const struct iphdr *ip = ip_hdr(skb); +- const struct in_ifaddr *ifa; +- int ret = 0; + +- if (ttl_check == NF_OSF_TTL_TRUE) ++ switch (ttl_check) { ++ case NF_OSF_TTL_TRUE: + return ip->ttl == f_ttl; +- if (ttl_check == NF_OSF_TTL_NOCHECK) +- return 1; +- else if (ip->ttl <= f_ttl) ++ break; ++ case NF_OSF_TTL_NOCHECK: + return 1; +- +- in_dev_for_each_ifa_rcu(ifa, in_dev) { +- if (inet_ifa_match(ip->saddr, ifa)) { +- ret = (ip->ttl == f_ttl); +- break; +- } ++ case NF_OSF_TTL_LESS: ++ default: ++ return ip->ttl <= f_ttl; + } +- +- return ret; + } + + struct nf_osf_hdr_ctx { diff --git a/1136-netfilter-nf-tables-use-list-del-rcu-for-netlink-hooks.patch b/1136-netfilter-nf-tables-use-list-del-rcu-for-netlink-hooks.patch new file mode 100644 index 000000000..827da0c09 --- /dev/null +++ b/1136-netfilter-nf-tables-use-list-del-rcu-for-netlink-hooks.patch @@ -0,0 +1,137 @@ +From bedd721414832ff460290adf488d09deff50db05 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:19 +0200 +Subject: [PATCH] netfilter: nf_tables: use list_del_rcu for netlink hooks + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f3224ee463f8 + +commit f3224ee463f8f6f6ced7dcdf6081add4f8128527 +Author: Florian Westphal +Date: Thu Apr 16 15:14:51 2026 +0200 + + netfilter: nf_tables: use list_del_rcu for netlink hooks + + nft_netdev_unregister_hooks and __nft_unregister_flowtable_net_hooks need + to use list_del_rcu(), this list can be walked by concurrent dumpers. + + Add a new helper and use it consistently. + + Fixes: f9a43007d3f7 ("netfilter: nf_tables: double hook unregistration in netns path") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 7cc4163..f67b9b0 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -373,6 +373,12 @@ static void nft_netdev_hook_free_rcu(struct nft_hook *hook) + call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu); + } + ++static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook) ++{ ++ list_del_rcu(&hook->list); ++ nft_netdev_hook_free_rcu(hook); ++} ++ + static void nft_netdev_unregister_hooks(struct net *net, + struct list_head *hook_list, + bool release_netdev) +@@ -383,10 +389,8 @@ static void nft_netdev_unregister_hooks(struct net *net, + list_for_each_entry_safe(hook, next, hook_list, list) { + list_for_each_entry(ops, &hook->ops_list, list) + nf_unregister_net_hook(net, ops); +- if (release_netdev) { +- list_del(&hook->list); +- nft_netdev_hook_free_rcu(hook); +- } ++ if (release_netdev) ++ nft_netdev_hook_unlink_free_rcu(hook); + } + } + +@@ -2314,10 +2318,8 @@ void nf_tables_chain_destroy(struct nft_chain *chain) + + if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) { + list_for_each_entry_safe(hook, next, +- &basechain->hook_list, list) { +- list_del_rcu(&hook->list); +- nft_netdev_hook_free_rcu(hook); +- } ++ &basechain->hook_list, list) ++ nft_netdev_hook_unlink_free_rcu(hook); + } + module_put(basechain->type->owner); + if (rcu_access_pointer(basechain->stats)) { +@@ -3017,6 +3019,7 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy, + list_for_each_entry(ops, &h->ops_list, list) + nf_unregister_net_hook(ctx->net, ops); + } ++ /* hook.list is on stack, no need for list_del_rcu() */ + list_del(&h->list); + nft_netdev_hook_free_rcu(h); + } +@@ -9049,10 +9052,8 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net, + list_for_each_entry_safe(hook, next, hook_list, list) { + list_for_each_entry(ops, &hook->ops_list, list) + nft_unregister_flowtable_ops(net, flowtable, ops); +- if (release_netdev) { +- list_del(&hook->list); +- nft_netdev_hook_free_rcu(hook); +- } ++ if (release_netdev) ++ nft_netdev_hook_unlink_free_rcu(hook); + } + } + +@@ -9123,8 +9124,7 @@ static int nft_register_flowtable_net_hooks(struct net *net, + + nft_unregister_flowtable_ops(net, flowtable, ops); + } +- list_del_rcu(&hook->list); +- nft_netdev_hook_free_rcu(hook); ++ nft_netdev_hook_unlink_free_rcu(hook); + } + + return err; +@@ -9134,10 +9134,8 @@ static void nft_hooks_destroy(struct list_head *hook_list) + { + struct nft_hook *hook, *next; + +- list_for_each_entry_safe(hook, next, hook_list, list) { +- list_del_rcu(&hook->list); +- nft_netdev_hook_free_rcu(hook); +- } ++ list_for_each_entry_safe(hook, next, hook_list, list) ++ nft_netdev_hook_unlink_free_rcu(hook); + } + + static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, +@@ -9225,8 +9223,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, + nft_unregister_flowtable_ops(ctx->net, + flowtable, ops); + } +- list_del_rcu(&hook->list); +- nft_netdev_hook_free_rcu(hook); ++ nft_netdev_hook_unlink_free_rcu(hook); + } + + return err; +@@ -9730,13 +9727,8 @@ static void nf_tables_flowtable_notify(struct nft_ctx *ctx, + + static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable) + { +- struct nft_hook *hook, *next; +- + flowtable->data.type->free(&flowtable->data); +- list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) { +- list_del_rcu(&hook->list); +- nft_netdev_hook_free_rcu(hook); +- } ++ nft_hooks_destroy(&flowtable->hook_list); + kfree(flowtable->name); + module_put(flowtable->data.type->owner); + kfree(flowtable); diff --git a/1137-rculist-add-list-splice-rcu-for-private-lists.patch b/1137-rculist-add-list-splice-rcu-for-private-lists.patch new file mode 100644 index 000000000..2071b3222 --- /dev/null +++ b/1137-rculist-add-list-splice-rcu-for-private-lists.patch @@ -0,0 +1,78 @@ +From 4f79f8ec95a853c69e776a91f3258f8309713123 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:20 +0200 +Subject: [PATCH] rculist: add list_splice_rcu() for private lists + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit f902877b6355 + +commit f902877b635551513729bdf9a8d1422c4aab7741 +Author: Pablo Neira Ayuso +Date: Wed Apr 15 17:56:02 2026 +0200 + + rculist: add list_splice_rcu() for private lists + + This patch adds a helper function, list_splice_rcu(), to safely splice + a private (non-RCU-protected) list into an RCU-protected list. + + The function ensures that only the pointer visible to RCU readers + (prev->next) is updated using rcu_assign_pointer(), while the rest of + the list manipulations are performed with regular assignments, as the + source list is private and not visible to concurrent RCU readers. + + This is useful for moving elements from a private list into a global + RCU-protected list, ensuring safe publication for RCU readers. + Subsystems with some sort of batching mechanism from userspace can + benefit from this new function. + + The function __list_splice_rcu() has been added for clarity and to + follow the same pattern as in the existing list_splice*() interfaces, + where there is a check to ensure that the list to splice is not + empty. Note that __list_splice_rcu() has no documentation for this + reason. + + Reviewed-by: Paul E. McKenney + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/linux/rculist.h b/include/linux/rculist.h +index 1b11926..591272f 100644 +--- a/include/linux/rculist.h ++++ b/include/linux/rculist.h +@@ -251,6 +251,35 @@ static inline void list_replace_rcu(struct list_head *old, + old->prev = LIST_POISON2; + } + ++static inline void __list_splice_rcu(struct list_head *list, ++ struct list_head *prev, ++ struct list_head *next) ++{ ++ struct list_head *first = list->next; ++ struct list_head *last = list->prev; ++ ++ last->next = next; ++ first->prev = prev; ++ next->prev = last; ++ rcu_assign_pointer(list_next_rcu(prev), first); ++} ++ ++/** ++ * list_splice_rcu - splice a non-RCU list into an RCU-protected list, ++ * designed for stacks. ++ * @list: the non RCU-protected list to splice ++ * @head: the place in the existing RCU-protected list to splice ++ * ++ * The list pointed to by @head can be RCU-read traversed concurrently with ++ * this function. ++ */ ++static inline void list_splice_rcu(struct list_head *list, ++ struct list_head *head) ++{ ++ if (!list_empty(list)) ++ __list_splice_rcu(list, head, head->next); ++} ++ + /** + * __list_splice_init_rcu - join an RCU-protected list into an existing list. + * @list: the RCU-protected list to splice diff --git a/1138-netfilter-nf-tables-join-hook-list-via-splice-list-rcu-in-commit-phase.patch b/1138-netfilter-nf-tables-join-hook-list-via-splice-list-rcu-in-commit-phase.patch new file mode 100644 index 000000000..efea172cf --- /dev/null +++ b/1138-netfilter-nf-tables-join-hook-list-via-splice-list-rcu-in-commit-phase.patch @@ -0,0 +1,51 @@ +From f5e3847dafb8aae3a40977b9a266e8c31cf0e018 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:21 +0200 +Subject: [PATCH] netfilter: nf_tables: join hook list via splice_list_rcu() in + commit phase + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit a6134e62dba2 + +commit a6134e62dba2ea4f760b29d5226907f447c92400 +Author: Pablo Neira Ayuso +Date: Wed Apr 15 17:56:14 2026 +0200 + + netfilter: nf_tables: join hook list via splice_list_rcu() in commit phase + + Publish new hooks in the list into the basechain/flowtable using + splice_list_rcu() to ensure netlink dump list traversal via rcu is safe + while concurrent ruleset update is going on. + + Fixes: 78d9f48f7f44 ("netfilter: nf_tables: add devices to existing flowtable") + Fixes: b9703ed44ffb ("netfilter: nf_tables: support for adding new devices to an existing netdev chain") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index f67b9b0..32155f1 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -11048,8 +11048,8 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + nft_chain_commit_update(nft_trans_container_chain(trans)); + nf_tables_chain_notify(&ctx, NFT_MSG_NEWCHAIN, + &nft_trans_chain_hooks(trans)); +- list_splice(&nft_trans_chain_hooks(trans), +- &nft_trans_basechain(trans)->hook_list); ++ list_splice_rcu(&nft_trans_chain_hooks(trans), ++ &nft_trans_basechain(trans)->hook_list); + /* trans destroyed after rcu grace period */ + } else { + nft_chain_commit_drop_policy(nft_trans_container_chain(trans)); +@@ -11178,8 +11178,8 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + nft_trans_flowtable(trans), + &nft_trans_flowtable_hooks(trans), + NFT_MSG_NEWFLOWTABLE); +- list_splice(&nft_trans_flowtable_hooks(trans), +- &nft_trans_flowtable(trans)->hook_list); ++ list_splice_rcu(&nft_trans_flowtable_hooks(trans), ++ &nft_trans_flowtable(trans)->hook_list); + } else { + nft_clear(net, nft_trans_flowtable(trans)); + nf_tables_flowtable_notify(&ctx, diff --git a/1139-netfilter-nf-tables-add-hook-transactions-for-device-deletions.patch b/1139-netfilter-nf-tables-add-hook-transactions-for-device-deletions.patch new file mode 100644 index 000000000..257a171e2 --- /dev/null +++ b/1139-netfilter-nf-tables-add-hook-transactions-for-device-deletions.patch @@ -0,0 +1,82 @@ +From 28986d167301cbf492736a86d0bd9138186d4bc4 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:21 +0200 +Subject: [PATCH] netfilter: nf_tables: add hook transactions for device + deletions + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 10f79dbd7719 + +commit 10f79dbd7719d1da9f5884d13060322d8729f091 +Author: Pablo Neira Ayuso +Date: Wed Apr 15 22:58:23 2026 +0200 + + netfilter: nf_tables: add hook transactions for device deletions + + Restore the flag that indicates that the hook is going away, ie. + NFT_HOOK_REMOVE, but add a new transaction object to track deletion + of hooks without altering the basechain/flowtable hook_list during + the preparation phase. + + The existing approach that moves the hook from the basechain/flowtable + hook_list to transaction hook_list breaks netlink dump path readers + of this RCU-protected list. + + It should be possible use an array for nft_trans_hook to store the + deleted hooks to compact the representation but I am not expecting + many hook object, specially now that wildcard support for devices + is in place. + + Note that the nft_trans_chain_hooks() list contains a list of struct + nft_trans_hook objects for DELCHAIN and DELFLOWTABLE commands, while + this list stores struct nft_hook objects for NEWCHAIN and NEWFLOWTABLE. + Note that new commands can be updated to use nft_trans_hook for + consistency. + + This patch also adapts the event notification path to deal with the list + of hook transactions. + + Fixes: 7d937b107108 ("netfilter: nf_tables: support for deleting devices in an existing netdev chain") + Fixes: b6d9014a3335 ("netfilter: nf_tables: delete flowtable hooks via transaction list") + Reported-by: Xiang Mei + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index 5fe20cc..a704616 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1213,12 +1213,15 @@ struct nft_stats { + struct u64_stats_sync syncp; + }; + ++#define NFT_HOOK_REMOVE (1 << 0) ++ + struct nft_hook { + struct list_head list; + struct list_head ops_list; + struct rcu_head rcu; + char ifname[IFNAMSIZ]; + u8 ifnamelen; ++ u8 flags; + }; + + struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook, +@@ -1673,6 +1676,16 @@ struct nft_trans { + u8 put_net:1; + }; + ++/** ++ * struct nft_trans_hook - nf_tables hook update in transaction ++ * @list: used internally ++ * @hook: struct nft_hook with the device hook ++ */ ++struct nft_trans_hook { ++ struct list_head list; ++ struct nft_hook *hook; ++}; ++ + /** + * struct nft_trans_binding - nf_tables object with binding support in transaction + * @nft_trans: base structure, MUST be first member diff --git a/1140-netfilter-xt-policy-fix-strict-mode-inbound-policy-matching.patch b/1140-netfilter-xt-policy-fix-strict-mode-inbound-policy-matching.patch new file mode 100644 index 000000000..10eeaf90b --- /dev/null +++ b/1140-netfilter-xt-policy-fix-strict-mode-inbound-policy-matching.patch @@ -0,0 +1,47 @@ +From 8356d8b52032a2e39e5e3ac6f8d4c8c69247064f Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:52 +0200 +Subject: [PATCH] netfilter: xt_policy: fix strict mode inbound policy matching + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 4b2b4d7d4e20 + +commit 4b2b4d7d4e203c92db8966b163edfacb1f0e1e29 +Author: Jiexun Wang +Date: Fri Apr 17 20:25:06 2026 +0800 + + netfilter: xt_policy: fix strict mode inbound policy matching + + match_policy_in() walks sec_path entries from the last transform to the + first one, but strict policy matching needs to consume info->pol[] in + the same forward order as the rule layout. + + Derive the strict-match policy position from the number of transforms + already consumed so that multi-element inbound rules are matched + consistently. + + Fixes: c4b885139203 ("[NETFILTER]: x_tables: replace IPv4/IPv6 policy match by address family independant version") + Reported-by: Yuan Tan + Reported-by: Yifan Wu + Reported-by: Juefei Pu + Reported-by: Xin Liu + Signed-off-by: Jiexun Wang + Signed-off-by: Ren Wei + Acked-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/xt_policy.c b/net/netfilter/xt_policy.c +index cb6e827..b5fa655 100644 +--- a/net/netfilter/xt_policy.c ++++ b/net/netfilter/xt_policy.c +@@ -63,7 +63,7 @@ match_policy_in(const struct sk_buff *skb, const struct xt_policy_info *info, + return 0; + + for (i = sp->len - 1; i >= 0; i--) { +- pos = strict ? i - sp->len + 1 : 0; ++ pos = strict ? sp->len - i - 1 : 0; + if (pos >= info->len) + return 0; + e = &info->pol[pos]; diff --git a/1141-netfilter-nf-conntrack-sip-don-t-use-simple-strtoul.patch b/1141-netfilter-nf-conntrack-sip-don-t-use-simple-strtoul.patch new file mode 100644 index 000000000..373b4d184 --- /dev/null +++ b/1141-netfilter-nf-conntrack-sip-don-t-use-simple-strtoul.patch @@ -0,0 +1,351 @@ +From cfeec2dad64d5c91326707d6a443086a260f5c22 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:53 +0200 +Subject: [PATCH] netfilter: nf_conntrack_sip: don't use simple_strtoul +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 8cf6809cddcb + +commit 8cf6809cddcbe301aedfc6b51bcd4944d45795f6 +Author: Florian Westphal +Date: Thu Apr 23 02:19:11 2026 +0200 + + netfilter: nf_conntrack_sip: don't use simple_strtoul + + Replace unsafe port parsing in epaddr_len(), ct_sip_parse_header_uri(), + and ct_sip_parse_request() with a new sip_parse_port() helper that + validates each digit against the buffer limit, eliminating the use of + simple_strtoul() which assumes NUL-terminated strings. + + The previous code dereferenced pointers without bounds checks after + sip_parse_addr() and relied on simple_strtoul() on non-NUL-terminated + skb data. A port that reaches the buffer limit without a trailing + character is also rejected as malformed. + + Also get rid of all simple_strtoul() usage in conntrack, prefer a + stricter version instead. There are intentional changes: + + - Bail out if number is > UINT_MAX and indicate a failure, same for + too long sequences. + While we do accept 05535 as port 5535, we will not accept e.g. + 'sip:10.0.0.1:005060'. While its syntactically valid under RFC 3261, + we should restrict this to not waste cycles when presented with + malformed packets with 64k '0' characters. + + - Force base 10 in ct_sip_parse_numerical_param(). This is used to fetch + 'expire=' and 'rports='; both are expected to use base-10. + + - In nf_nat_sip.c, only accept the parsed value if its within the 1k-64k + range. + + - epaddr_len now returns 0 if the port is invalid, as it already does + for invalid ip addresses. This is intentional. nf_conntrack_sip + performs lots of guesswork to find the right parts of the message + to parse. Being stricter could break existing setups. + Connection tracking helpers are designed to allow traffic to + pass, not to block it. + + Based on an earlier patch from Jenny Guanni Qu . + + Fixes: 05e3ced297fe ("[NETFILTER]: nf_conntrack_sip: introduce SIP-URI parsing helper") + Reported-by: Klaudia Kloc + Reported-by: Dawid Moczadło + Reported-by: Jenny Guanni Qu . + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c +index 20e57cf..b31f31e 100644 +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -181,6 +181,57 @@ static int sip_parse_addr(const struct nf_conn *ct, const char *cp, + return 1; + } + ++/* Parse optional port number after IP address. ++ * Returns false on malformed input, true otherwise. ++ * If port is non-NULL, stores parsed port in network byte order. ++ * If no port is present, sets *port to default SIP port. ++ */ ++static bool sip_parse_port(const char *dptr, const char **endp, ++ const char *limit, __be16 *port) ++{ ++ unsigned int p = 0; ++ int len = 0; ++ ++ if (dptr >= limit) ++ return false; ++ ++ if (*dptr != ':') { ++ if (port) ++ *port = htons(SIP_PORT); ++ if (endp) ++ *endp = dptr; ++ return true; ++ } ++ ++ dptr++; /* skip ':' */ ++ ++ while (dptr < limit && isdigit(*dptr)) { ++ p = p * 10 + (*dptr - '0'); ++ dptr++; ++ len++; ++ if (len > 5) /* max "65535" */ ++ return false; ++ } ++ ++ if (len == 0) ++ return false; ++ ++ /* reached limit while parsing port */ ++ if (dptr >= limit) ++ return false; ++ ++ if (p < 1024 || p > 65535) ++ return false; ++ ++ if (port) ++ *port = htons(p); ++ ++ if (endp) ++ *endp = dptr; ++ ++ return true; ++} ++ + /* skip ip address. returns its length. */ + static int epaddr_len(const struct nf_conn *ct, const char *dptr, + const char *limit, int *shift) +@@ -193,11 +244,8 @@ static int epaddr_len(const struct nf_conn *ct, const char *dptr, + return 0; + } + +- /* Port number */ +- if (*dptr == ':') { +- dptr++; +- dptr += digits_len(ct, dptr, limit, shift); +- } ++ if (!sip_parse_port(dptr, &dptr, limit, NULL)) ++ return 0; + return dptr - aux; + } + +@@ -228,6 +276,51 @@ static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr, + return epaddr_len(ct, dptr, limit, shift); + } + ++/* simple_strtoul stops after first non-number character. ++ * But as we're not dealing with c-strings, we can't rely on ++ * hitting \r,\n,\0 etc. before moving past end of buffer. ++ * ++ * This is a variant of simple_strtoul, but doesn't require ++ * a c-string. ++ * ++ * If value exceeds UINT_MAX, 0 is returned. ++ */ ++static unsigned int sip_strtouint(const char *cp, unsigned int len, char **endp) ++{ ++ const unsigned int max = sizeof("4294967295"); ++ unsigned int olen = len; ++ const char *s = cp; ++ u64 result = 0; ++ ++ if (len > max) ++ len = max; ++ ++ while (olen > 0 && isdigit(*s)) { ++ unsigned int value; ++ ++ if (len == 0) ++ goto err; ++ ++ value = *s - '0'; ++ result = result * 10 + value; ++ ++ if (result > UINT_MAX) ++ goto err; ++ s++; ++ len--; ++ olen--; ++ } ++ ++ if (endp) ++ *endp = (char *)s; ++ ++ return result; ++err: ++ if (endp) ++ *endp = (char *)cp; ++ return 0; ++} ++ + /* Parse a SIP request line of the form: + * + * Request-Line = Method SP Request-URI SP SIP-Version CRLF +@@ -241,7 +334,6 @@ int ct_sip_parse_request(const struct nf_conn *ct, + { + const char *start = dptr, *limit = dptr + datalen, *end; + unsigned int mlen; +- unsigned int p; + int shift = 0; + + /* Skip method and following whitespace */ +@@ -267,14 +359,8 @@ int ct_sip_parse_request(const struct nf_conn *ct, + + if (!sip_parse_addr(ct, dptr, &end, addr, limit, true)) + return -1; +- if (end < limit && *end == ':') { +- end++; +- p = simple_strtoul(end, (char **)&end, 10); +- if (p < 1024 || p > 65535) +- return -1; +- *port = htons(p); +- } else +- *port = htons(SIP_PORT); ++ if (!sip_parse_port(end, &end, limit, port)) ++ return -1; + + if (end == dptr) + return 0; +@@ -509,7 +595,6 @@ int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr, + union nf_inet_addr *addr, __be16 *port) + { + const char *c, *limit = dptr + datalen; +- unsigned int p; + int ret; + + ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen, +@@ -520,14 +605,8 @@ int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr, + + if (!sip_parse_addr(ct, dptr + *matchoff, &c, addr, limit, true)) + return -1; +- if (*c == ':') { +- c++; +- p = simple_strtoul(c, (char **)&c, 10); +- if (p < 1024 || p > 65535) +- return -1; +- *port = htons(p); +- } else +- *port = htons(SIP_PORT); ++ if (!sip_parse_port(c, &c, limit, port)) ++ return -1; + + if (dataoff) + *dataoff = c - dptr; +@@ -609,7 +688,7 @@ int ct_sip_parse_numerical_param(const struct nf_conn *ct, const char *dptr, + return 0; + + start += strlen(name); +- *val = simple_strtoul(start, &end, 0); ++ *val = sip_strtouint(start, limit - start, (char **)&end); + if (start == end) + return -1; + if (matchoff && matchlen) { +@@ -1061,6 +1140,8 @@ static int process_sdp(struct sk_buff *skb, unsigned int protoff, + + mediaoff = sdpoff; + for (i = 0; i < ARRAY_SIZE(sdp_media_types); ) { ++ char *end; ++ + if (ct_sip_get_sdp_header(ct, *dptr, mediaoff, *datalen, + SDP_HDR_MEDIA, SDP_HDR_UNSPEC, + &mediaoff, &medialen) <= 0) +@@ -1076,8 +1157,8 @@ static int process_sdp(struct sk_buff *skb, unsigned int protoff, + mediaoff += t->len; + medialen -= t->len; + +- port = simple_strtoul(*dptr + mediaoff, NULL, 10); +- if (port == 0) ++ port = sip_strtouint(*dptr + mediaoff, *datalen - mediaoff, (char **)&end); ++ if (port == 0 || *dptr + mediaoff == end) + continue; + if (port < 1024 || port > 65535) { + nf_ct_helper_log(skb, ct, "wrong port %u", port); +@@ -1249,7 +1330,7 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff, + */ + if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES, + &matchoff, &matchlen) > 0) +- expires = simple_strtoul(*dptr + matchoff, NULL, 10); ++ expires = sip_strtouint(*dptr + matchoff, *datalen - matchoff, NULL); + + ret = ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, + SIP_HDR_CONTACT, NULL, +@@ -1353,7 +1434,7 @@ static int process_register_response(struct sk_buff *skb, unsigned int protoff, + + if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES, + &matchoff, &matchlen) > 0) +- expires = simple_strtoul(*dptr + matchoff, NULL, 10); ++ expires = sip_strtouint(*dptr + matchoff, *datalen - matchoff, NULL); + + while (1) { + unsigned int c_expires = expires; +@@ -1413,10 +1494,12 @@ static int process_sip_response(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct = nf_ct_get(skb, &ctinfo); + unsigned int matchoff, matchlen, matchend; + unsigned int code, cseq, i; ++ char *end; + + if (*datalen < strlen("SIP/2.0 200")) + return NF_ACCEPT; +- code = simple_strtoul(*dptr + strlen("SIP/2.0 "), NULL, 10); ++ code = sip_strtouint(*dptr + strlen("SIP/2.0 "), ++ *datalen - strlen("SIP/2.0 "), NULL); + if (!code) { + nf_ct_helper_log(skb, ct, "cannot get code"); + return NF_DROP; +@@ -1427,8 +1510,8 @@ static int process_sip_response(struct sk_buff *skb, unsigned int protoff, + nf_ct_helper_log(skb, ct, "cannot parse cseq"); + return NF_DROP; + } +- cseq = simple_strtoul(*dptr + matchoff, NULL, 10); +- if (!cseq && *(*dptr + matchoff) != '0') { ++ cseq = sip_strtouint(*dptr + matchoff, *datalen - matchoff, (char **)&end); ++ if (*dptr + matchoff == end) { + nf_ct_helper_log(skb, ct, "cannot get cseq"); + return NF_DROP; + } +@@ -1477,6 +1560,7 @@ static int process_sip_request(struct sk_buff *skb, unsigned int protoff, + + for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) { + const struct sip_handler *handler; ++ char *end; + + handler = &sip_handlers[i]; + if (handler->request == NULL) +@@ -1493,8 +1577,8 @@ static int process_sip_request(struct sk_buff *skb, unsigned int protoff, + nf_ct_helper_log(skb, ct, "cannot parse cseq"); + return NF_DROP; + } +- cseq = simple_strtoul(*dptr + matchoff, NULL, 10); +- if (!cseq && *(*dptr + matchoff) != '0') { ++ cseq = sip_strtouint(*dptr + matchoff, *datalen - matchoff, (char **)&end); ++ if (*dptr + matchoff == end) { + nf_ct_helper_log(skb, ct, "cannot get cseq"); + return NF_DROP; + } +@@ -1570,7 +1654,7 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, + &matchoff, &matchlen) <= 0) + break; + +- clen = simple_strtoul(dptr + matchoff, (char **)&end, 10); ++ clen = sip_strtouint(dptr + matchoff, datalen - matchoff, (char **)&end); + if (dptr + matchoff == end) + break; + +diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c +index c845b6d..9fbfc6b 100644 +--- a/net/netfilter/nf_nat_sip.c ++++ b/net/netfilter/nf_nat_sip.c +@@ -246,6 +246,7 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, + if (ct_sip_parse_numerical_param(ct, *dptr, matchend, *datalen, + "rport=", &poff, &plen, + &n) > 0 && ++ n >= 1024 && n <= 65535 && + htons(n) == ct->tuplehash[dir].tuple.dst.u.udp.port && + htons(n) != ct->tuplehash[!dir].tuple.src.u.udp.port) { + __be16 p = ct->tuplehash[!dir].tuple.src.u.udp.port; diff --git a/1142-netfilter-replace-skb-try-make-writable-by-skb-ensure-writable.patch b/1142-netfilter-replace-skb-try-make-writable-by-skb-ensure-writable.patch new file mode 100644 index 000000000..9b914f774 --- /dev/null +++ b/1142-netfilter-replace-skb-try-make-writable-by-skb-ensure-writable.patch @@ -0,0 +1,81 @@ +From a3f61eef6943577933d43f3546d57ae78f5b44f3 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:55 +0200 +Subject: [PATCH] netfilter: replace skb_try_make_writable() by + skb_ensure_writable() + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 1049970d7583 + +commit 1049970d7583194eedc30e45a3c898b2cb1c30ba +Author: Pablo Neira Ayuso +Date: Mon Apr 27 14:34:45 2026 +0200 + + netfilter: replace skb_try_make_writable() by skb_ensure_writable() + + skb_try_make_writable() only works on clones and uncloned packets might + have their network header in paged fragments. + + nft_fwd needs to work for the ingress and egress hooks, but the egress + hook where skb->data points to the mac header, use skb_network_offset() + to include the mac header. The flowtable is fine since it already uses + the transport offset. + + Fixes: d32de98ea70f ("netfilter: nft_fwd_netdev: allow to forward packets via neighbour layer") + Fixes: 7d2086871762 ("netfilter: nf_flow_table: move ipv4 offload hook code to nf_flow_table") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c +index 8cd4cf7..43266ab 100644 +--- a/net/netfilter/nf_flow_table_ip.c ++++ b/net/netfilter/nf_flow_table_ip.c +@@ -394,7 +394,7 @@ static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx, + return 0; + } + +- if (skb_try_make_writable(skb, thoff + ctx->hdrsize)) ++ if (skb_ensure_writable(skb, thoff + ctx->hdrsize)) + return -1; + + flow_offload_refresh(flow_table, flow, false); +@@ -673,7 +673,7 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx, + return 0; + } + +- if (skb_try_make_writable(skb, thoff + ctx->hdrsize)) ++ if (skb_ensure_writable(skb, thoff + ctx->hdrsize)) + return -1; + + flow_offload_refresh(flow_table, flow, false); +diff --git a/net/netfilter/nft_fwd_netdev.c b/net/netfilter/nft_fwd_netdev.c +index 152a9fb..c49da00 100644 +--- a/net/netfilter/nft_fwd_netdev.c ++++ b/net/netfilter/nft_fwd_netdev.c +@@ -100,6 +100,7 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + int oif = regs->data[priv->sreg_dev]; + unsigned int verdict = NF_STOLEN; + struct sk_buff *skb = pkt->skb; ++ int nhoff = skb_network_offset(skb); + struct net_device *dev; + int neigh_table; + +@@ -111,7 +112,7 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + verdict = NFT_BREAK; + goto out; + } +- if (skb_try_make_writable(skb, sizeof(*iph))) { ++ if (skb_ensure_writable(skb, nhoff + sizeof(*iph))) { + verdict = NF_DROP; + goto out; + } +@@ -127,7 +128,7 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + verdict = NFT_BREAK; + goto out; + } +- if (skb_try_make_writable(skb, sizeof(*ip6h))) { ++ if (skb_ensure_writable(skb, nhoff + sizeof(*ip6h))) { + verdict = NF_DROP; + goto out; + } diff --git a/1143-netfilter-nft-fwd-netdev-add-device-and-headroom-validate-with-neigh-forwarding.patch b/1143-netfilter-nft-fwd-netdev-add-device-and-headroom-validate-with-neigh-forwarding.patch new file mode 100644 index 000000000..db958bf9c --- /dev/null +++ b/1143-netfilter-nft-fwd-netdev-add-device-and-headroom-validate-with-neigh-forwarding.patch @@ -0,0 +1,63 @@ +From 2073f1cc95a6d0cfea7e966c7307730ba7dca41c Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:57 +0200 +Subject: [PATCH] netfilter: nft_fwd_netdev: add device and headroom validate + with neigh forwarding + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 0a0b35f0bf10 + +commit 0a0b35f0bf10b4c2be607465f5c9c12c8681305b +Author: Pablo Neira Ayuso +Date: Mon Apr 27 14:34:48 2026 +0200 + + netfilter: nft_fwd_netdev: add device and headroom validate with neigh forwarding + + The ttl field has been decremented already and evaluation of this rule + would proceed, just drop this packet instead if there is no destination + device to forwards this packet. This is exactly what nf_dup already does + in this case. + + Moreover, check for headroom and call skb_expand_head() like in the IP + output path to ensure there is sufficient headroom when forwarding this + via neigh_xmit(). + + Fixes: d32de98ea70f ("netfilter: nft_fwd_netdev: allow to forward packets via neighbour layer") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nft_fwd_netdev.c b/net/netfilter/nft_fwd_netdev.c +index c49da00..08246ef 100644 +--- a/net/netfilter/nft_fwd_netdev.c ++++ b/net/netfilter/nft_fwd_netdev.c +@@ -102,6 +102,7 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + struct sk_buff *skb = pkt->skb; + int nhoff = skb_network_offset(skb); + struct net_device *dev; ++ unsigned int hh_len; + int neigh_table; + + switch (priv->nfproto) { +@@ -143,8 +144,19 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + } + + dev = dev_get_by_index_rcu(nft_net(pkt), oif); +- if (dev == NULL) +- return; ++ if (dev == NULL) { ++ verdict = NF_DROP; ++ goto out; ++ } ++ ++ hh_len = LL_RESERVED_SPACE(dev); ++ if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) { ++ skb = skb_expand_head(skb, hh_len); ++ if (!skb) { ++ verdict = NF_STOLEN; ++ goto out; ++ } ++ } + + skb->dev = dev; + skb_clear_tstamp(skb); diff --git a/1144-netfilter-nft-fwd-netdev-use-recursion-counter-in-neigh-egress-path.patch b/1144-netfilter-nft-fwd-netdev-use-recursion-counter-in-neigh-egress-path.patch new file mode 100644 index 000000000..6ec1e533a --- /dev/null +++ b/1144-netfilter-nft-fwd-netdev-use-recursion-counter-in-neigh-egress-path.patch @@ -0,0 +1,124 @@ +From 5c6290d3ed9c20d216c2e6fe34d529a1417264e5 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:58 +0200 +Subject: [PATCH] netfilter: nft_fwd_netdev: use recursion counter in neigh + egress path + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 1d47b55b36d2 + +commit 1d47b55b36d2ec73fe6901212c8b28a593c3b27c +Author: Weiming Shi +Date: Mon Apr 27 14:34:50 2026 +0200 + + netfilter: nft_fwd_netdev: use recursion counter in neigh egress path + + nft_fwd_neigh can be used in egress chains (NF_NETDEV_EGRESS). When the + forwarding rule targets the same device or two devices forward to each + other, neigh_xmit() triggers dev_queue_xmit() which re-enters + nf_hook_egress(), causing infinite recursion and stack overflow. + + Move the nf_get_nf_dup_skb_recursion() accessor and NF_RECURSION_LIMIT + to the shared header nf_dup_netdev.h as a static inline, so that + nft_fwd_netdev can use the recursion counter directly without exported + function call overhead. Guard neigh_xmit() with the same recursion + limit already used in nf_do_netdev_egress(). + + [ Updated to cache the nf_get_nf_dup_skb_recursion pointer. --pablo ] + + Fixes: f87b9464d152 ("netfilter: nft_fwd_netdev: Support egress hook") + Reported-by: Xiang Mei + Signed-off-by: Weiming Shi + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_dup_netdev.h b/include/net/netfilter/nf_dup_netdev.h +index b175d27..609bcf4 100644 +--- a/include/net/netfilter/nf_dup_netdev.h ++++ b/include/net/netfilter/nf_dup_netdev.h +@@ -3,10 +3,23 @@ + #define _NF_DUP_NETDEV_H_ + + #include ++#include ++#include + + void nf_dup_netdev_egress(const struct nft_pktinfo *pkt, int oif); + void nf_fwd_netdev_egress(const struct nft_pktinfo *pkt, int oif); + ++#define NF_RECURSION_LIMIT 2 ++ ++static inline u8 *nf_get_nf_dup_skb_recursion(void) ++{ ++#ifndef CONFIG_PREEMPT_RT ++ return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion); ++#else ++ return ¤t->net_xmit.nf_dup_skb_recursion; ++#endif ++} ++ + struct nft_offload_ctx; + struct nft_flow_rule; + +diff --git a/net/netfilter/nf_dup_netdev.c b/net/netfilter/nf_dup_netdev.c +index a8e2425..516bcf4 100644 +--- a/net/netfilter/nf_dup_netdev.c ++++ b/net/netfilter/nf_dup_netdev.c +@@ -17,6 +17,22 @@ + + static DEFINE_PER_CPU(u8, nf_dup_skb_recursion); + ++#define NF_RECURSION_LIMIT 2 ++ ++#ifndef CONFIG_PREEMPT_RT ++static u8 *nf_get_nf_dup_skb_recursion(void) ++{ ++ return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion); ++} ++#else ++ ++static u8 *nf_get_nf_dup_skb_recursion(void) ++{ ++ return ¤t->net_xmit.nf_dup_skb_recursion; ++} ++ ++#endif ++ + static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev, + enum nf_dev_hooks hook) + { +diff --git a/net/netfilter/nft_fwd_netdev.c b/net/netfilter/nft_fwd_netdev.c +index 08246ef..a9743a1 100644 +--- a/net/netfilter/nft_fwd_netdev.c ++++ b/net/netfilter/nft_fwd_netdev.c +@@ -95,6 +95,7 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) + { ++ u8 *nf_dup_skb_recursion = nf_get_nf_dup_skb_recursion(); + struct nft_fwd_neigh *priv = nft_expr_priv(expr); + void *addr = ®s->data[priv->sreg_addr]; + int oif = regs->data[priv->sreg_dev]; +@@ -143,6 +144,11 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + goto out; + } + ++ if (*nf_dup_skb_recursion > NF_RECURSION_LIMIT) { ++ verdict = NF_DROP; ++ goto out; ++ } ++ + dev = dev_get_by_index_rcu(nft_net(pkt), oif); + if (dev == NULL) { + verdict = NF_DROP; +@@ -160,7 +166,9 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + + skb->dev = dev; + skb_clear_tstamp(skb); ++ (*nf_dup_skb_recursion)++; + neigh_xmit(neigh_table, dev, addr, skb); ++ (*nf_dup_skb_recursion)--; + out: + regs->verdict.code = verdict; + } diff --git a/1145-netfilter-xtables-restrict-several-matches-to-inet-family.patch b/1145-netfilter-xtables-restrict-several-matches-to-inet-family.patch new file mode 100644 index 000000000..ebf03d389 --- /dev/null +++ b/1145-netfilter-xtables-restrict-several-matches-to-inet-family.patch @@ -0,0 +1,205 @@ +From 2dd0f2f9dede63706b7b211f6ae7c0b0db170e7b Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:16 +0200 +Subject: [PATCH] netfilter: xtables: restrict several matches to inet family + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit b6fe26f86a16 + +commit b6fe26f86a1649f84e057f3f15605b08eda15497 +Author: Pablo Neira Ayuso +Date: Wed Apr 15 12:21:00 2026 +0200 + + netfilter: xtables: restrict several matches to inet family + + This is a partial revert of: + + commit ab4f21e6fb1c ("netfilter: xtables: use NFPROTO_UNSPEC in more extensions") + + to allow ipv4 and ipv6 only. + + - xt_mac + - xt_owner + - xt_physdev + + These extensions are not used by ebtables in userspace. + + Moreover, xt_realm is only for ipv4, since dst->tclassid is ipv4 + specific. + + Fixes: ab4f21e6fb1c ("netfilter: xtables: use NFPROTO_UNSPEC in more extensions") + Reported-by: "Kito Xu (veritas501)" + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/xt_mac.c b/net/netfilter/xt_mac.c +index 81649da..bd23547 100644 +--- a/net/netfilter/xt_mac.c ++++ b/net/netfilter/xt_mac.c +@@ -38,25 +38,37 @@ static bool mac_mt(const struct sk_buff *skb, struct xt_action_param *par) + return ret; + } + +-static struct xt_match mac_mt_reg __read_mostly = { +- .name = "mac", +- .revision = 0, +- .family = NFPROTO_UNSPEC, +- .match = mac_mt, +- .matchsize = sizeof(struct xt_mac_info), +- .hooks = (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_IN) | +- (1 << NF_INET_FORWARD), +- .me = THIS_MODULE, ++static struct xt_match mac_mt_reg[] __read_mostly = { ++ { ++ .name = "mac", ++ .family = NFPROTO_IPV4, ++ .match = mac_mt, ++ .matchsize = sizeof(struct xt_mac_info), ++ .hooks = (1 << NF_INET_PRE_ROUTING) | ++ (1 << NF_INET_LOCAL_IN) | ++ (1 << NF_INET_FORWARD), ++ .me = THIS_MODULE, ++ }, ++ { ++ .name = "mac", ++ .family = NFPROTO_IPV6, ++ .match = mac_mt, ++ .matchsize = sizeof(struct xt_mac_info), ++ .hooks = (1 << NF_INET_PRE_ROUTING) | ++ (1 << NF_INET_LOCAL_IN) | ++ (1 << NF_INET_FORWARD), ++ .me = THIS_MODULE, ++ }, + }; + + static int __init mac_mt_init(void) + { +- return xt_register_match(&mac_mt_reg); ++ return xt_register_matches(mac_mt_reg, ARRAY_SIZE(mac_mt_reg)); + } + + static void __exit mac_mt_exit(void) + { +- xt_unregister_match(&mac_mt_reg); ++ xt_unregister_matches(mac_mt_reg, ARRAY_SIZE(mac_mt_reg)); + } + + module_init(mac_mt_init); +diff --git a/net/netfilter/xt_owner.c b/net/netfilter/xt_owner.c +index 5033288..7be2fe2 100644 +--- a/net/netfilter/xt_owner.c ++++ b/net/netfilter/xt_owner.c +@@ -127,26 +127,39 @@ owner_mt(const struct sk_buff *skb, struct xt_action_param *par) + return true; + } + +-static struct xt_match owner_mt_reg __read_mostly = { +- .name = "owner", +- .revision = 1, +- .family = NFPROTO_UNSPEC, +- .checkentry = owner_check, +- .match = owner_mt, +- .matchsize = sizeof(struct xt_owner_match_info), +- .hooks = (1 << NF_INET_LOCAL_OUT) | +- (1 << NF_INET_POST_ROUTING), +- .me = THIS_MODULE, ++static struct xt_match owner_mt_reg[] __read_mostly = { ++ { ++ .name = "owner", ++ .revision = 1, ++ .family = NFPROTO_IPV4, ++ .checkentry = owner_check, ++ .match = owner_mt, ++ .matchsize = sizeof(struct xt_owner_match_info), ++ .hooks = (1 << NF_INET_LOCAL_OUT) | ++ (1 << NF_INET_POST_ROUTING), ++ .me = THIS_MODULE, ++ }, ++ { ++ .name = "owner", ++ .revision = 1, ++ .family = NFPROTO_IPV6, ++ .checkentry = owner_check, ++ .match = owner_mt, ++ .matchsize = sizeof(struct xt_owner_match_info), ++ .hooks = (1 << NF_INET_LOCAL_OUT) | ++ (1 << NF_INET_POST_ROUTING), ++ .me = THIS_MODULE, ++ } + }; + + static int __init owner_mt_init(void) + { +- return xt_register_match(&owner_mt_reg); ++ return xt_register_matches(owner_mt_reg, ARRAY_SIZE(owner_mt_reg)); + } + + static void __exit owner_mt_exit(void) + { +- xt_unregister_match(&owner_mt_reg); ++ xt_unregister_matches(owner_mt_reg, ARRAY_SIZE(owner_mt_reg)); + } + + module_init(owner_mt_init); +diff --git a/net/netfilter/xt_physdev.c b/net/netfilter/xt_physdev.c +index 343e65f..130842c 100644 +--- a/net/netfilter/xt_physdev.c ++++ b/net/netfilter/xt_physdev.c +@@ -115,24 +115,33 @@ static int physdev_mt_check(const struct xt_mtchk_param *par) + return 0; + } + +-static struct xt_match physdev_mt_reg __read_mostly = { +- .name = "physdev", +- .revision = 0, +- .family = NFPROTO_UNSPEC, +- .checkentry = physdev_mt_check, +- .match = physdev_mt, +- .matchsize = sizeof(struct xt_physdev_info), +- .me = THIS_MODULE, ++static struct xt_match physdev_mt_reg[] __read_mostly = { ++ { ++ .name = "physdev", ++ .family = NFPROTO_IPV4, ++ .checkentry = physdev_mt_check, ++ .match = physdev_mt, ++ .matchsize = sizeof(struct xt_physdev_info), ++ .me = THIS_MODULE, ++ }, ++ { ++ .name = "physdev", ++ .family = NFPROTO_IPV6, ++ .checkentry = physdev_mt_check, ++ .match = physdev_mt, ++ .matchsize = sizeof(struct xt_physdev_info), ++ .me = THIS_MODULE, ++ }, + }; + + static int __init physdev_mt_init(void) + { +- return xt_register_match(&physdev_mt_reg); ++ return xt_register_matches(physdev_mt_reg, ARRAY_SIZE(physdev_mt_reg)); + } + + static void __exit physdev_mt_exit(void) + { +- xt_unregister_match(&physdev_mt_reg); ++ xt_unregister_matches(physdev_mt_reg, ARRAY_SIZE(physdev_mt_reg)); + } + + module_init(physdev_mt_init); +diff --git a/net/netfilter/xt_realm.c b/net/netfilter/xt_realm.c +index 6df485f..61b2f1e 100644 +--- a/net/netfilter/xt_realm.c ++++ b/net/netfilter/xt_realm.c +@@ -33,7 +33,7 @@ static struct xt_match realm_mt_reg __read_mostly = { + .matchsize = sizeof(struct xt_realm_info), + .hooks = (1 << NF_INET_POST_ROUTING) | (1 << NF_INET_FORWARD) | + (1 << NF_INET_LOCAL_OUT) | (1 << NF_INET_LOCAL_IN), +- .family = NFPROTO_UNSPEC, ++ .family = NFPROTO_IPV4, + .me = THIS_MODULE + }; + diff --git a/1146-netfilter-x-tables-add-check-hooks-to-matches-and-targets.patch b/1146-netfilter-x-tables-add-check-hooks-to-matches-and-targets.patch new file mode 100644 index 000000000..8ccfb25a3 --- /dev/null +++ b/1146-netfilter-x-tables-add-check-hooks-to-matches-and-targets.patch @@ -0,0 +1,487 @@ +From 65acf9af5571ee9fd69dd8d8e4e5b7a423905d3b Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:22:59 +0200 +Subject: [PATCH] netfilter: x_tables: add .check_hooks to matches and targets + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 6813985ca456 + +commit 6813985ca456d1f5677ad9554f55805cbf27e16f +Author: Pablo Neira Ayuso +Date: Tue Apr 28 17:35:18 2026 +0200 + + netfilter: x_tables: add .check_hooks to matches and targets + + Add a new .check_hooks interface for checking if the match/target is + used from the validate hook according to its configuration. + + Move existing conditional hook check based on the match/target + configuration from .checkentry to .check_hooks for the following + matches/targets: + + - addrtype + - devgroup + - physdev + - policy + - set + - TCPMSS + - SET + + This is a preparation patch to fix nft_compat, not functional changes + are intended. + + Based on patch from Florian Westphal. + + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h +index 5897f3d..53b1f25 100644 +--- a/include/linux/netfilter/x_tables.h ++++ b/include/linux/netfilter/x_tables.h +@@ -156,6 +156,9 @@ struct xt_match { + /* Called when user tries to insert an entry of this type. */ + int (*checkentry)(const struct xt_mtchk_param *); + ++ /* Called to validate hooks based on the match configuration. */ ++ int (*check_hooks)(const struct xt_mtchk_param *); ++ + /* Called when entry of this type deleted. */ + void (*destroy)(const struct xt_mtdtor_param *); + #ifdef CONFIG_NETFILTER_XTABLES_COMPAT +@@ -197,6 +200,9 @@ struct xt_target { + /* Should return 0 on success or an error code otherwise (-Exxxx). */ + int (*checkentry)(const struct xt_tgchk_param *); + ++ /* Called to validate hooks based on the target configuration. */ ++ int (*check_hooks)(const struct xt_tgchk_param *); ++ + /* Called when entry of this type deleted. */ + void (*destroy)(const struct xt_tgdtor_param *); + #ifdef CONFIG_NETFILTER_XTABLES_COMPAT +@@ -289,8 +295,10 @@ bool xt_find_jump_offset(const unsigned int *offsets, + + int xt_check_proc_name(const char *name, unsigned int size); + ++int xt_check_hooks_match(struct xt_mtchk_param *par); + int xt_check_match(struct xt_mtchk_param *, unsigned int size, u16 proto, + bool inv_proto); ++int xt_check_hooks_target(struct xt_tgchk_param *par); + int xt_check_target(struct xt_tgchk_param *, unsigned int size, u16 proto, + bool inv_proto); + +diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c +index 10530a0..9be7832 100644 +--- a/net/netfilter/x_tables.c ++++ b/net/netfilter/x_tables.c +@@ -477,11 +477,9 @@ int xt_check_proc_name(const char *name, unsigned int size) + } + EXPORT_SYMBOL(xt_check_proc_name); + +-int xt_check_match(struct xt_mtchk_param *par, +- unsigned int size, u16 proto, bool inv_proto) ++static int xt_check_match_common(struct xt_mtchk_param *par, ++ unsigned int size, u16 proto, bool inv_proto) + { +- int ret; +- + if (XT_ALIGN(par->match->matchsize) != size && + par->match->matchsize != -1) { + /* +@@ -530,6 +528,14 @@ int xt_check_match(struct xt_mtchk_param *par, + par->match->proto); + return -EINVAL; + } ++ ++ return 0; ++} ++ ++static int xt_checkentry_match(struct xt_mtchk_param *par) ++{ ++ int ret; ++ + if (par->match->checkentry != NULL) { + ret = par->match->checkentry(par); + if (ret < 0) +@@ -538,8 +544,34 @@ int xt_check_match(struct xt_mtchk_param *par, + /* Flag up potential errors. */ + return -EIO; + } ++ ++ return 0; ++} ++ ++int xt_check_hooks_match(struct xt_mtchk_param *par) ++{ ++ if (par->match->check_hooks != NULL) ++ return par->match->check_hooks(par); ++ + return 0; + } ++EXPORT_SYMBOL_GPL(xt_check_hooks_match); ++ ++int xt_check_match(struct xt_mtchk_param *par, ++ unsigned int size, u16 proto, bool inv_proto) ++{ ++ int ret; ++ ++ ret = xt_check_match_common(par, size, proto, inv_proto); ++ if (ret < 0) ++ return ret; ++ ++ ret = xt_check_hooks_match(par); ++ if (ret < 0) ++ return ret; ++ ++ return xt_checkentry_match(par); ++} + EXPORT_SYMBOL_GPL(xt_check_match); + + /** xt_check_entry_match - check that matches end before start of target +@@ -1008,11 +1040,9 @@ bool xt_find_jump_offset(const unsigned int *offsets, + } + EXPORT_SYMBOL(xt_find_jump_offset); + +-int xt_check_target(struct xt_tgchk_param *par, +- unsigned int size, u16 proto, bool inv_proto) ++static int xt_check_target_common(struct xt_tgchk_param *par, ++ unsigned int size, u16 proto, bool inv_proto) + { +- int ret; +- + if (XT_ALIGN(par->target->targetsize) != size) { + pr_err_ratelimited("%s_tables: %s.%u target: invalid size %u (kernel) != (user) %u\n", + xt_prefix[par->family], par->target->name, +@@ -1057,6 +1087,23 @@ int xt_check_target(struct xt_tgchk_param *par, + par->target->proto); + return -EINVAL; + } ++ ++ return 0; ++} ++ ++int xt_check_hooks_target(struct xt_tgchk_param *par) ++{ ++ if (par->target->check_hooks != NULL) ++ return par->target->check_hooks(par); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(xt_check_hooks_target); ++ ++static int xt_checkentry_target(struct xt_tgchk_param *par) ++{ ++ int ret; ++ + if (par->target->checkentry != NULL) { + ret = par->target->checkentry(par); + if (ret < 0) +@@ -1067,6 +1114,22 @@ int xt_check_target(struct xt_tgchk_param *par, + } + return 0; + } ++ ++int xt_check_target(struct xt_tgchk_param *par, ++ unsigned int size, u16 proto, bool inv_proto) ++{ ++ int ret; ++ ++ ret = xt_check_target_common(par, size, proto, inv_proto); ++ if (ret < 0) ++ return ret; ++ ++ ret = xt_check_hooks_target(par); ++ if (ret < 0) ++ return ret; ++ ++ return xt_checkentry_target(par); ++} + EXPORT_SYMBOL_GPL(xt_check_target); + + /** +diff --git a/net/netfilter/xt_addrtype.c b/net/netfilter/xt_addrtype.c +index a770889..913dbe3 100644 +--- a/net/netfilter/xt_addrtype.c ++++ b/net/netfilter/xt_addrtype.c +@@ -153,14 +153,10 @@ addrtype_mt_v1(const struct sk_buff *skb, struct xt_action_param *par) + return ret; + } + +-static int addrtype_mt_checkentry_v1(const struct xt_mtchk_param *par) ++static int addrtype_mt_check_hooks(const struct xt_mtchk_param *par) + { +- const char *errmsg = "both incoming and outgoing interface limitation cannot be selected"; + struct xt_addrtype_info_v1 *info = par->matchinfo; +- +- if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN && +- info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT) +- goto err; ++ const char *errmsg; + + if (par->hook_mask & ((1 << NF_INET_PRE_ROUTING) | + (1 << NF_INET_LOCAL_IN)) && +@@ -176,6 +172,21 @@ static int addrtype_mt_checkentry_v1(const struct xt_mtchk_param *par) + goto err; + } + ++ return 0; ++err: ++ pr_info_ratelimited("%s\n", errmsg); ++ return -EINVAL; ++} ++ ++static int addrtype_mt_checkentry_v1(const struct xt_mtchk_param *par) ++{ ++ const char *errmsg = "both incoming and outgoing interface limitation cannot be selected"; ++ struct xt_addrtype_info_v1 *info = par->matchinfo; ++ ++ if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN && ++ info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT) ++ goto err; ++ + #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) + if (par->family == NFPROTO_IPV6) { + if ((info->source | info->dest) & XT_ADDRTYPE_BLACKHOLE) { +@@ -211,6 +222,7 @@ static struct xt_match addrtype_mt_reg[] __read_mostly = { + .family = NFPROTO_IPV4, + .revision = 1, + .match = addrtype_mt_v1, ++ .check_hooks = addrtype_mt_check_hooks, + .checkentry = addrtype_mt_checkentry_v1, + .matchsize = sizeof(struct xt_addrtype_info_v1), + .me = THIS_MODULE +@@ -221,6 +233,7 @@ static struct xt_match addrtype_mt_reg[] __read_mostly = { + .family = NFPROTO_IPV6, + .revision = 1, + .match = addrtype_mt_v1, ++ .check_hooks = addrtype_mt_check_hooks, + .checkentry = addrtype_mt_checkentry_v1, + .matchsize = sizeof(struct xt_addrtype_info_v1), + .me = THIS_MODULE +diff --git a/net/netfilter/xt_devgroup.c b/net/netfilter/xt_devgroup.c +index 9520dd0..6d1a44a 100644 +--- a/net/netfilter/xt_devgroup.c ++++ b/net/netfilter/xt_devgroup.c +@@ -33,14 +33,10 @@ static bool devgroup_mt(const struct sk_buff *skb, struct xt_action_param *par) + return true; + } + +-static int devgroup_mt_checkentry(const struct xt_mtchk_param *par) ++static int devgroup_mt_check_hooks(const struct xt_mtchk_param *par) + { + const struct xt_devgroup_info *info = par->matchinfo; + +- if (info->flags & ~(XT_DEVGROUP_MATCH_SRC | XT_DEVGROUP_INVERT_SRC | +- XT_DEVGROUP_MATCH_DST | XT_DEVGROUP_INVERT_DST)) +- return -EINVAL; +- + if (info->flags & XT_DEVGROUP_MATCH_SRC && + par->hook_mask & ~((1 << NF_INET_PRE_ROUTING) | + (1 << NF_INET_LOCAL_IN) | +@@ -56,9 +52,21 @@ static int devgroup_mt_checkentry(const struct xt_mtchk_param *par) + return 0; + } + ++static int devgroup_mt_checkentry(const struct xt_mtchk_param *par) ++{ ++ const struct xt_devgroup_info *info = par->matchinfo; ++ ++ if (info->flags & ~(XT_DEVGROUP_MATCH_SRC | XT_DEVGROUP_INVERT_SRC | ++ XT_DEVGROUP_MATCH_DST | XT_DEVGROUP_INVERT_DST)) ++ return -EINVAL; ++ ++ return 0; ++} ++ + static struct xt_match devgroup_mt_reg __read_mostly = { + .name = "devgroup", + .match = devgroup_mt, ++ .check_hooks = devgroup_mt_check_hooks, + .checkentry = devgroup_mt_checkentry, + .matchsize = sizeof(struct xt_devgroup_info), + .family = NFPROTO_UNSPEC, +diff --git a/net/netfilter/xt_physdev.c b/net/netfilter/xt_physdev.c +index 130842c..e6025d7 100644 +--- a/net/netfilter/xt_physdev.c ++++ b/net/netfilter/xt_physdev.c +@@ -91,14 +91,10 @@ physdev_mt(const struct sk_buff *skb, struct xt_action_param *par) + return (!!ret ^ !(info->invert & XT_PHYSDEV_OP_OUT)); + } + +-static int physdev_mt_check(const struct xt_mtchk_param *par) ++static int physdev_mt_check_hooks(const struct xt_mtchk_param *par) + { + const struct xt_physdev_info *info = par->matchinfo; +- static bool brnf_probed __read_mostly; + +- if (!(info->bitmask & XT_PHYSDEV_OP_MASK) || +- info->bitmask & ~XT_PHYSDEV_OP_MASK) +- return -EINVAL; + if (info->bitmask & (XT_PHYSDEV_OP_OUT | XT_PHYSDEV_OP_ISOUT) && + (!(info->bitmask & XT_PHYSDEV_OP_BRIDGED) || + info->invert & XT_PHYSDEV_OP_BRIDGED) && +@@ -107,6 +103,18 @@ static int physdev_mt_check(const struct xt_mtchk_param *par) + return -EINVAL; + } + ++ return 0; ++} ++ ++static int physdev_mt_check(const struct xt_mtchk_param *par) ++{ ++ const struct xt_physdev_info *info = par->matchinfo; ++ static bool brnf_probed __read_mostly; ++ ++ if (!(info->bitmask & XT_PHYSDEV_OP_MASK) || ++ info->bitmask & ~XT_PHYSDEV_OP_MASK) ++ return -EINVAL; ++ + if (!brnf_probed) { + brnf_probed = true; + request_module("br_netfilter"); +@@ -119,6 +127,7 @@ static struct xt_match physdev_mt_reg[] __read_mostly = { + { + .name = "physdev", + .family = NFPROTO_IPV4, ++ .check_hooks = physdev_mt_check_hooks, + .checkentry = physdev_mt_check, + .match = physdev_mt, + .matchsize = sizeof(struct xt_physdev_info), +@@ -127,6 +136,7 @@ static struct xt_match physdev_mt_reg[] __read_mostly = { + { + .name = "physdev", + .family = NFPROTO_IPV6, ++ .check_hooks = physdev_mt_check_hooks, + .checkentry = physdev_mt_check, + .match = physdev_mt, + .matchsize = sizeof(struct xt_physdev_info), +diff --git a/net/netfilter/xt_policy.c b/net/netfilter/xt_policy.c +index b5fa655..ff54e3a 100644 +--- a/net/netfilter/xt_policy.c ++++ b/net/netfilter/xt_policy.c +@@ -126,13 +126,10 @@ policy_mt(const struct sk_buff *skb, struct xt_action_param *par) + return ret; + } + +-static int policy_mt_check(const struct xt_mtchk_param *par) ++static int policy_mt_check_hooks(const struct xt_mtchk_param *par) + { + const struct xt_policy_info *info = par->matchinfo; +- const char *errmsg = "neither incoming nor outgoing policy selected"; +- +- if (!(info->flags & (XT_POLICY_MATCH_IN|XT_POLICY_MATCH_OUT))) +- goto err; ++ const char *errmsg; + + if (par->hook_mask & ((1 << NF_INET_PRE_ROUTING) | + (1 << NF_INET_LOCAL_IN)) && info->flags & XT_POLICY_MATCH_OUT) { +@@ -144,6 +141,21 @@ static int policy_mt_check(const struct xt_mtchk_param *par) + errmsg = "input policy not valid in POSTROUTING and OUTPUT"; + goto err; + } ++ ++ return 0; ++err: ++ pr_info_ratelimited("%s\n", errmsg); ++ return -EINVAL; ++} ++ ++static int policy_mt_check(const struct xt_mtchk_param *par) ++{ ++ const struct xt_policy_info *info = par->matchinfo; ++ const char *errmsg = "neither incoming nor outgoing policy selected"; ++ ++ if (!(info->flags & (XT_POLICY_MATCH_IN|XT_POLICY_MATCH_OUT))) ++ goto err; ++ + if (info->len > XT_POLICY_MAX_ELEM) { + errmsg = "too many policy elements"; + goto err; +@@ -158,6 +170,7 @@ static struct xt_match policy_mt_reg[] __read_mostly = { + { + .name = "policy", + .family = NFPROTO_IPV4, ++ .check_hooks = policy_mt_check_hooks, + .checkentry = policy_mt_check, + .match = policy_mt, + .matchsize = sizeof(struct xt_policy_info), +@@ -166,6 +179,7 @@ static struct xt_match policy_mt_reg[] __read_mostly = { + { + .name = "policy", + .family = NFPROTO_IPV6, ++ .check_hooks = policy_mt_check_hooks, + .checkentry = policy_mt_check, + .match = policy_mt, + .matchsize = sizeof(struct xt_policy_info), +diff --git a/net/netfilter/xt_set.c b/net/netfilter/xt_set.c +index 731bc2c..4ae04bb 100644 +--- a/net/netfilter/xt_set.c ++++ b/net/netfilter/xt_set.c +@@ -430,6 +430,29 @@ set_target_v3(struct sk_buff *skb, const struct xt_action_param *par) + return XT_CONTINUE; + } + ++static int ++set_target_v3_check_hooks(const struct xt_tgchk_param *par) ++{ ++ const struct xt_set_info_target_v3 *info = par->targinfo; ++ ++ if (info->map_set.index != IPSET_INVALID_ID) { ++ if (strncmp(par->table, "mangle", 7)) { ++ pr_info_ratelimited("--map-set only usable from mangle table\n"); ++ return -EINVAL; ++ } ++ if (((info->flags & IPSET_FLAG_MAP_SKBPRIO) | ++ (info->flags & IPSET_FLAG_MAP_SKBQUEUE)) && ++ (par->hook_mask & ~(1 << NF_INET_FORWARD | ++ 1 << NF_INET_LOCAL_OUT | ++ 1 << NF_INET_POST_ROUTING))) { ++ pr_info_ratelimited("mapping of prio or/and queue is allowed only from OUTPUT/FORWARD/POSTROUTING chains\n"); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ + static int + set_target_v3_checkentry(const struct xt_tgchk_param *par) + { +@@ -459,20 +482,6 @@ set_target_v3_checkentry(const struct xt_tgchk_param *par) + } + + if (info->map_set.index != IPSET_INVALID_ID) { +- if (strncmp(par->table, "mangle", 7)) { +- pr_info_ratelimited("--map-set only usable from mangle table\n"); +- ret = -EINVAL; +- goto cleanup_del; +- } +- if (((info->flags & IPSET_FLAG_MAP_SKBPRIO) | +- (info->flags & IPSET_FLAG_MAP_SKBQUEUE)) && +- (par->hook_mask & ~(1 << NF_INET_FORWARD | +- 1 << NF_INET_LOCAL_OUT | +- 1 << NF_INET_POST_ROUTING))) { +- pr_info_ratelimited("mapping of prio or/and queue is allowed only from OUTPUT/FORWARD/POSTROUTING chains\n"); +- ret = -EINVAL; +- goto cleanup_del; +- } + index = ip_set_nfnl_get_byindex(par->net, + info->map_set.index); + if (index == IPSET_INVALID_ID) { +@@ -672,6 +681,7 @@ static struct xt_target set_targets[] __read_mostly = { + .family = NFPROTO_IPV4, + .target = set_target_v3, + .targetsize = sizeof(struct xt_set_info_target_v3), ++ .check_hooks = set_target_v3_check_hooks, + .checkentry = set_target_v3_checkentry, + .destroy = set_target_v3_destroy, + .me = THIS_MODULE +@@ -682,6 +692,7 @@ static struct xt_target set_targets[] __read_mostly = { + .family = NFPROTO_IPV6, + .target = set_target_v3, + .targetsize = sizeof(struct xt_set_info_target_v3), ++ .check_hooks = set_target_v3_check_hooks, + .checkentry = set_target_v3_checkentry, + .destroy = set_target_v3_destroy, + .me = THIS_MODULE diff --git a/1147-netfilter-nft-compat-run-xt-check-hooks-match-target-from-validate.patch b/1147-netfilter-nft-compat-run-xt-check-hooks-match-target-from-validate.patch new file mode 100644 index 000000000..2341a91e9 --- /dev/null +++ b/1147-netfilter-nft-compat-run-xt-check-hooks-match-target-from-validate.patch @@ -0,0 +1,148 @@ +From 0a7bf7be14b96c957f43956b2cec03faeee6a6c7 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:23:00 +0200 +Subject: [PATCH] netfilter: nft_compat: run xt_check_hooks_{match,target}() + from .validate + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 2f768d638d97 + +commit 2f768d638d977eff824f64dcc9639e3fea32da8f +Author: Pablo Neira Ayuso +Date: Tue Apr 28 19:04:07 2026 +0200 + + netfilter: nft_compat: run xt_check_hooks_{match,target}() from .validate + + Several matches and one target check that the hook is correct from + checkentry(), however, the basechain is only available from + nft_table_validate(). + + This patch uses xt_check_hooks_{match,target}() from the nft_compat + expression .validate path. + + This patch sets the table in the nft_ctx struct in nft_table_validate() + which is required by this patch. + + Based on patch from Florian Westphal. + + Fixes: 0ca743a55991 ("netfilter: nf_tables: add compatibility layer for x_tables") + Reported-by: Xiang Mei + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 32155f1..188a621 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -4185,6 +4185,7 @@ static int nft_table_validate(struct net *net, const struct nft_table *table) + struct nft_chain *chain; + struct nft_ctx ctx = { + .net = net, ++ .table = (struct nft_table *)table, + .family = table->family, + }; + int err = 0; +diff --git a/net/netfilter/nft_compat.c b/net/netfilter/nft_compat.c +index 72711d6..c58a93d 100644 +--- a/net/netfilter/nft_compat.c ++++ b/net/netfilter/nft_compat.c +@@ -260,10 +260,10 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr, + return ret; + } + +- nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv); +- + nft_compat_wait_for_destructors(ctx->net); + ++ nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv); ++ + ret = xt_check_target(&par, size, proto, inv); + if (ret < 0) { + if (ret == -ENOENT) { +@@ -352,8 +352,6 @@ static int nft_target_dump(struct sk_buff *skb, + static int nft_target_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) + { +- struct xt_target *target = expr->ops->data; +- unsigned int hook_mask = 0; + int ret; + + if (ctx->family != NFPROTO_IPV4 && +@@ -376,11 +374,21 @@ static int nft_target_validate(const struct nft_ctx *ctx, + const struct nft_base_chain *basechain = + nft_base_chain(ctx->chain); + const struct nf_hook_ops *ops = &basechain->ops; ++ unsigned int hook_mask = 1 << ops->hooknum; ++ struct xt_target *target = expr->ops->data; ++ void *info = nft_expr_priv(expr); ++ struct xt_tgchk_param par; ++ union nft_entry e = {}; + +- hook_mask = 1 << ops->hooknum; + if (target->hooks && !(hook_mask & target->hooks)) + return -EINVAL; + ++ nft_target_set_tgchk_param(&par, ctx, target, info, &e, 0, false); ++ ++ ret = xt_check_hooks_target(&par); ++ if (ret < 0) ++ return ret; ++ + ret = nft_compat_chain_validate_dependency(ctx, target->table); + if (ret < 0) + return ret; +@@ -513,10 +521,10 @@ __nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr, + return ret; + } + +- nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv); +- + nft_compat_wait_for_destructors(ctx->net); + ++ nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv); ++ + return xt_check_match(&par, size, proto, inv); + } + +@@ -612,8 +620,6 @@ static int nft_match_large_dump(struct sk_buff *skb, + static int nft_match_validate(const struct nft_ctx *ctx, + const struct nft_expr *expr) + { +- struct xt_match *match = expr->ops->data; +- unsigned int hook_mask = 0; + int ret; + + if (ctx->family != NFPROTO_IPV4 && +@@ -636,11 +642,30 @@ static int nft_match_validate(const struct nft_ctx *ctx, + const struct nft_base_chain *basechain = + nft_base_chain(ctx->chain); + const struct nf_hook_ops *ops = &basechain->ops; ++ unsigned int hook_mask = 1 << ops->hooknum; ++ struct xt_match *match = expr->ops->data; ++ size_t size = XT_ALIGN(match->matchsize); ++ struct xt_mtchk_param par; ++ union nft_entry e = {}; ++ void *info; + +- hook_mask = 1 << ops->hooknum; + if (match->hooks && !(hook_mask & match->hooks)) + return -EINVAL; + ++ if (NFT_EXPR_SIZE(size) > NFT_MATCH_LARGE_THRESH) { ++ struct nft_xt_match_priv *priv = nft_expr_priv(expr); ++ ++ info = priv->info; ++ } else { ++ info = nft_expr_priv(expr); ++ } ++ ++ nft_match_set_mtchk_param(&par, ctx, match, info, &e, 0, false); ++ ++ ret = xt_check_hooks_match(&par); ++ if (ret < 0) ++ return ret; ++ + ret = nft_compat_chain_validate_dependency(ctx, match->table); + if (ret < 0) + return ret; diff --git a/1148-netfilter-xt-ct-fix-usersize-for-v1-and-v2-revision.patch b/1148-netfilter-xt-ct-fix-usersize-for-v1-and-v2-revision.patch new file mode 100644 index 000000000..5a2a35824 --- /dev/null +++ b/1148-netfilter-xt-ct-fix-usersize-for-v1-and-v2-revision.patch @@ -0,0 +1,69 @@ +From 9b62200a0fb5be4d5f78d4911d534b2cda92d079 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:23:01 +0200 +Subject: [PATCH] netfilter: xt_CT: fix usersize for v1 and v2 revision + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 8bedb6c46945 + +commit 8bedb6c46945752a688d9b0cf2021e0e68b1876c +Author: Florian Westphal +Date: Tue Apr 28 19:37:57 2026 +0200 + + netfilter: xt_CT: fix usersize for v1 and v2 revision + + While resurrecting the conntrack-tool test cases I found following bug: + In: + iptables -I OUTPUT -t raw -p 13 -j CT --timeout test-generic + Out: + [0:0] -A OUTPUT -p 13 -j CT --timeout test + + Data after first four bytes of the timeout policy name is never + copied to userspace because its treated as kernel-only. + + Fixes: ec2318904965 ("xtables: extend matches and targets with .usersize") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/xt_CT.c b/net/netfilter/xt_CT.c +index 3ba94c3..23f46bc 100644 +--- a/net/netfilter/xt_CT.c ++++ b/net/netfilter/xt_CT.c +@@ -350,7 +350,7 @@ static struct xt_target xt_ct_tg_reg[] __read_mostly = { + .family = NFPROTO_IPV4, + .revision = 1, + .targetsize = sizeof(struct xt_ct_target_info_v1), +- .usersize = offsetof(struct xt_ct_target_info, ct), ++ .usersize = offsetof(struct xt_ct_target_info_v1, ct), + .checkentry = xt_ct_tg_check_v1, + .destroy = xt_ct_tg_destroy_v1, + .target = xt_ct_target_v1, +@@ -362,7 +362,7 @@ static struct xt_target xt_ct_tg_reg[] __read_mostly = { + .family = NFPROTO_IPV4, + .revision = 2, + .targetsize = sizeof(struct xt_ct_target_info_v1), +- .usersize = offsetof(struct xt_ct_target_info, ct), ++ .usersize = offsetof(struct xt_ct_target_info_v1, ct), + .checkentry = xt_ct_tg_check_v2, + .destroy = xt_ct_tg_destroy_v1, + .target = xt_ct_target_v1, +@@ -394,7 +394,7 @@ static struct xt_target xt_ct_tg_reg[] __read_mostly = { + .family = NFPROTO_IPV6, + .revision = 1, + .targetsize = sizeof(struct xt_ct_target_info_v1), +- .usersize = offsetof(struct xt_ct_target_info, ct), ++ .usersize = offsetof(struct xt_ct_target_info_v1, ct), + .checkentry = xt_ct_tg_check_v1, + .destroy = xt_ct_tg_destroy_v1, + .target = xt_ct_target_v1, +@@ -406,7 +406,7 @@ static struct xt_target xt_ct_tg_reg[] __read_mostly = { + .family = NFPROTO_IPV6, + .revision = 2, + .targetsize = sizeof(struct xt_ct_target_info_v1), +- .usersize = offsetof(struct xt_ct_target_info, ct), ++ .usersize = offsetof(struct xt_ct_target_info_v1, ct), + .checkentry = xt_ct_tg_check_v2, + .destroy = xt_ct_tg_destroy_v1, + .target = xt_ct_target_v1, diff --git a/1149-netfilter-nf-tables-fix-netdev-hook-allocation-memleak-with-dormant-tables.patch b/1149-netfilter-nf-tables-fix-netdev-hook-allocation-memleak-with-dormant-tables.patch new file mode 100644 index 000000000..617900440 --- /dev/null +++ b/1149-netfilter-nf-tables-fix-netdev-hook-allocation-memleak-with-dormant-tables.patch @@ -0,0 +1,631 @@ +From 56d449ab32e8a5457a93885e2d7a3e8d141ad11c Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:23:03 +0200 +Subject: [PATCH] netfilter: nf_tables: fix netdev hook allocation memleak with + dormant tables + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 63bac0278603 + +commit 63bac027860308d1344f761cb47aabb3b30973fd +Author: Florian Westphal +Date: Wed Apr 29 08:21:35 2026 +0200 + + netfilter: nf_tables: fix netdev hook allocation memleak with dormant tables + + sashiko says: + could the related code in __nf_tables_abort() leak the struct nft_hook objects when the table is dormant? + + In __nf_tables_abort(), when rolling back a NEWCHAIN transaction that + updates hooks, the code conditionally unregisters and frees the hooks only + if the table is not dormant [..] + if (!(table->flags & NFT_TABLE_F_DORMANT)) { + nft_netdev_unregister_hooks(net, + &nft_trans_chain_hooks(trans), + true); + } + ... + nft_trans_destroy(trans); + + Unfortunately netdev family mixes hook registration and allocation. + Push table struct down and only check for the flag to unregister. + + Fixes: 216e7bf7402c ("netfilter: nf_tables: skip netdev hook unregistration if table is dormant") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 188a621..9883e64 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -379,7 +379,34 @@ static void nft_netdev_hook_unlink_free_rcu(struct nft_hook *hook) + nft_netdev_hook_free_rcu(hook); + } + ++static void nft_trans_hook_destroy(struct nft_trans_hook *trans_hook) ++{ ++ list_del(&trans_hook->list); ++ kfree(trans_hook); ++} ++ ++static void nft_netdev_unregister_trans_hook(struct net *net, ++ const struct nft_table *table, ++ struct list_head *hook_list) ++{ ++ struct nft_trans_hook *trans_hook, *next; ++ struct nf_hook_ops *ops; ++ struct nft_hook *hook; ++ ++ list_for_each_entry_safe(trans_hook, next, hook_list, list) { ++ hook = trans_hook->hook; ++ ++ if (!(table->flags & NFT_TABLE_F_DORMANT)) { ++ list_for_each_entry(ops, &hook->ops_list, list) ++ nf_unregister_net_hook(net, ops); ++ } ++ nft_netdev_hook_unlink_free_rcu(hook); ++ nft_trans_hook_destroy(trans_hook); ++ } ++} ++ + static void nft_netdev_unregister_hooks(struct net *net, ++ const struct nft_table *table, + struct list_head *hook_list, + bool release_netdev) + { +@@ -387,8 +414,10 @@ static void nft_netdev_unregister_hooks(struct net *net, + struct nf_hook_ops *ops; + + list_for_each_entry_safe(hook, next, hook_list, list) { +- list_for_each_entry(ops, &hook->ops_list, list) +- nf_unregister_net_hook(net, ops); ++ if (!(table->flags & NFT_TABLE_F_DORMANT)) { ++ list_for_each_entry(ops, &hook->ops_list, list) ++ nf_unregister_net_hook(net, ops); ++ } + if (release_netdev) + nft_netdev_hook_unlink_free_rcu(hook); + } +@@ -425,20 +454,25 @@ static void __nf_tables_unregister_hook(struct net *net, + struct nft_base_chain *basechain; + const struct nf_hook_ops *ops; + +- if (table->flags & NFT_TABLE_F_DORMANT || +- !nft_is_base_chain(chain)) ++ if (!nft_is_base_chain(chain)) + return; + basechain = nft_base_chain(chain); + ops = &basechain->ops; + ++ /* must also be called for dormant tables */ ++ if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) { ++ nft_netdev_unregister_hooks(net, table, &basechain->hook_list, ++ release_netdev); ++ return; ++ } ++ ++ if (table->flags & NFT_TABLE_F_DORMANT) ++ return; ++ + if (basechain->type->ops_unregister) + return basechain->type->ops_unregister(net, ops); + +- if (nft_base_chain_netdev(table->family, basechain->ops.hooknum)) +- nft_netdev_unregister_hooks(net, &basechain->hook_list, +- release_netdev); +- else +- nf_unregister_net_hook(net, &basechain->ops); ++ nf_unregister_net_hook(net, &basechain->ops); + } + + static void nf_tables_unregister_hook(struct net *net, +@@ -1991,15 +2025,69 @@ static int nft_nla_put_hook_dev(struct sk_buff *skb, struct nft_hook *hook) + return nla_put_string(skb, attr, hook->ifname); + } + ++struct nft_hook_dump_ctx { ++ struct nft_hook *first; ++ int n; ++}; ++ ++static int nft_dump_basechain_hook_one(struct sk_buff *skb, ++ struct nft_hook *hook, ++ struct nft_hook_dump_ctx *dump_ctx) ++{ ++ if (!dump_ctx->first) ++ dump_ctx->first = hook; ++ ++ if (nft_nla_put_hook_dev(skb, hook)) ++ return -1; ++ ++ dump_ctx->n++; ++ ++ return 0; ++} ++ ++static int nft_dump_basechain_hook_list(struct sk_buff *skb, ++ const struct net *net, ++ const struct list_head *hook_list, ++ struct nft_hook_dump_ctx *dump_ctx) ++{ ++ struct nft_hook *hook; ++ int err; ++ ++ list_for_each_entry_rcu(hook, hook_list, list, ++ lockdep_commit_lock_is_held(net)) { ++ err = nft_dump_basechain_hook_one(skb, hook, dump_ctx); ++ if (err < 0) ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int nft_dump_basechain_trans_hook_list(struct sk_buff *skb, ++ const struct list_head *trans_hook_list, ++ struct nft_hook_dump_ctx *dump_ctx) ++{ ++ struct nft_trans_hook *trans_hook; ++ int err; ++ ++ list_for_each_entry(trans_hook, trans_hook_list, list) { ++ err = nft_dump_basechain_hook_one(skb, trans_hook->hook, dump_ctx); ++ if (err < 0) ++ return err; ++ } ++ ++ return 0; ++} ++ + static int nft_dump_basechain_hook(struct sk_buff *skb, + const struct net *net, int family, + const struct nft_base_chain *basechain, +- const struct list_head *hook_list) ++ const struct list_head *hook_list, ++ const struct list_head *trans_hook_list) + { + const struct nf_hook_ops *ops = &basechain->ops; +- struct nft_hook *hook, *first = NULL; ++ struct nft_hook_dump_ctx dump_hook_ctx = {}; + struct nlattr *nest, *nest_devs; +- int n = 0; + + nest = nla_nest_start_noflag(skb, NFTA_CHAIN_HOOK); + if (nest == NULL) +@@ -2014,23 +2102,23 @@ static int nft_dump_basechain_hook(struct sk_buff *skb, + if (!nest_devs) + goto nla_put_failure; + +- if (!hook_list) ++ if (!hook_list && !trans_hook_list) + hook_list = &basechain->hook_list; + +- list_for_each_entry_rcu(hook, hook_list, list, +- lockdep_commit_lock_is_held(net)) { +- if (!first) +- first = hook; +- +- if (nft_nla_put_hook_dev(skb, hook)) +- goto nla_put_failure; +- n++; ++ if (hook_list && ++ nft_dump_basechain_hook_list(skb, net, hook_list, &dump_hook_ctx)) { ++ goto nla_put_failure; ++ } else if (trans_hook_list && ++ nft_dump_basechain_trans_hook_list(skb, trans_hook_list, ++ &dump_hook_ctx)) { ++ goto nla_put_failure; + } ++ + nla_nest_end(skb, nest_devs); + +- if (n == 1 && +- !hook_is_prefix(first) && +- nla_put_string(skb, NFTA_HOOK_DEV, first->ifname)) ++ if (dump_hook_ctx.n == 1 && ++ !hook_is_prefix(dump_hook_ctx.first) && ++ nla_put_string(skb, NFTA_HOOK_DEV, dump_hook_ctx.first->ifname)) + goto nla_put_failure; + } + nla_nest_end(skb, nest); +@@ -2044,7 +2132,8 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net, + u32 portid, u32 seq, int event, u32 flags, + int family, const struct nft_table *table, + const struct nft_chain *chain, +- const struct list_head *hook_list) ++ const struct list_head *hook_list, ++ const struct list_head *trans_hook_list) + { + struct nlmsghdr *nlh; + +@@ -2060,7 +2149,7 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net, + NFTA_CHAIN_PAD)) + goto nla_put_failure; + +- if (event == NFT_MSG_DELCHAIN && !hook_list) { ++ if (event == NFT_MSG_DELCHAIN && !hook_list && !trans_hook_list) { + nlmsg_end(skb, nlh); + return 0; + } +@@ -2069,7 +2158,8 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net, + const struct nft_base_chain *basechain = nft_base_chain(chain); + struct nft_stats __percpu *stats; + +- if (nft_dump_basechain_hook(skb, net, family, basechain, hook_list)) ++ if (nft_dump_basechain_hook(skb, net, family, basechain, ++ hook_list, trans_hook_list)) + goto nla_put_failure; + + if (nla_put_be32(skb, NFTA_CHAIN_POLICY, +@@ -2105,7 +2195,8 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net, + } + + static void nf_tables_chain_notify(const struct nft_ctx *ctx, int event, +- const struct list_head *hook_list) ++ const struct list_head *hook_list, ++ const struct list_head *trans_hook_list) + { + struct nftables_pernet *nft_net; + struct sk_buff *skb; +@@ -2125,7 +2216,7 @@ static void nf_tables_chain_notify(const struct nft_ctx *ctx, int event, + + err = nf_tables_fill_chain_info(skb, ctx->net, ctx->portid, ctx->seq, + event, flags, ctx->family, ctx->table, +- ctx->chain, hook_list); ++ ctx->chain, hook_list, trans_hook_list); + if (err < 0) { + kfree_skb(skb); + goto err; +@@ -2171,7 +2262,7 @@ static int nf_tables_dump_chains(struct sk_buff *skb, + NFT_MSG_NEWCHAIN, + NLM_F_MULTI, + table->family, table, +- chain, NULL) < 0) ++ chain, NULL, NULL) < 0) + goto done; + + nl_dump_check_consistent(cb, nlmsg_hdr(skb)); +@@ -2225,7 +2316,7 @@ static int nf_tables_getchain(struct sk_buff *skb, const struct nfnl_info *info, + + err = nf_tables_fill_chain_info(skb2, net, NETLINK_CB(skb).portid, + info->nlh->nlmsg_seq, NFT_MSG_NEWCHAIN, +- 0, family, table, chain, NULL); ++ 0, family, table, chain, NULL, NULL); + if (err < 0) + goto err_fill_chain_info; + +@@ -2388,8 +2479,12 @@ static struct nft_hook *nft_hook_list_find(struct list_head *hook_list, + + list_for_each_entry(hook, hook_list, list) { + if (!strncmp(hook->ifname, this->ifname, +- min(hook->ifnamelen, this->ifnamelen))) ++ min(hook->ifnamelen, this->ifnamelen))) { ++ if (hook->flags & NFT_HOOK_REMOVE) ++ continue; ++ + return hook; ++ } + } + + return NULL; +@@ -3148,6 +3243,32 @@ static int nf_tables_newchain(struct sk_buff *skb, const struct nfnl_info *info, + return nf_tables_addchain(&ctx, family, policy, flags, extack); + } + ++static int nft_trans_delhook(struct nft_hook *hook, ++ struct list_head *del_list) ++{ ++ struct nft_trans_hook *trans_hook; ++ ++ trans_hook = kmalloc(sizeof(*trans_hook), GFP_KERNEL); ++ if (!trans_hook) ++ return -ENOMEM; ++ ++ trans_hook->hook = hook; ++ list_add_tail(&trans_hook->list, del_list); ++ hook->flags |= NFT_HOOK_REMOVE; ++ ++ return 0; ++} ++ ++static void nft_trans_delhook_abort(struct list_head *del_list) ++{ ++ struct nft_trans_hook *trans_hook, *next; ++ ++ list_for_each_entry_safe(trans_hook, next, del_list, list) { ++ trans_hook->hook->flags &= ~NFT_HOOK_REMOVE; ++ nft_trans_hook_destroy(trans_hook); ++ } ++} ++ + static int nft_delchain_hook(struct nft_ctx *ctx, + struct nft_base_chain *basechain, + struct netlink_ext_ack *extack) +@@ -3174,7 +3295,10 @@ static int nft_delchain_hook(struct nft_ctx *ctx, + err = -ENOENT; + goto err_chain_del_hook; + } +- list_move(&hook->list, &chain_del_list); ++ if (nft_trans_delhook(hook, &chain_del_list) < 0) { ++ err = -ENOMEM; ++ goto err_chain_del_hook; ++ } + } + + trans = nft_trans_alloc_chain(ctx, NFT_MSG_DELCHAIN); +@@ -3194,7 +3318,7 @@ static int nft_delchain_hook(struct nft_ctx *ctx, + return 0; + + err_chain_del_hook: +- list_splice(&chain_del_list, &basechain->hook_list); ++ nft_trans_delhook_abort(&chain_del_list); + nft_chain_release_hook(&chain_hook); + + return err; +@@ -9139,6 +9263,24 @@ static void nft_hooks_destroy(struct list_head *hook_list) + nft_netdev_hook_unlink_free_rcu(hook); + } + ++static void nft_flowtable_unregister_trans_hook(struct net *net, ++ struct nft_flowtable *flowtable, ++ struct list_head *hook_list) ++{ ++ struct nft_trans_hook *trans_hook, *next; ++ struct nf_hook_ops *ops; ++ struct nft_hook *hook; ++ ++ list_for_each_entry_safe(trans_hook, next, hook_list, list) { ++ hook = trans_hook->hook; ++ list_for_each_entry(ops, &hook->ops_list, list) ++ nft_unregister_flowtable_ops(net, flowtable, ops); ++ ++ nft_netdev_hook_unlink_free_rcu(hook); ++ nft_trans_hook_destroy(trans_hook); ++ } ++} ++ + static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh, + struct nft_flowtable *flowtable, + struct netlink_ext_ack *extack) +@@ -9397,7 +9539,10 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx, + err = -ENOENT; + goto err_flowtable_del_hook; + } +- list_move(&hook->list, &flowtable_del_list); ++ if (nft_trans_delhook(hook, &flowtable_del_list) < 0) { ++ err = -ENOMEM; ++ goto err_flowtable_del_hook; ++ } + } + + trans = nft_trans_alloc(ctx, NFT_MSG_DELFLOWTABLE, +@@ -9418,7 +9563,7 @@ static int nft_delflowtable_hook(struct nft_ctx *ctx, + return 0; + + err_flowtable_del_hook: +- list_splice(&flowtable_del_list, &flowtable->hook_list); ++ nft_trans_delhook_abort(&flowtable_del_list); + nft_flowtable_hook_release(&flowtable_hook); + + return err; +@@ -9483,8 +9628,10 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net, + u32 portid, u32 seq, int event, + u32 flags, int family, + struct nft_flowtable *flowtable, +- struct list_head *hook_list) ++ struct list_head *hook_list, ++ struct list_head *trans_hook_list) + { ++ struct nft_trans_hook *trans_hook; + struct nlattr *nest, *nest_devs; + struct nft_hook *hook; + struct nlmsghdr *nlh; +@@ -9501,7 +9648,7 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net, + NFTA_FLOWTABLE_PAD)) + goto nla_put_failure; + +- if (event == NFT_MSG_DELFLOWTABLE && !hook_list) { ++ if (event == NFT_MSG_DELFLOWTABLE && !hook_list && !trans_hook_list) { + nlmsg_end(skb, nlh); + return 0; + } +@@ -9521,13 +9668,20 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net, + if (!nest_devs) + goto nla_put_failure; + +- if (!hook_list) ++ if (!hook_list && !trans_hook_list) + hook_list = &flowtable->hook_list; + +- list_for_each_entry_rcu(hook, hook_list, list, +- lockdep_commit_lock_is_held(net)) { +- if (nft_nla_put_hook_dev(skb, hook)) +- goto nla_put_failure; ++ if (hook_list) { ++ list_for_each_entry_rcu(hook, hook_list, list, ++ lockdep_commit_lock_is_held(net)) { ++ if (nft_nla_put_hook_dev(skb, hook)) ++ goto nla_put_failure; ++ } ++ } else if (trans_hook_list) { ++ list_for_each_entry(trans_hook, trans_hook_list, list) { ++ if (nft_nla_put_hook_dev(skb, trans_hook->hook)) ++ goto nla_put_failure; ++ } + } + nla_nest_end(skb, nest_devs); + nla_nest_end(skb, nest); +@@ -9581,7 +9735,7 @@ static int nf_tables_dump_flowtable(struct sk_buff *skb, + NFT_MSG_NEWFLOWTABLE, + NLM_F_MULTI | NLM_F_APPEND, + table->family, +- flowtable, NULL) < 0) ++ flowtable, NULL, NULL) < 0) + goto done; + + nl_dump_check_consistent(cb, nlmsg_hdr(skb)); +@@ -9681,7 +9835,7 @@ static int nf_tables_getflowtable(struct sk_buff *skb, + err = nf_tables_fill_flowtable_info(skb2, net, NETLINK_CB(skb).portid, + info->nlh->nlmsg_seq, + NFT_MSG_NEWFLOWTABLE, 0, family, +- flowtable, NULL); ++ flowtable, NULL, NULL); + if (err < 0) + goto err_fill_flowtable_info; + +@@ -9694,7 +9848,9 @@ static int nf_tables_getflowtable(struct sk_buff *skb, + + static void nf_tables_flowtable_notify(struct nft_ctx *ctx, + struct nft_flowtable *flowtable, +- struct list_head *hook_list, int event) ++ struct list_head *hook_list, ++ struct list_head *trans_hook_list, ++ int event) + { + struct nftables_pernet *nft_net = nft_pernet(ctx->net); + struct sk_buff *skb; +@@ -9714,7 +9870,8 @@ static void nf_tables_flowtable_notify(struct nft_ctx *ctx, + + err = nf_tables_fill_flowtable_info(skb, ctx->net, ctx->portid, + ctx->seq, event, flags, +- ctx->family, flowtable, hook_list); ++ ctx->family, flowtable, ++ hook_list, trans_hook_list); + if (err < 0) { + kfree_skb(skb); + goto err; +@@ -10248,9 +10405,7 @@ static void nft_commit_release(struct nft_trans *trans) + break; + case NFT_MSG_DELCHAIN: + case NFT_MSG_DESTROYCHAIN: +- if (nft_trans_chain_update(trans)) +- nft_hooks_destroy(&nft_trans_chain_hooks(trans)); +- else ++ if (!nft_trans_chain_update(trans)) + nf_tables_chain_destroy(nft_trans_chain(trans)); + break; + case NFT_MSG_DELRULE: +@@ -10271,9 +10426,7 @@ static void nft_commit_release(struct nft_trans *trans) + break; + case NFT_MSG_DELFLOWTABLE: + case NFT_MSG_DESTROYFLOWTABLE: +- if (nft_trans_flowtable_update(trans)) +- nft_hooks_destroy(&nft_trans_flowtable_hooks(trans)); +- else ++ if (!nft_trans_flowtable_update(trans)) + nf_tables_flowtable_destroy(nft_trans_flowtable(trans)); + break; + } +@@ -11048,31 +11201,28 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + if (nft_trans_chain_update(trans)) { + nft_chain_commit_update(nft_trans_container_chain(trans)); + nf_tables_chain_notify(&ctx, NFT_MSG_NEWCHAIN, +- &nft_trans_chain_hooks(trans)); ++ &nft_trans_chain_hooks(trans), NULL); + list_splice_rcu(&nft_trans_chain_hooks(trans), + &nft_trans_basechain(trans)->hook_list); + /* trans destroyed after rcu grace period */ + } else { + nft_chain_commit_drop_policy(nft_trans_container_chain(trans)); + nft_clear(net, nft_trans_chain(trans)); +- nf_tables_chain_notify(&ctx, NFT_MSG_NEWCHAIN, NULL); ++ nf_tables_chain_notify(&ctx, NFT_MSG_NEWCHAIN, NULL, NULL); + nft_trans_destroy(trans); + } + break; + case NFT_MSG_DELCHAIN: + case NFT_MSG_DESTROYCHAIN: + if (nft_trans_chain_update(trans)) { +- nf_tables_chain_notify(&ctx, NFT_MSG_DELCHAIN, ++ nf_tables_chain_notify(&ctx, NFT_MSG_DELCHAIN, NULL, + &nft_trans_chain_hooks(trans)); +- if (!(table->flags & NFT_TABLE_F_DORMANT)) { +- nft_netdev_unregister_hooks(net, +- &nft_trans_chain_hooks(trans), +- true); +- } ++ nft_netdev_unregister_trans_hook(net, table, ++ &nft_trans_chain_hooks(trans)); + } else { + nft_chain_del(nft_trans_chain(trans)); + nf_tables_chain_notify(&ctx, NFT_MSG_DELCHAIN, +- NULL); ++ NULL, NULL); + nf_tables_unregister_hook(ctx.net, ctx.table, + nft_trans_chain(trans)); + } +@@ -11178,6 +11328,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + nf_tables_flowtable_notify(&ctx, + nft_trans_flowtable(trans), + &nft_trans_flowtable_hooks(trans), ++ NULL, + NFT_MSG_NEWFLOWTABLE); + list_splice_rcu(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); +@@ -11186,6 +11337,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + nf_tables_flowtable_notify(&ctx, + nft_trans_flowtable(trans), + NULL, ++ NULL, + NFT_MSG_NEWFLOWTABLE); + } + nft_trans_destroy(trans); +@@ -11195,16 +11347,18 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + if (nft_trans_flowtable_update(trans)) { + nf_tables_flowtable_notify(&ctx, + nft_trans_flowtable(trans), ++ NULL, + &nft_trans_flowtable_hooks(trans), + trans->msg_type); +- nft_unregister_flowtable_net_hooks(net, +- nft_trans_flowtable(trans), +- &nft_trans_flowtable_hooks(trans)); ++ nft_flowtable_unregister_trans_hook(net, ++ nft_trans_flowtable(trans), ++ &nft_trans_flowtable_hooks(trans)); + } else { + list_del_rcu(&nft_trans_flowtable(trans)->list); + nf_tables_flowtable_notify(&ctx, + nft_trans_flowtable(trans), + NULL, ++ NULL, + trans->msg_type); + nft_unregister_flowtable_net_hooks(net, + nft_trans_flowtable(trans), +@@ -11346,11 +11500,9 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_NEWCHAIN: + if (nft_trans_chain_update(trans)) { +- if (!(table->flags & NFT_TABLE_F_DORMANT)) { +- nft_netdev_unregister_hooks(net, +- &nft_trans_chain_hooks(trans), +- true); +- } ++ nft_netdev_unregister_hooks(net, table, ++ &nft_trans_chain_hooks(trans), ++ true); + free_percpu(nft_trans_chain_stats(trans)); + kfree(nft_trans_chain_name(trans)); + nft_trans_destroy(trans); +@@ -11368,8 +11520,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + case NFT_MSG_DELCHAIN: + case NFT_MSG_DESTROYCHAIN: + if (nft_trans_chain_update(trans)) { +- list_splice(&nft_trans_chain_hooks(trans), +- &nft_trans_basechain(trans)->hook_list); ++ nft_trans_delhook_abort(&nft_trans_chain_hooks(trans)); + } else { + nft_use_inc_restore(&table->use); + nft_clear(trans->net, nft_trans_chain(trans)); +@@ -11483,8 +11634,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + case NFT_MSG_DELFLOWTABLE: + case NFT_MSG_DESTROYFLOWTABLE: + if (nft_trans_flowtable_update(trans)) { +- list_splice(&nft_trans_flowtable_hooks(trans), +- &nft_trans_flowtable(trans)->hook_list); ++ nft_trans_delhook_abort(&nft_trans_flowtable_hooks(trans)); + } else { + nft_use_inc_restore(&table->use); + nft_clear(trans->net, nft_trans_flowtable(trans)); diff --git a/1150-netfilter-nf-conntrack-expect-restore-helper-propagation-via-expectation.patch b/1150-netfilter-nf-conntrack-expect-restore-helper-propagation-via-expectation.patch new file mode 100644 index 000000000..642e727c7 --- /dev/null +++ b/1150-netfilter-nf-conntrack-expect-restore-helper-propagation-via-expectation.patch @@ -0,0 +1,260 @@ +From 5a3930625f5b94ea1c8906d5f8ec7a6c34133018 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:24:34 +0200 +Subject: [PATCH] netfilter: nf_conntrack_expect: restore helper propagation + via expectation + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit dcb0f9aefdd6 + +commit dcb0f9aefdd604d36710fda53c25bd7cf4a3e37a +Author: Pablo Neira Ayuso +Date: Thu May 7 13:00:28 2026 +0200 + + netfilter: nf_conntrack_expect: restore helper propagation via expectation + + 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 + Tested-by: Ilya Maximets + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h +index e9a8350..80f50fd 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 4f39bf7..75e53fd 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 963f9ac..02a73b2 100644 +--- a/net/netfilter/nf_conntrack_core.c ++++ b/net/netfilter/nf_conntrack_core.c +@@ -1819,14 +1819,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 db28801..1bb5bf8 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 fbf69d4..8a14404 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 a715304..b594cd2 100644 +--- a/net/netfilter/nf_conntrack_helper.c ++++ b/net/netfilter/nf_conntrack_helper.c +@@ -400,6 +400,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 dcef3ef..7e71cbc 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -2655,6 +2655,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); + +@@ -2881,6 +2882,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; +@@ -2896,8 +2898,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); + +@@ -3536,6 +3548,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) + { +@@ -3589,6 +3602,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; +@@ -3644,7 +3658,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 b31f31e..25ab92e 100644 +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -1378,7 +1378,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); diff --git a/1151-netfilter-ctnetlink-check-tuple-and-mask-in-expectations-created-via-nfqueue.patch b/1151-netfilter-ctnetlink-check-tuple-and-mask-in-expectations-created-via-nfqueue.patch new file mode 100644 index 000000000..cf4c74620 --- /dev/null +++ b/1151-netfilter-ctnetlink-check-tuple-and-mask-in-expectations-created-via-nfqueue.patch @@ -0,0 +1,37 @@ +From b837d5c40d7bd2c2bb19b0ac2bcf96a81a130009 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:24:36 +0200 +Subject: [PATCH] netfilter: ctnetlink: check tuple and mask in expectations + created via nfqueue + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit d8ef54c83ad7 + +commit d8ef54c83ad70b81735b506431affadd2f720aa1 +Author: Pablo Neira Ayuso +Date: Thu May 7 23:57:55 2026 +0200 + + netfilter: ctnetlink: check tuple and mask in expectations created via nfqueue + + Ensure the expectation tuple and mask attributes are present in netlink + message, otherwise null-ptr-deref is possible. + + Fixes: bd0779370588 ("netfilter: nfnetlink_queue: allow to attach expectations to conntracks") + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index 7e71cbc..caf0506 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -2893,6 +2893,9 @@ ctnetlink_glue_attach_expect(const struct nlattr *attr, struct nf_conn *ct, + if (err < 0) + return err; + ++ if (!cda[CTA_EXPECT_TUPLE] || !cda[CTA_EXPECT_MASK]) ++ return -EINVAL; ++ + err = ctnetlink_glue_exp_parse((const struct nlattr * const *)cda, + ct, &tuple, &mask); + if (err < 0) diff --git a/1152-netfilter-nf-conntrack-sip-get-helper-before-allocating-expectation.patch b/1152-netfilter-nf-conntrack-sip-get-helper-before-allocating-expectation.patch new file mode 100644 index 000000000..9dbbfde5a --- /dev/null +++ b/1152-netfilter-nf-conntrack-sip-get-helper-before-allocating-expectation.patch @@ -0,0 +1,56 @@ +From f2a8d75fdf87f1a3075b27c58f10f0ac902475bc Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:24:37 +0200 +Subject: [PATCH] netfilter: nf_conntrack_sip: get helper before allocating + expectation + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit eb6317739b1e + +commit eb6317739b1ea3ab28791e1f91b24781905fa815 +Author: Li Xiasong +Date: Thu May 7 22:04:22 2026 +0800 + + netfilter: nf_conntrack_sip: get helper before allocating expectation + + process_register_request() allocates an expectation and then checks + whether a conntrack helper is available. If helper lookup fails, the + function returns early and the allocated expectation is left behind. + + Reorder the code to fetch and validate helper before calling + nf_ct_expect_alloc(). This keeps the logic simpler and removes the leak + path while preserving existing behavior. + + Fixes: e14575fa7529 ("netfilter: nf_conntrack: use rcu accessors where needed") + Cc: stable@vger.kernel.org + Signed-off-by: Li Xiasong + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c +index 25ab92e..d5bc99b 100644 +--- a/net/netfilter/nf_conntrack_sip.c ++++ b/net/netfilter/nf_conntrack_sip.c +@@ -1361,6 +1361,10 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff, + goto store_cseq; + } + ++ helper = rcu_dereference(nfct_help(ct)->helper); ++ if (!helper) ++ return NF_DROP; ++ + exp = nf_ct_expect_alloc(ct); + if (!exp) { + nf_ct_helper_log(skb, ct, "cannot alloc expectation"); +@@ -1371,10 +1375,6 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff, + if (sip_direct_signalling) + saddr = &ct->tuplehash[!dir].tuple.src.u3; + +- helper = rcu_dereference(nfct_help(ct)->helper); +- if (!helper) +- return NF_DROP; +- + nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct), + saddr, &daddr, proto, NULL, &port); + exp->timeout.expires = sip_timeout * HZ; diff --git a/1153-netfilter-nft-ct-fix-missing-expect-put-in-obj-eval.patch b/1153-netfilter-nft-ct-fix-missing-expect-put-in-obj-eval.patch new file mode 100644 index 000000000..901639790 --- /dev/null +++ b/1153-netfilter-nft-ct-fix-missing-expect-put-in-obj-eval.patch @@ -0,0 +1,39 @@ +From 3bbba78afad15b303372a701c571840395dea7a1 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:24:38 +0200 +Subject: [PATCH] netfilter: nft_ct: fix missing expect put in obj eval + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 19f94b6fee75 + +commit 19f94b6fee75b3ef7fbc06f3745b9a771a8a19a4 +Author: Li Xiasong +Date: Thu May 7 22:04:23 2026 +0800 + + netfilter: nft_ct: fix missing expect put in obj eval + + nft_ct_expect_obj_eval() allocates an expectation and may call + nf_ct_expect_related(), but never drops its local reference. + + Add nf_ct_expect_put(exp) before return to balance allocation. + + Fixes: 857b46027d6f ("netfilter: nft_ct: add ct expectations support") + Cc: stable@vger.kernel.org + Signed-off-by: Li Xiasong + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c +index d0090d0..d877767 100644 +--- a/net/netfilter/nft_ct.c ++++ b/net/netfilter/nft_ct.c +@@ -1378,6 +1378,8 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, + + if (nf_ct_expect_related(exp, 0) != 0) + regs->verdict.code = NF_DROP; ++ ++ nf_ct_expect_put(exp); + } + + static const struct nla_policy nft_ct_expect_policy[NFTA_CT_EXPECT_MAX + 1] = { diff --git a/1154-netfilter-nf-conntrack-helper-fix-possible-null-deref-during-error-log.patch b/1154-netfilter-nf-conntrack-helper-fix-possible-null-deref-during-error-log.patch new file mode 100644 index 000000000..48c5526ea --- /dev/null +++ b/1154-netfilter-nf-conntrack-helper-fix-possible-null-deref-during-error-log.patch @@ -0,0 +1,65 @@ +From 57807b90c78a36d8729fc8d8a04ce31bd4f3e4ff Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 20 May 2026 11:48:56 +0200 +Subject: [PATCH] netfilter: nf_conntrack_helper: fix possible null deref + during error log + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 1afc25ae7528 + +commit 1afc25ae75288b3ce59e9e5a4b448bd354c9e565 +Author: Florian Westphal +Date: Sat May 9 10:27:06 2026 +0200 + + netfilter: nf_conntrack_helper: fix possible null deref during error log + + Reported by sashiko: there is a small race window. + + If a helper module is unloaded or a userspace-defined helper is + removed, nf_conntrack_helper_unregister() sets ->helper to NULL. + + Handle this safely. This needs a second patch to close related + race during nf_conntrack_helper_unregister(). + + Fixes: b20ab9cc63ca ("netfilter: nf_ct_helper: better logging for dropped packets") + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c +index b594cd2..17e971b 100644 +--- a/net/netfilter/nf_conntrack_helper.c ++++ b/net/netfilter/nf_conntrack_helper.c +@@ -321,8 +321,8 @@ __printf(3, 4) + void nf_ct_helper_log(struct sk_buff *skb, const struct nf_conn *ct, + const char *fmt, ...) + { ++ const char *helper_name = "(null)"; + const struct nf_conn_help *help; +- const struct nf_conntrack_helper *helper; + struct va_format vaf; + va_list args; + +@@ -331,14 +331,17 @@ void nf_ct_helper_log(struct sk_buff *skb, const struct nf_conn *ct, + vaf.fmt = fmt; + vaf.va = &args; + +- /* Called from the helper function, this call never fails */ + help = nfct_help(ct); ++ if (help) { ++ const struct nf_conntrack_helper *helper; + +- /* rcu_read_lock()ed by nf_hook_thresh */ +- helper = rcu_dereference(help->helper); ++ helper = rcu_dereference(help->helper); ++ if (helper) ++ helper_name = helper->name; ++ } + + nf_log_packet(nf_ct_net(ct), nf_ct_l3num(ct), 0, skb, NULL, NULL, NULL, +- "nf_ct_%s: dropping packet: %pV ", helper->name, &vaf); ++ "helper %s dropping packet: %pV ", helper_name, &vaf); + + va_end(args); + } diff --git a/1155-netfilter-ip6t-hbh-reject-oversized-option-lists.patch b/1155-netfilter-ip6t-hbh-reject-oversized-option-lists.patch new file mode 100644 index 000000000..41341060e --- /dev/null +++ b/1155-netfilter-ip6t-hbh-reject-oversized-option-lists.patch @@ -0,0 +1,56 @@ +From c3badd18c01702498ec26b9c13396a7fc2e8fc11 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 20 May 2026 11:48:58 +0200 +Subject: [PATCH] netfilter: ip6t_hbh: reject oversized option lists + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 4322dcde6b41 + +commit 4322dcde6b4173c2d8e8e6118ed290794263bcc8 +Author: Zhengchuan Liang +Date: Wed May 13 15:57:17 2026 +0800 + + netfilter: ip6t_hbh: reject oversized option lists + + struct ip6t_opts stores at most IP6T_OPTS_OPTSNR option descriptors, + but hbh_mt6_check() does not reject larger optsnr values supplied from + userspace. + + Validate optsnr in the rule setup path so only match data that fits the + fixed-size opts array can be installed. This follows the existing xtables + pattern of rejecting invalid user-provided counts in checkentry() and + keeps the packet matching path unchanged. + + `struct ip6t_opts` has a fixed `opts[IP6T_OPTS_OPTSNR]` array, + where `IP6T_OPTS_OPTSNR` is 16, then off-by-one array access is possible: + + [ 137.924693][ T8692] UBSAN: array-index-out-of-bounds in ../net/ipv6/netfilter/ip6t_hbh.c:110:29 + [ 137.926167][ T8692] index 16 is out of range for type '__u16 [16]' + + Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") + Cc: stable@kernel.org + Reported-by: Yuan Tan + Reported-by: Yifan Wu + Reported-by: Juefei Pu + Reported-by: Xin Liu + Signed-off-by: Zhengchuan Liang + Signed-off-by: Ren Wei + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/net/ipv6/netfilter/ip6t_hbh.c b/net/ipv6/netfilter/ip6t_hbh.c +index e7a3fb9..450dd53 100644 +--- a/net/ipv6/netfilter/ip6t_hbh.c ++++ b/net/ipv6/netfilter/ip6t_hbh.c +@@ -168,6 +168,10 @@ static int hbh_mt6_check(const struct xt_mtchk_param *par) + pr_debug("unknown flags %X\n", optsinfo->invflags); + return -EINVAL; + } ++ if (optsinfo->optsnr > IP6T_OPTS_OPTSNR) { ++ pr_debug("too many supported opts specified\n"); ++ return -EINVAL; ++ } + + if (optsinfo->flags & IP6T_OPTS_NSTRICT) { + pr_debug("Not strict - not implemented"); diff --git a/1156-netfilter-br-netfilter-reallocate-headroom-if-necessary-in-neigh-hh-bridge.patch b/1156-netfilter-br-netfilter-reallocate-headroom-if-necessary-in-neigh-hh-bridge.patch new file mode 100644 index 000000000..408226e81 --- /dev/null +++ b/1156-netfilter-br-netfilter-reallocate-headroom-if-necessary-in-neigh-hh-bridge.patch @@ -0,0 +1,141 @@ +From 5c589ceb09725bf23a6c443837049ee5212c8bb2 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 20 May 2026 11:48:59 +0200 +Subject: [PATCH] netfilter: br_netfilter: Reallocate headroom if necessary in + neigh_hh_bridge() + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit b2870fc21601 + +commit b2870fc21601db9133bc70c48c603b487614fa3b +Author: Lorenzo Bianconi +Date: Thu May 14 16:46:38 2026 +0200 + + netfilter: br_netfilter: Reallocate headroom if necessary in neigh_hh_bridge() + + neigh_hh_bridge() assumes the skb always has sufficient headroom to copy + the aligned L2 header. This assumption can trigger the crash reported + below using the following netfilter setup: + + $modprobe br_netfilter + $sysctl -w net.bridge.bridge-nf-call-iptables=1 + + $root@OpenWrt:~# nft list ruleset + table ip nat { + chain prerouting { + type nat hook prerouting priority dstnat; policy accept; + ip daddr 192.168.83.123 dnat to 192.168.83.120 + } + } + + - iperf3 client (192.168.83.119) --> bridge (192.168.83.118) --> iperf3 server (192.168.83.120) + + the iperf3 client is sending packet for 192.168.83.123 to the bridge device. + + [ 1579.036575] Unable to handle kernel write to read-only memory at virtual address ffffff8004d76ffe + [ 1579.045482] Mem abort info: + [ 1579.048273] ESR = 0x000000009600004f + [ 1579.052024] EC = 0x25: DABT (current EL), IL = 32 bits + [ 1579.057363] SET = 0, FnV = 0 + [ 1579.060417] EA = 0, S1PTW = 0 + [ 1579.063550] FSC = 0x0f: level 3 permission fault + [ 1579.068345] Data abort info: + [ 1579.071224] ISV = 0, ISS = 0x0000004f, ISS2 = 0x00000000 + [ 1579.076720] CM = 0, WnR = 1, TnD = 0, TagAccess = 0 + [ 1579.081770] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 + [ 1579.087092] swapper pgtable: 4k pages, 39-bit VAs, pgdp=0000000080dc4000 + [ 1579.093794] [ffffff8004d76ffe] pgd=180000009ffff003, p4d=180000009ffff003, pud=180000009ffff003, pmd=180000009ffe3003, pte=0060000084d76787 + [ 1579.106343] Internal error: Oops: 000000009600004f [#1] SMP + [ 1579.193824] CPU: 0 UID: 0 PID: 235 Comm: napi/qdma_eth-3 Tainted: G O 6.12.57 #0 + [ 1579.202614] Tainted: [O]=OOT_MODULE + [ 1579.206102] Hardware name: Airoha AN7581 Evaluation Board (DT) + [ 1579.211929] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--) + [ 1579.218889] pc : br_nf_pre_routing_finish_bridge+0x1ac/0xcc8 [br_netfilter] + [ 1579.225859] lr : br_nf_pre_routing_finish_bridge+0x18c/0xcc8 [br_netfilter] + [ 1579.232822] sp : ffffffc0817cba20 + [ 1579.236128] x29: ffffffc0817cba20 x28: 0000000000000000 x27: ffffff8002b89000 + [ 1579.243273] x26: ffffff8004d7700e x25: 0000000000000008 x24: 0000000000000000 + [ 1579.250416] x23: ffffffc08179d4c0 x22: 0000000000000000 x21: ffffffc08179d4c0 + [ 1579.257561] x20: ffffff8004d9b800 x19: ffffff8015010000 x18: 0000000000000014 + [ 1579.264704] x17: ffffffbf9e930000 x16: ffffffc0817c8000 x15: 0000000000000070 + [ 1579.271848] x14: 0000000000000080 x13: 0000000000000001 x12: 0000000000000000 + [ 1579.278993] x11: ffffffc0798caae0 x10: ffffff8014db6fd8 x9 : 0000000000000000 + [ 1579.286136] x8 : 0000000000000003 x7 : ffffffc08171f628 x6 : 000000001a3b83d3 + [ 1579.293281] x5 : 0000000000000000 x4 : 1beb76f22fee0000 x3 : ffffff8004d7700e + [ 1579.300425] x2 : 0000000000000000 x1 : ffffff8004d9b8bc x0 : ffffff80026ed000 + [ 1579.307570] Call trace: + [ 1579.310018] br_nf_pre_routing_finish_bridge+0x1ac/0xcc8 [br_netfilter] + [ 1579.316632] br_nf_hook_thresh+0xd4/0x14bc [br_netfilter] + [ 1579.322032] br_nf_hook_thresh+0x250/0x14bc [br_netfilter] + [ 1579.327517] br_nf_hook_thresh+0x76c/0x14bc [br_netfilter] + [ 1579.333003] br_handle_frame+0x180/0x480 + [ 1579.336935] __netif_receive_skb_core.constprop.0+0x540/0xf40 + [ 1579.342682] __netif_receive_skb_one_core+0x28/0x50 + [ 1579.347561] process_backlog+0x98/0x1e0 + [ 1579.351398] __napi_poll+0x34/0x1c4 + [ 1579.354887] net_rx_action+0x178/0x330 + [ 1579.358638] handle_softirqs+0x108/0x2d4 + [ 1579.362560] __do_softirq+0x10/0x18 + [ 1579.366051] ____do_softirq+0xc/0x20 + [ 1579.369627] call_on_irq_stack+0x30/0x4c + [ 1579.373550] do_softirq_own_stack+0x18/0x20 + [ 1579.377734] do_softirq+0x4c/0x60 + [ 1579.381050] __local_bh_enable_ip+0x88/0x98 + [ 1579.385234] napi_threaded_poll_loop+0x188/0x21c + [ 1579.389853] napi_threaded_poll+0x70/0x80 + [ 1579.393863] kthread+0xd8/0xdc + [ 1579.396918] ret_from_fork+0x10/0x20 + [ 1579.400499] Code: 88dffc22 3707ffc2 f9406663 f9406684 (f81f0064) + [ 1579.406589] ---[ end trace 0000000000000000 ]--- + [ 1579.411209] Kernel panic - not syncing: Oops: Fatal exception in interrupt + [ 1579.418083] SMP: stopping secondary CPUs + [ 1579.422012] Kernel Offset: disabled + + Fix the issue reallocating the skb headroom if necessary in neigh_hh_bridge routine. + + Fixes: e179e6322ac33 ("netfilter: bridge-netfilter: Fix MAC header handling with IP DNAT") + Reviewed-by: Ido Schimmel + Signed-off-by: Lorenzo Bianconi + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/neighbour.h b/include/net/neighbour.h +index a44f262..81807ec 100644 +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -475,11 +475,15 @@ static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb) + #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) + static inline int neigh_hh_bridge(struct hh_cache *hh, struct sk_buff *skb) + { +- unsigned int seq, hh_alen; ++ unsigned int seq, hh_alen = HH_DATA_ALIGN(ETH_HLEN); ++ int err; ++ ++ err = skb_cow_head(skb, hh_alen); ++ if (err) ++ return err; + + do { + seq = read_seqbegin(&hh->hh_lock); +- hh_alen = HH_DATA_ALIGN(ETH_HLEN); + memcpy(skb->data - hh_alen, hh->hh_data, ETH_ALEN + hh_alen - ETH_HLEN); + } while (read_seqretry(&hh->hh_lock, seq)); + return 0; +diff --git a/net/bridge/br_netfilter_hooks.c b/net/bridge/br_netfilter_hooks.c +index 1ba0780..1e5ac85 100644 +--- a/net/bridge/br_netfilter_hooks.c ++++ b/net/bridge/br_netfilter_hooks.c +@@ -296,7 +296,11 @@ int br_nf_pre_routing_finish_bridge(struct net *net, struct sock *sk, struct sk_ + goto free_skb; + } + +- neigh_hh_bridge(&neigh->hh, skb); ++ if (neigh_hh_bridge(&neigh->hh, skb)) { ++ neigh_release(neigh); ++ goto free_skb; ++ } ++ + skb->dev = br_indev; + + ret = br_handle_frame_finish(net, sk, skb); diff --git a/1157-netfilter-nf-queue-hold-bridge-skb-dev-while-queued.patch b/1157-netfilter-nf-queue-hold-bridge-skb-dev-while-queued.patch new file mode 100644 index 000000000..fff6410f6 --- /dev/null +++ b/1157-netfilter-nf-queue-hold-bridge-skb-dev-while-queued.patch @@ -0,0 +1,97 @@ +From a5a3e9f4eb2479abc1e49647644becd5106e74ab Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 20 May 2026 11:49:00 +0200 +Subject: [PATCH] netfilter: nf_queue: hold bridge skb->dev while queued + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit e196115ec330 + +commit e196115ec330a18de415bdb9f5071aa9f08e53ce +Author: Haoze Xie +Date: Fri May 15 11:19:02 2026 +0800 + + netfilter: nf_queue: hold bridge skb->dev while queued + + br_pass_frame_up() rewrites skb->dev from the ingress port to the bridge + master before queueing bridge LOCAL_IN packets. NFQUEUE only holds + references on state.in/out and bridge physdevs, so a queued bridge + packet can retain a freed bridge master in skb->dev until reinjection. + + When the verdict is reinjected later, br_netif_receive_skb() re-enters + the receive path with skb->dev still pointing at the freed bridge master, + triggering a use-after-free. + + Store skb->dev in the queue entry, hold a reference on it for the queue + lifetime, and use the saved device when dropping queued packets during + NETDEV_DOWN handling. + + Fixes: ac2863445686 ("netfilter: bridge: add nf_afinfo to enable queuing to userspace") + Cc: stable@kernel.org + Reported-by: Yuan Tan + Reported-by: Yifan Wu + Reported-by: Juefei Pu + Reported-by: Xin Liu + Signed-off-by: Haoze Xie + Signed-off-by: Ren Wei + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h +index 4aeffdd..b880f18 100644 +--- a/include/net/netfilter/nf_queue.h ++++ b/include/net/netfilter/nf_queue.h +@@ -12,6 +12,7 @@ + struct nf_queue_entry { + struct list_head list; + struct sk_buff *skb; ++ struct net_device *skb_dev; + unsigned int id; + unsigned int hook_index; /* index in hook_entries->hook[] */ + #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) +diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c +index 7f12e56..dd416c8 100644 +--- a/net/netfilter/nf_queue.c ++++ b/net/netfilter/nf_queue.c +@@ -60,6 +60,7 @@ static void nf_queue_entry_release_refs(struct nf_queue_entry *entry) + struct nf_hook_state *state = &entry->state; + + /* Release those devices we held, or Alexey will kill me. */ ++ dev_put(entry->skb_dev); + dev_put(state->in); + dev_put(state->out); + if (state->sk) +@@ -101,6 +102,7 @@ bool nf_queue_entry_get_refs(struct nf_queue_entry *entry) + if (state->sk && !refcount_inc_not_zero(&state->sk->sk_refcnt)) + return false; + ++ dev_hold(entry->skb_dev); + dev_hold(state->in); + dev_hold(state->out); + +@@ -201,11 +203,11 @@ static int __nf_queue(struct sk_buff *skb, const struct nf_hook_state *state, + + *entry = (struct nf_queue_entry) { + .skb = skb, ++ .skb_dev = skb->dev, + .state = *state, + .hook_index = index, + .size = sizeof(*entry) + route_key_size, + }; +- + __nf_queue_entry_init_physdevs(entry); + + if (!nf_queue_entry_get_refs(entry)) { +diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c +index 0bb1656..64496e7 100644 +--- a/net/netfilter/nfnetlink_queue.c ++++ b/net/netfilter/nfnetlink_queue.c +@@ -1132,6 +1132,8 @@ dev_cmp(struct nf_queue_entry *entry, unsigned long ifindex) + if (physinif == ifindex || physoutif == ifindex) + return 1; + #endif ++ if (entry->skb_dev && entry->skb_dev->ifindex == ifindex) ++ return 1; + if (entry->state.in) + if (entry->state.in->ifindex == ifindex) + return 1; diff --git a/1158-netfilter-conntrack-tcp-do-not-force-close-on-invalid-seq-rst-without-direction-.patch b/1158-netfilter-conntrack-tcp-do-not-force-close-on-invalid-seq-rst-without-direction-.patch new file mode 100644 index 000000000..eeb6ecd8b --- /dev/null +++ b/1158-netfilter-conntrack-tcp-do-not-force-close-on-invalid-seq-rst-without-direction-.patch @@ -0,0 +1,55 @@ +From 4a13cd8d6754b3c1c69f9c883caa7ee03674a53b Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 26 May 2026 13:22:20 +0200 +Subject: [PATCH] netfilter: conntrack: tcp: do not force CLOSE on invalid-seq + RST without direction check + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit bed6e04be8e6 + +commit bed6e04be8e6b9133d8b16d5a42d0e0ce674fa9a +Author: Hamza Mahfooz +Date: Mon May 11 10:43:14 2026 -0400 + + netfilter: conntrack: tcp: do not force CLOSE on invalid-seq RST without direction check + + An unintended behavior in the TCP conntrack state machine allows a + connection to be forced into the CLOSE state using an RST packet with an + invalid sequence number. + + Specifically, after a SYN packet is observed, an RST with an invalid SEQ + can transition the conntrack entry to TCP_CONNTRACK_CLOSE, regardless of + whether the RST corresponds to the expected reply direction. The relevant + code path assumes the RST is a response to an outgoing SYN, but does not + validate packet direction or ensure that a matching SYN was actually sent + in the opposite direction. + + As a result, a crafted packet sequence consisting of a SYN followed by an + invalid-sequence RST can prematurely terminate an active NAT entry. This + makes connection teardown easier than intended. + + So, tighten the state transition logic to ensure that RST-triggered + CLOSE transitions only occur when the RST is a valid response to a + previously observed SYN in the correct direction. + + Cc: stable@vger.kernel.org + Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.") + Signed-off-by: Hamza Mahfooz + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c +index b67426c..e99ab1e 100644 +--- a/net/netfilter/nf_conntrack_proto_tcp.c ++++ b/net/netfilter/nf_conntrack_proto_tcp.c +@@ -1221,7 +1221,8 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct, + new_state = old_state; + } + if (((test_bit(IPS_SEEN_REPLY_BIT, &ct->status) +- && ct->proto.tcp.last_index == TCP_SYN_SET) ++ && ct->proto.tcp.last_index == TCP_SYN_SET ++ && ct->proto.tcp.last_dir != dir) + || (!test_bit(IPS_ASSURED_BIT, &ct->status) + && ct->proto.tcp.last_index == TCP_ACK_SET)) + && ntohl(th->ack_seq) == ct->proto.tcp.last_end) { diff --git a/1159-netfilter-synproxy-refresh-tcphdr-after-skb-ensure-writable.patch b/1159-netfilter-synproxy-refresh-tcphdr-after-skb-ensure-writable.patch new file mode 100644 index 000000000..1839f07da --- /dev/null +++ b/1159-netfilter-synproxy-refresh-tcphdr-after-skb-ensure-writable.patch @@ -0,0 +1,68 @@ +From 35ea4ea680d6794f01d54eec2b1e507a45f422cf Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 26 May 2026 13:22:20 +0200 +Subject: [PATCH] netfilter: synproxy: refresh tcphdr after skb_ensure_writable + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 92170e6afe92 + +commit 92170e6afe927ab2792a3f71902845789c8e31b1 +Author: Chris Mason +Date: Tue May 19 12:36:14 2026 -0700 + + netfilter: synproxy: refresh tcphdr after skb_ensure_writable + + synproxy_tstamp_adjust() rewrites the TCP timestamp option in place + and then patches the TCP checksum via inet_proto_csum_replace4() on + the caller-supplied tcphdr pointer. Both ipv4_synproxy_hook() and + ipv6_synproxy_hook() obtain that pointer with skb_header_pointer() + before calling in, so it may either alias skb->head directly or + point at the caller's on-stack _tcph buffer. + + Between obtaining the pointer and using it, the function calls + skb_ensure_writable(skb, optend), which on a cloned or non-linear + skb invokes pskb_expand_head() and frees the old skb->head. After + that point the cached th is stale: + + caller (ipv[46]_synproxy_hook) + th = skb_header_pointer(skb, ..., &_tcph) + synproxy_tstamp_adjust(skb, protoff, th, ...) + skb_ensure_writable(skb, optend) + pskb_expand_head() /* kfree(old skb->head) */ + ... + inet_proto_csum_replace4(&th->check, ...) + /* writes into freed head, or + into the caller's stack copy + leaving the on-wire checksum + stale */ + + The option bytes are written through skb->data and are fine; only + the checksum update goes through th and so lands in the wrong + place. The result is either a write into freed slab memory or a + packet leaving with a checksum that does not match its payload. + + Fix by re-deriving th from skb->data + protoff immediately after + skb_ensure_writable() succeeds, so the subsequent checksum update + targets the linear, writable header. + + Fixes: 48b1de4c110a ("netfilter: add SYNPROXY core/target") + Assisted-by: kres (claude-opus-4-7) + Signed-off-by: Chris Mason + Reviewed-by: Fernando Fernandez Mancera + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nf_synproxy_core.c b/net/netfilter/nf_synproxy_core.c +index 3fa3f5d..6a851ac 100644 +--- a/net/netfilter/nf_synproxy_core.c ++++ b/net/netfilter/nf_synproxy_core.c +@@ -199,6 +199,8 @@ synproxy_tstamp_adjust(struct sk_buff *skb, unsigned int protoff, + if (skb_ensure_writable(skb, optend)) + return 0; + ++ th = (struct tcphdr *)(skb->data + protoff); ++ + while (optoff < optend) { + unsigned char *op = skb->data + optoff; + diff --git a/1160-netfilter-nf-conntrack-gre-fix-gre-keymap-list-corruption.patch b/1160-netfilter-nf-conntrack-gre-fix-gre-keymap-list-corruption.patch new file mode 100644 index 000000000..e5c7d4079 --- /dev/null +++ b/1160-netfilter-nf-conntrack-gre-fix-gre-keymap-list-corruption.patch @@ -0,0 +1,246 @@ +From 8fa68cde22f8c43d3f1bc0d4c7bf7c3fd8492c2e Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 26 May 2026 13:22:21 +0200 +Subject: [PATCH] netfilter: nf_conntrack_gre: fix gre keymap list corruption + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 47980b6dbf83 + +Conflict is due to missing kmalloc_obj() conversion in cs-10. + +commit 47980b6dbf83961eec1c1363ea986e9c06ff8054 +Author: Florian Westphal +Date: Thu May 14 14:21:57 2026 +0200 + + netfilter: nf_conntrack_gre: fix gre keymap list corruption + + Quoting reporter: + A race between GRE keymap insertion and destruction can corrupt the + kernel list or use a freed object. `nf_ct_gre_keymap_add()` publishes a + new keymap pointer before the embedded `list_head` is linked, while + `nf_ct_gre_keymap_destroy()` can concurrently delete and free that + same object. An unprivileged user can reach this through the PPTP + conntrack helper by racing PPTP control messages or helper teardown, + leading to KASAN-detectable list corruption/UAF in kernel context. + + ## Root Cause Analysis + `exp_gre()` installs GRE expectations for a PPTP control flow and then + adds two GRE keymap entries [..] + + The add path publishes `ct_pptp_info->keymap[dir]` before linking the + embedded list node [..] + Concurrent teardown deletes that partially initialized object. + + Make add/destroy symmetric: install both, destroy both while under lock. + + Furthermore, we should refuse to publish a new mapping in case ct is going + away, else we may leak the allocation. + + The "retrans" detection is strange: existing mapping is checked for key + equality with the new mapping, then for "is on the list" via list walk. + + But I can't see how an existing keymap entry can be NOT on list. + + Change this to only check if we're asked to map same tuple again -- if so, + skip re-install, else signal failure. + + Last, add a bug trap for the keymap list; it has to be empty when namespace + is going away. + + Reported-by: Leo Lin + Signed-off-by: Florian Westphal + +Assisted-by: Patchpal AI +Signed-off-by: Florian Westphal + +diff --git a/include/linux/netfilter/nf_conntrack_proto_gre.h b/include/linux/netfilter/nf_conntrack_proto_gre.h +index 34ce5d2..4014f78 100644 +--- a/include/linux/netfilter/nf_conntrack_proto_gre.h ++++ b/include/linux/netfilter/nf_conntrack_proto_gre.h +@@ -21,9 +21,10 @@ struct nf_ct_gre_keymap { + struct rcu_head rcu; + }; + +-/* add new tuple->key_reply pair to keymap */ +-int nf_ct_gre_keymap_add(struct nf_conn *ct, enum ip_conntrack_dir dir, +- struct nf_conntrack_tuple *t); ++/* add tuple->key_reply pairs to keymap */ ++bool nf_ct_gre_keymap_add(struct nf_conn *ct, ++ const struct nf_conntrack_tuple *orig, ++ const struct nf_conntrack_tuple *repl); + + /* delete keymap entries */ + void nf_ct_gre_keymap_destroy(struct nf_conn *ct); +diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c +index 02a73b2..04f7a13 100644 +--- a/net/netfilter/nf_conntrack_core.c ++++ b/net/netfilter/nf_conntrack_core.c +@@ -576,6 +576,13 @@ static void destroy_gre_conntrack(struct nf_conn *ct) + #endif + } + ++static void warn_on_keymap_list_leak(const struct net *net) ++{ ++#ifdef CONFIG_NF_CT_PROTO_GRE ++ WARN_ON_ONCE(!list_empty(&net->ct.nf_ct_proto.gre.keymap_list)); ++#endif ++} ++ + void nf_ct_destroy(struct nf_conntrack *nfct) + { + struct nf_conn *ct = (struct nf_conn *)nfct; +@@ -2525,6 +2532,7 @@ void nf_conntrack_cleanup_net_list(struct list_head *net_exit_list) + } + + list_for_each_entry(net, net_exit_list, exit_list) { ++ warn_on_keymap_list_leak(net); + nf_conntrack_ecache_pernet_fini(net); + nf_conntrack_expect_pernet_fini(net); + free_percpu(net->ct.stat); +diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c +index 4c67963..dc23e41 100644 +--- a/net/netfilter/nf_conntrack_pptp.c ++++ b/net/netfilter/nf_conntrack_pptp.c +@@ -225,13 +225,9 @@ static int exp_gre(struct nf_conn *ct, __be16 callid, __be16 peer_callid) + if (nf_ct_expect_related(exp_reply, 0) != 0) + goto out_unexpect_orig; + +- /* Add GRE keymap entries */ +- if (nf_ct_gre_keymap_add(ct, IP_CT_DIR_ORIGINAL, &exp_orig->tuple) != 0) ++ if (!nf_ct_gre_keymap_add(ct, &exp_orig->tuple, ++ &exp_reply->tuple)) + goto out_unexpect_both; +- if (nf_ct_gre_keymap_add(ct, IP_CT_DIR_REPLY, &exp_reply->tuple) != 0) { +- nf_ct_gre_keymap_destroy(ct); +- goto out_unexpect_both; +- } + ret = 0; + + out_put_both: +diff --git a/net/netfilter/nf_conntrack_proto_gre.c b/net/netfilter/nf_conntrack_proto_gre.c +index af369e6..d637080 100644 +--- a/net/netfilter/nf_conntrack_proto_gre.c ++++ b/net/netfilter/nf_conntrack_proto_gre.c +@@ -85,41 +85,97 @@ static __be16 gre_keymap_lookup(struct net *net, struct nf_conntrack_tuple *t) + return key; + } + +-/* add a single keymap entry, associate with specified master ct */ +-int nf_ct_gre_keymap_add(struct nf_conn *ct, enum ip_conntrack_dir dir, +- struct nf_conntrack_tuple *t) ++enum nf_ct_gre_km_act { ++ NF_CT_GRE_KM_NEW, ++ NF_CT_GRE_KM_BAD, ++ NF_CT_GRE_KM_DUP ++}; ++ ++static enum nf_ct_gre_km_act ++nf_ct_gre_km_acceptable(const struct nf_ct_pptp_master *ct_pptp_info, ++ const struct nf_conntrack_tuple *orig, ++ const struct nf_conntrack_tuple *repl) ++{ ++ struct nf_ct_gre_keymap *km_orig, *km_repl; ++ ++ lockdep_assert_held(&keymap_lock); ++ ++ km_orig = ct_pptp_info->keymap[IP_CT_DIR_ORIGINAL]; ++ km_repl = ct_pptp_info->keymap[IP_CT_DIR_REPLY]; ++ ++ if (km_orig && km_repl) { ++ if (!gre_key_cmpfn(km_orig, orig)) ++ return NF_CT_GRE_KM_BAD; ++ ++ if (!gre_key_cmpfn(km_repl, repl)) ++ return NF_CT_GRE_KM_BAD; ++ ++ return NF_CT_GRE_KM_DUP; ++ } ++ ++ DEBUG_NET_WARN_ON_ONCE(km_orig); ++ DEBUG_NET_WARN_ON_ONCE(km_repl); ++ return NF_CT_GRE_KM_NEW; ++} ++ ++/* add keymap entries, associate with specified master ct */ ++bool nf_ct_gre_keymap_add(struct nf_conn *ct, ++ const struct nf_conntrack_tuple *orig, ++ const struct nf_conntrack_tuple *repl) + { + struct net *net = nf_ct_net(ct); + struct nf_gre_net *net_gre = gre_pernet(net); + struct nf_ct_pptp_master *ct_pptp_info = nfct_help_data(ct); +- struct nf_ct_gre_keymap **kmp, *km; +- +- kmp = &ct_pptp_info->keymap[dir]; +- if (*kmp) { +- /* check whether it's a retransmission */ +- list_for_each_entry_rcu(km, &net_gre->keymap_list, list) { +- if (gre_key_cmpfn(km, t) && km == *kmp) +- return 0; +- } +- pr_debug("trying to override keymap_%s for ct %p\n", +- dir == IP_CT_DIR_REPLY ? "reply" : "orig", ct); +- return -EEXIST; +- } ++ struct nf_ct_gre_keymap *km_orig, *km_repl; ++ bool ret = false; + +- km = kmalloc(sizeof(*km), GFP_ATOMIC); +- if (!km) +- return -ENOMEM; +- memcpy(&km->tuple, t, sizeof(*t)); +- *kmp = km; ++ km_orig = kmalloc(sizeof(*km_orig), GFP_ATOMIC); ++ if (!km_orig) ++ return false; ++ km_repl = kmalloc(sizeof(*km_repl), GFP_ATOMIC); ++ if (!km_repl) ++ goto km_free; + +- pr_debug("adding new entry %p: ", km); +- nf_ct_dump_tuple(&km->tuple); ++ memcpy(&km_orig->tuple, orig, sizeof(*orig)); ++ memcpy(&km_repl->tuple, repl, sizeof(*repl)); + + spin_lock_bh(&keymap_lock); +- list_add_tail(&km->list, &net_gre->keymap_list); ++ if (nf_ct_is_dying(ct)) ++ goto unlock_free; ++ ++ switch (nf_ct_gre_km_acceptable(ct_pptp_info, orig, repl)) { ++ case NF_CT_GRE_KM_NEW: ++ break; ++ case NF_CT_GRE_KM_DUP: ++ ret = true; ++ goto unlock_free; ++ case NF_CT_GRE_KM_BAD: ++ pr_debug("trying to override keymap for ct %p\n", ct); ++ goto unlock_free; ++ } ++ ++ if (ct_pptp_info->keymap[IP_CT_DIR_ORIGINAL] || ++ ct_pptp_info->keymap[IP_CT_DIR_REPLY]) ++ goto unlock_free; ++ ++ pr_debug("adding new entries %p,%p: ", km_orig, km_repl); ++ nf_ct_dump_tuple(&km_orig->tuple); ++ nf_ct_dump_tuple(&km_repl->tuple); ++ ++ list_add_tail_rcu(&km_orig->list, &net_gre->keymap_list); ++ list_add_tail_rcu(&km_repl->list, &net_gre->keymap_list); ++ ct_pptp_info->keymap[IP_CT_DIR_ORIGINAL] = km_orig; ++ ct_pptp_info->keymap[IP_CT_DIR_REPLY] = km_repl; + spin_unlock_bh(&keymap_lock); + +- return 0; ++ return true; ++ ++unlock_free: ++ spin_unlock_bh(&keymap_lock); ++km_free: ++ kfree(km_orig); ++ kfree(km_repl); ++ return ret; + } + EXPORT_SYMBOL_GPL(nf_ct_gre_keymap_add); + diff --git a/1161-netfilter-disable-payload-mangling-in-userns.patch b/1161-netfilter-disable-payload-mangling-in-userns.patch new file mode 100644 index 000000000..4cda0c63b --- /dev/null +++ b/1161-netfilter-disable-payload-mangling-in-userns.patch @@ -0,0 +1,69 @@ +From 968cc2c96390f06e56ed6a43f935bfebdefed28f Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Sat, 16 May 2026 23:23:21 +0800 +Subject: [PATCH] netfilter: disable payload mangling in userns + +Several parts of network stack rely on iph->ihl validation +done by network stack before PRE_ROUTING. + +Disable this feature for user namespaces for now. + +tcp option handling is likely safe even for LOCAL_IN, so this +this leaves tcp option mangling via nft_exthdr.c as-is. + +I don't think these are the only means to alter packets, but these +appear to be relatively prominent. + +This could be relaxed later. Example: + - allow userns for ingress hook. + - allow userns if base is transport header. + + Also, we should revalidate or restrict generally: + - Don't allow linklayer writes to spill into network header + - restrict ipv4 and ipv6 to 'known safe' writes, e.g. + saddr/daddr/check/tos + +Reported-by: Qi Tang +Reported-by: Tong Liu +Tested-by: Qi Tang +Link: https://lore.kernel.org/netfilter-devel/20260515100411.3141-1-fw@strlen.de/ +Signed-off-by: Florian Westphal + +diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c +index 64496e7..8d4fa10 100644 +--- a/net/netfilter/nfnetlink_queue.c ++++ b/net/netfilter/nfnetlink_queue.c +@@ -1061,6 +1061,9 @@ nfqnl_mangle(void *data, unsigned int data_len, struct nf_queue_entry *e, int di + { + struct sk_buff *nskb; + ++ if (e->state.net->user_ns != &init_user_ns) ++ return -EPERM; ++ + if (diff < 0) { + unsigned int min_len = skb_transport_offset(e->skb); + +@@ -1458,8 +1461,7 @@ static int nfqnl_recv_verdict(struct sk_buff *skb, const struct nfnl_info *info, + if (nfqnl_mangle(nla_data(nfqa[NFQA_PAYLOAD]), + payload_len, entry, diff) < 0) + verdict = NF_DROP; +- +- if (ct && diff) ++ else if (ct && diff) + nfnl_ct->seq_adjust(entry->skb, ct, ctinfo, diff); + } + +diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c +index 7dfc534..0dba42e 100644 +--- a/net/netfilter/nft_payload.c ++++ b/net/netfilter/nft_payload.c +@@ -944,6 +944,9 @@ static int nft_payload_set_init(const struct nft_ctx *ctx, + u32 csum_offset, csum_type = NFT_PAYLOAD_CSUM_NONE; + int err; + ++ if (ctx->net->user_ns != &init_user_ns) ++ return -EPERM; ++ + priv->base = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_BASE])); + priv->offset = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_OFFSET])); + priv->len = ntohl(nla_get_be32(tb[NFTA_PAYLOAD_LEN])); diff --git a/1162-netfilter-synproxy-add-mutex-to-guard-hook-reference-counting.patch b/1162-netfilter-synproxy-add-mutex-to-guard-hook-reference-counting.patch new file mode 100644 index 000000000..d220c9401 --- /dev/null +++ b/1162-netfilter-synproxy-add-mutex-to-guard-hook-reference-counting.patch @@ -0,0 +1,106 @@ +From 640441348258220e78daed40528b85b8afcedab6 Mon Sep 17 00:00:00 2001 +From: Fernando Fernandez Mancera +Date: Tue, 26 May 2026 23:58:31 +0200 +Subject: [PATCH] netfilter: synproxy: add mutex to guard hook reference + counting + +[ Upstream commit 2fcba19caaeb2a33017459d3430f057967bb91b6 ] + +As the synproxy infrastructure register netfilter hooks on-demand when a +user adds the first iptables target or nftables expression, if done +concurrently they can race each other. + +Introduce a mutex to serialize the refcount control blocks access from +both frontends. While a per namespace mutex might be more efficient, it +is not needed for target/expression like SYNPROXY. + +Fixes: ad49d86e07a4 ("netfilter: nf_tables: Add synproxy support") +Signed-off-by: Fernando Fernandez Mancera +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/netfilter/nf_synproxy_core.c b/net/netfilter/nf_synproxy_core.c +index 6a851ac..a277b2b 100644 +--- a/net/netfilter/nf_synproxy_core.c ++++ b/net/netfilter/nf_synproxy_core.c +@@ -21,6 +21,8 @@ + #include + #include + ++static DEFINE_MUTEX(synproxy_mutex); ++ + unsigned int synproxy_net_id; + EXPORT_SYMBOL_GPL(synproxy_net_id); + +@@ -768,26 +770,31 @@ static const struct nf_hook_ops ipv4_synproxy_ops[] = { + + int nf_synproxy_ipv4_init(struct synproxy_net *snet, struct net *net) + { +- int err; ++ int err = 0; + ++ mutex_lock(&synproxy_mutex); + if (snet->hook_ref4 == 0) { + err = nf_register_net_hooks(net, ipv4_synproxy_ops, + ARRAY_SIZE(ipv4_synproxy_ops)); + if (err) +- return err; ++ goto out; + } + + snet->hook_ref4++; +- return 0; ++out: ++ mutex_unlock(&synproxy_mutex); ++ return err; + } + EXPORT_SYMBOL_GPL(nf_synproxy_ipv4_init); + + void nf_synproxy_ipv4_fini(struct synproxy_net *snet, struct net *net) + { ++ mutex_lock(&synproxy_mutex); + snet->hook_ref4--; + if (snet->hook_ref4 == 0) + nf_unregister_net_hooks(net, ipv4_synproxy_ops, + ARRAY_SIZE(ipv4_synproxy_ops)); ++ mutex_unlock(&synproxy_mutex); + } + EXPORT_SYMBOL_GPL(nf_synproxy_ipv4_fini); + +@@ -1192,27 +1199,32 @@ static const struct nf_hook_ops ipv6_synproxy_ops[] = { + int + nf_synproxy_ipv6_init(struct synproxy_net *snet, struct net *net) + { +- int err; ++ int err = 0; + ++ mutex_lock(&synproxy_mutex); + if (snet->hook_ref6 == 0) { + err = nf_register_net_hooks(net, ipv6_synproxy_ops, + ARRAY_SIZE(ipv6_synproxy_ops)); + if (err) +- return err; ++ goto out; + } + + snet->hook_ref6++; +- return 0; ++out: ++ mutex_unlock(&synproxy_mutex); ++ return err; + } + EXPORT_SYMBOL_GPL(nf_synproxy_ipv6_init); + + void + nf_synproxy_ipv6_fini(struct synproxy_net *snet, struct net *net) + { ++ mutex_lock(&synproxy_mutex); + snet->hook_ref6--; + if (snet->hook_ref6 == 0) + nf_unregister_net_hooks(net, ipv6_synproxy_ops, + ARRAY_SIZE(ipv6_synproxy_ops)); ++ mutex_unlock(&synproxy_mutex); + } + EXPORT_SYMBOL_GPL(nf_synproxy_ipv6_fini); + #endif /* CONFIG_IPV6 */ diff --git a/1163-netfilter-conntrack-irc-fix-possible-out-of-bounds-read.patch b/1163-netfilter-conntrack-irc-fix-possible-out-of-bounds-read.patch new file mode 100644 index 000000000..079cba287 --- /dev/null +++ b/1163-netfilter-conntrack-irc-fix-possible-out-of-bounds-read.patch @@ -0,0 +1,42 @@ +From ddddd8271359961e403d11c90c9ba9fc38914f7e Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 27 May 2026 12:20:19 +0200 +Subject: [PATCH] netfilter: conntrack_irc: fix possible out-of-bounds read + +[ Upstream commit 66eba0ffce3b7e11449946b4cbbef8ea36112f56 ] + +When parsing fails after we've matched the command string we +should bail out instead of trying to match a different command. + +This helper should be deprecated, given prevalence of TLS I doubt it has +any relevance in 2026. + +Fixes: 869f37d8e48f ("[NETFILTER]: nf_conntrack/nf_nat: add IRC helper port") +Closes: https://sashiko.dev/#/patchset/20260525182924.28456-1-fw%40strlen.de +Signed-off-by: Florian Westphal +Reviewed-by: Fernando Fernandez Mancera +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c +index 5703846..0f50ea9 100644 +--- a/net/netfilter/nf_conntrack_irc.c ++++ b/net/netfilter/nf_conntrack_irc.c +@@ -208,7 +208,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, + if (parse_dcc(data, data_limit, &dcc_ip, + &dcc_port, &addr_beg_p, &addr_end_p)) { + pr_debug("unable to parse dcc command\n"); +- continue; ++ goto out; + } + + pr_debug("DCC bound ip/port: %pI4:%u\n", +@@ -222,7 +222,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, + net_warn_ratelimited("Forged DCC command from %pI4: %pI4:%u\n", + &tuple->src.u3.ip, + &dcc_ip, dcc_port); +- continue; ++ goto out; + } + + exp = nf_ct_expect_alloc(ct); diff --git a/1164-netfilter-nft-tunnel-fix-use-after-free-on-object-destroy.patch b/1164-netfilter-nft-tunnel-fix-use-after-free-on-object-destroy.patch new file mode 100644 index 000000000..ef6b14728 --- /dev/null +++ b/1164-netfilter-nft-tunnel-fix-use-after-free-on-object-destroy.patch @@ -0,0 +1,40 @@ +From fda6573a46ad24f35348e024905ee5bdf729797e Mon Sep 17 00:00:00 2001 +From: Tristan Madani +Date: Wed, 27 May 2026 13:57:50 +0000 +Subject: [PATCH] netfilter: nft_tunnel: fix use-after-free on object destroy + +commit c32b26aaa2f9216520a38b3f4bfeec846eb3eb8a upstream. + +nft_tunnel_obj_destroy() calls metadata_dst_free() which directly +kfree()s the metadata_dst, ignoring the dst_entry refcount. Packets +that took a reference via dst_hold() in nft_tunnel_obj_eval() and +are still queued (e.g. in a netem qdisc) are left with a dangling +pointer. When these packets are eventually dequeued, dst_release() +operates on freed memory. + +Replace metadata_dst_free() with dst_release() so the metadata_dst +is freed only after all references are dropped. The dst subsystem +already handles metadata_dst cleanup in dst_destroy() when +DST_METADATA is set. + +Fixes: af308b94a2a4 ("netfilter: nf_tables: add tunnel support") +Cc: stable@vger.kernel.org +Signed-off-by: Tristan Madani +Reviewed-by: Fernando Fernandez Mancera +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Greg Kroah-Hartman + +diff --git a/net/netfilter/nft_tunnel.c b/net/netfilter/nft_tunnel.c +index e18d322..714b6a5 100644 +--- a/net/netfilter/nft_tunnel.c ++++ b/net/netfilter/nft_tunnel.c +@@ -705,7 +705,7 @@ static void nft_tunnel_obj_destroy(const struct nft_ctx *ctx, + { + struct nft_tunnel_obj *priv = nft_obj_data(obj); + +- metadata_dst_free(priv->md); ++ dst_release(&priv->md->dst); + } + + static struct nft_object_type nft_tunnel_obj_type; diff --git a/1165-netfilter-nft-ct-bail-out-on-template-ct-in-get-eval.patch b/1165-netfilter-nft-ct-bail-out-on-template-ct-in-get-eval.patch new file mode 100644 index 000000000..15393ccf6 --- /dev/null +++ b/1165-netfilter-nft-ct-bail-out-on-template-ct-in-get-eval.patch @@ -0,0 +1,87 @@ +From 8470f676eadeab99132708acb1a85915664d6115 Mon Sep 17 00:00:00 2001 +From: Jiayuan Chen +Date: Thu, 28 May 2026 19:09:19 +0800 +Subject: [PATCH] netfilter: nft_ct: bail out on template ct in get eval + +[ Upstream commit 3027ecbdb5fdf9200251c21d4818e4c447ef78e1 ] + +I noticed this issue while looking at a historic syzbot report [1]. + +A rule like the one below is enough to trigger the bug: + + table ip t { + chain pre { + type filter hook prerouting priority raw; + ct zone set 1 + ct original saddr 1.2.3.4 accept + } + } + +The first expression attaches a per-cpu template ct via +nft_ct_set_zone_eval() (nf_ct_tmpl_alloc -> kzalloc, tuple is all +zero, nf_ct_l3num(ct) == 0). The next expression then calls +nft_ct_get_eval() on the same skb, treats the template as a real ct +and hits the 16-byte memcpy path. With dreg at NFT_REG32_15 this +overflows past struct nft_regs on the kernel stack; with smaller +dreg values it silently clobbers adjacent registers. + +Reject template ct at the eval entry and in nft_ct_get_fast_eval(), +mirroring the check nft_ct_set_eval() already has. Additionally, +bound the address copy in NFT_CT_SRC / NFT_CT_DST by priv->len +instead of by nf_ct_l3num(ct): nf_ct_get_tuple() zeroes the tuple +before pkt_to_tuple() fills in only the protocol-relevant leading +bytes, so the trailing bytes of tuple->{src,dst}.u3.all are +well-defined zero. priv->len is validated at rule load, so the +copy size is now bounded by the destination register rather than +by an untrusted field on the conntrack. + +[1]: https://syzkaller.appspot.com/bug?id=389cf09cb72926114fce90dc85a2c3231dcb647c + +Fixes: 45d9bcda21f4 ("netfilter: nf_tables: validate len in nft_validate_data_load()") +Suggested-by: Florian Westphal +Signed-off-by: Jiayuan Chen +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c +index d877767..d411971 100644 +--- a/net/netfilter/nft_ct.c ++++ b/net/netfilter/nft_ct.c +@@ -77,7 +77,7 @@ static void nft_ct_get_eval(const struct nft_expr *expr, + break; + } + +- if (ct == NULL) ++ if (!ct || nf_ct_is_template(ct)) + goto err; + + switch (priv->key) { +@@ -179,12 +179,10 @@ static void nft_ct_get_eval(const struct nft_expr *expr, + tuple = &ct->tuplehash[priv->dir].tuple; + switch (priv->key) { + case NFT_CT_SRC: +- memcpy(dest, tuple->src.u3.all, +- nf_ct_l3num(ct) == NFPROTO_IPV4 ? 4 : 16); ++ memcpy(dest, tuple->src.u3.all, priv->len); + return; + case NFT_CT_DST: +- memcpy(dest, tuple->dst.u3.all, +- nf_ct_l3num(ct) == NFPROTO_IPV4 ? 4 : 16); ++ memcpy(dest, tuple->dst.u3.all, priv->len); + return; + case NFT_CT_PROTO_SRC: + nft_reg_store16(dest, (__force u16)tuple->src.u.all); +diff --git a/net/netfilter/nft_ct_fast.c b/net/netfilter/nft_ct_fast.c +index e684c8a..ecf7b3a 100644 +--- a/net/netfilter/nft_ct_fast.c ++++ b/net/netfilter/nft_ct_fast.c +@@ -30,7 +30,7 @@ void nft_ct_get_fast_eval(const struct nft_expr *expr, + break; + } + +- if (!ct) { ++ if (!ct || nf_ct_is_template(ct)) { + regs->verdict.code = NFT_BREAK; + return; + } diff --git a/1166-netfilter-revalidate-bridge-ports.patch b/1166-netfilter-revalidate-bridge-ports.patch new file mode 100644 index 000000000..e78a8917e --- /dev/null +++ b/1166-netfilter-revalidate-bridge-ports.patch @@ -0,0 +1,228 @@ +From 43330a1e8aace6b5a8de9aba127e9e394ab49b0f Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 2 Jun 2026 17:04:25 +0200 +Subject: [PATCH] netfilter: revalidate bridge ports + +[ Upstream commit ccb9fd4b87538ccf19ccff78ee26700526d94867 ] + +ebt_redirect_tg() dereferences br_port_get_rcu() return without a +NULL check, causing a kernel panic when the bridge port has been +removed between the original hook invocation and an NFQUEUE +reinject. + +A mere NULL check isn't sufficient, however. As sashiko review +points out userspace can not only remove the port from the bridge, +it could also place the device in a different virtual device, e.g. +macvlan. + +If this happens, we must drop the packet, there is no way for us to +reinject it into the bridge path. + +Switch to _upper API, we don't need the bridge port structure. +Also, this fix keeps another bug intact: + +Both nfnetlink_log and nfnetlink_queue use CONFIG_BRIDGE_NETFILTER +too aggressive, which prevents certain logging features when queueing +in bridge family: NETFILTER_FAMILY_BRIDGE can be enabled while the old +CONFIG_BRIDGE_NETFILTER cruft is off. + +Fixes tag is a common ancestor, this was always broken. + +Fixes: f350a0a87374 ("bridge: use rx_handler_data pointer to store net_bridge_port pointer") +Reported-by: Ji'an Zhou +Assisted-by: Claude:claude-sonnet-4-6 +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/bridge/netfilter/ebt_dnat.c b/net/bridge/netfilter/ebt_dnat.c +index 3fda71a..73f185c 100644 +--- a/net/bridge/netfilter/ebt_dnat.c ++++ b/net/bridge/netfilter/ebt_dnat.c +@@ -39,7 +39,9 @@ ebt_dnat_tg(struct sk_buff *skb, const struct xt_action_param *par) + dev = xt_in(par); + break; + case NF_BR_PRE_ROUTING: +- dev = br_port_get_rcu(xt_in(par))->br->dev; ++ dev = netdev_master_upper_dev_get_rcu(xt_in(par)); ++ if (!dev) /* bridge port removed? */ ++ return EBT_DROP; + break; + default: + dev = NULL; +diff --git a/net/bridge/netfilter/ebt_redirect.c b/net/bridge/netfilter/ebt_redirect.c +index 3077905..83486cd 100644 +--- a/net/bridge/netfilter/ebt_redirect.c ++++ b/net/bridge/netfilter/ebt_redirect.c +@@ -24,12 +24,18 @@ ebt_redirect_tg(struct sk_buff *skb, const struct xt_action_param *par) + if (skb_ensure_writable(skb, 0)) + return EBT_DROP; + +- if (xt_hooknum(par) != NF_BR_BROUTING) +- /* rcu_read_lock()ed by nf_hook_thresh */ +- ether_addr_copy(eth_hdr(skb)->h_dest, +- br_port_get_rcu(xt_in(par))->br->dev->dev_addr); +- else ++ if (xt_hooknum(par) != NF_BR_BROUTING) { ++ const struct net_device *dev; ++ ++ dev = netdev_master_upper_dev_get_rcu(xt_in(par)); ++ if (!dev) ++ return EBT_DROP; ++ ++ ether_addr_copy(eth_hdr(skb)->h_dest, dev->dev_addr); ++ } else { + ether_addr_copy(eth_hdr(skb)->h_dest, xt_in(par)->dev_addr); ++ } ++ + skb->pkt_type = PACKET_HOST; + return info->target; + } +diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c +index b1f3eda..25a30bf 100644 +--- a/net/netfilter/nfnetlink_log.c ++++ b/net/netfilter/nfnetlink_log.c +@@ -450,6 +450,23 @@ static int nfulnl_put_bridge(struct nfulnl_instance *inst, const struct sk_buff + return -1; + } + ++#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) ++static int nflog_put_master_ifindex(struct sk_buff *nlskb, int attr, ++ const struct net_device *dev) ++{ ++ const struct net_device *upper; ++ ++ if (dev && !netif_is_bridge_port(dev)) ++ return 0; ++ ++ upper = netdev_master_upper_dev_get_rcu((struct net_device *)dev); ++ if (upper && nla_put_be32(nlskb, attr, htonl(upper->ifindex))) ++ return -EMSGSIZE; ++ ++ return 0; ++} ++#endif ++ + /* This is an inline function, we don't really care about a long + * list of arguments */ + static inline int +@@ -504,8 +521,7 @@ __build_packet_message(struct nfnl_log_net *log, + /* rcu_read_lock()ed by nf_hook_thresh or + * nf_log_packet. + */ +- nla_put_be32(inst->skb, NFULA_IFINDEX_INDEV, +- htonl(br_port_get_rcu(indev)->br->dev->ifindex))) ++ nflog_put_master_ifindex(inst->skb, NFULA_IFINDEX_INDEV, indev)) + goto nla_put_failure; + } else { + int physinif; +@@ -541,8 +557,7 @@ __build_packet_message(struct nfnl_log_net *log, + /* rcu_read_lock()ed by nf_hook_thresh or + * nf_log_packet. + */ +- nla_put_be32(inst->skb, NFULA_IFINDEX_OUTDEV, +- htonl(br_port_get_rcu(outdev)->br->dev->ifindex))) ++ nflog_put_master_ifindex(inst->skb, NFULA_IFINDEX_OUTDEV, outdev)) + goto nla_put_failure; + } else { + struct net_device *physoutdev; +diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c +index 8d4fa10..87ac5c8 100644 +--- a/net/netfilter/nfnetlink_queue.c ++++ b/net/netfilter/nfnetlink_queue.c +@@ -369,10 +369,47 @@ static void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict) + nf_queue_entry_free(entry); + } + ++static bool nf_bridge_port_valid(const struct net_device *dev) ++{ ++ if (!dev) ++ return true; ++ ++ return netif_is_bridge_port(dev); ++} ++ ++/* queued skbs leave rcu protection. We bump device refcount so that ++ * the device cannot go away. However, while packet was out the port ++ * could have been removed from the bridge. ++ * ++ * Ensure in+outdev are still part of a bridge at reinject time. ++ * ++ * The device rx_handler_data could even be pointing at data that is ++ * not a net_bridge_port structure. ++ */ ++static bool nf_bridge_ports_valid(const struct nf_queue_entry *entry) ++{ ++#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) ++ if (!nf_bridge_port_valid(entry->physin) || ++ !nf_bridge_port_valid(entry->physout)) ++ return false; ++#endif ++ if (entry->state.pf != PF_BRIDGE) ++ return true; ++ ++ if (!nf_bridge_port_valid(entry->state.in) || ++ !nf_bridge_port_valid(entry->state.out)) ++ return false; ++ ++ return true; ++} ++ + static void nfqnl_reinject(struct nf_queue_entry *entry, unsigned int verdict) + { + const struct nf_ct_hook *ct_hook; + ++ if (!nf_bridge_ports_valid(entry)) ++ verdict = NF_DROP; ++ + if (verdict == NF_ACCEPT || + verdict == NF_REPEAT || + verdict == NF_STOP) { +@@ -548,6 +585,23 @@ static int nf_queue_checksum_help(struct sk_buff *entskb) + return skb_checksum_help(entskb); + } + ++#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) ++static int nfqnl_put_master_ifindex(struct sk_buff *nlskb, int attr, ++ const struct net_device *dev) ++{ ++ const struct net_device *upper; ++ ++ if (dev && !netif_is_bridge_port(dev)) ++ return 0; ++ ++ upper = netdev_master_upper_dev_get_rcu((struct net_device *)dev); ++ if (upper && nla_put_be32(nlskb, attr, htonl(upper->ifindex))) ++ return -EMSGSIZE; ++ ++ return 0; ++} ++#endif ++ + static struct sk_buff * + nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue, + struct nf_queue_entry *entry, +@@ -681,10 +735,7 @@ nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue, + * netfilter_bridge) */ + if (nla_put_be32(skb, NFQA_IFINDEX_PHYSINDEV, + htonl(indev->ifindex)) || +- /* this is the bridge group "brX" */ +- /* rcu_read_lock()ed by __nf_queue */ +- nla_put_be32(skb, NFQA_IFINDEX_INDEV, +- htonl(br_port_get_rcu(indev)->br->dev->ifindex))) ++ nfqnl_put_master_ifindex(skb, NFQA_IFINDEX_INDEV, indev)) + goto nla_put_failure; + } else { + int physinif; +@@ -715,10 +766,7 @@ nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue, + * netfilter_bridge) */ + if (nla_put_be32(skb, NFQA_IFINDEX_PHYSOUTDEV, + htonl(outdev->ifindex)) || +- /* this is the bridge group "brX" */ +- /* rcu_read_lock()ed by __nf_queue */ +- nla_put_be32(skb, NFQA_IFINDEX_OUTDEV, +- htonl(br_port_get_rcu(outdev)->br->dev->ifindex))) ++ nfqnl_put_master_ifindex(skb, NFQA_IFINDEX_OUTDEV, outdev)) + goto nla_put_failure; + } else { + int physoutif; diff --git a/1167-netfilter-nf-conntrack-destroy-stale-expectfn-expectations-on-unregister.patch b/1167-netfilter-nf-conntrack-destroy-stale-expectfn-expectations-on-unregister.patch new file mode 100644 index 000000000..22765311d --- /dev/null +++ b/1167-netfilter-nf-conntrack-destroy-stale-expectfn-expectations-on-unregister.patch @@ -0,0 +1,138 @@ +From f92c90a2a3e6ff6f9f7fe88fde9004b4ca8f956d Mon Sep 17 00:00:00 2001 +From: Weiming Shi +Date: Wed, 3 Jun 2026 00:38:17 -0700 +Subject: [PATCH] netfilter: nf_conntrack: destroy stale expectfn expectations + on unregister + +[ Upstream commit c3009418f9fa1dcb3eb86f4d8c92583537b5faa3 ] + +NAT helpers such as nf_nat_h323 store a raw pointer to module text in +exp->expectfn (e.g. ip_nat_q931_expect). nf_ct_helper_expectfn_unregister() +only unlinks the callback descriptor and never walks the expectation table, +so an expectation pending at module removal survives with a dangling +exp->expectfn into freed module text. + +When the expected connection arrives, init_conntrack() invokes +exp->expectfn(), now a stale pointer into the unloaded module. Reproduced +on a KASAN build by loading the H.323 helpers, creating a Q.931 +expectation, unloading nf_nat_h323, then connecting to the expected port: + + Oops: int3: 0000 [#1] SMP KASAN NOPTI + RIP: 0010:0xffffffffa06102d1 + init_conntrack.isra.0 (net/netfilter/nf_conntrack_core.c:1862) + nf_conntrack_in (net/netfilter/nf_conntrack_core.c:2049) + ipv4_conntrack_local (net/netfilter/nf_conntrack_proto.c:223) + nf_hook_slow (net/netfilter/core.c:619) + __ip_local_out (net/ipv4/ip_output.c:120) + __tcp_transmit_skb (net/ipv4/tcp_output.c:1715) + tcp_connect (net/ipv4/tcp_output.c:4374) + tcp_v4_connect (net/ipv4/tcp_ipv4.c:345) + __sys_connect (net/socket.c:2167) + Modules linked in: nf_conntrack_h323 [last unloaded: nf_nat_h323] + +Reaching the dangling state requires CAP_SYS_MODULE in the initial user +namespace to remove a NAT helper that still has live expectations, so this +is a robustness fix; leaving an expectation pointing at freed text is wrong +regardless. + +Add nf_ct_helper_expectfn_destroy(), which walks the expectation table and +drops every expectation whose ->expectfn matches the descriptor being torn +down. Call it from each NAT helper's exit path after the existing RCU grace +period, so no expectation outlives the code it points at and no extra +synchronize_rcu() is introduced. With the fix, the same reproducer runs to +completion without the Oops. + +Fixes: f587de0e2feb ("[NETFILTER]: nf_conntrack/nf_nat: add H.323 helper port") +Reported-by: Xiang Mei +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Weiming Shi +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h +index de2f956..24cf3d2 100644 +--- a/include/net/netfilter/nf_conntrack_helper.h ++++ b/include/net/netfilter/nf_conntrack_helper.h +@@ -155,6 +155,7 @@ void nf_ct_helper_log(struct sk_buff *skb, const struct nf_conn *ct, + + void nf_ct_helper_expectfn_register(struct nf_ct_helper_expectfn *n); + void nf_ct_helper_expectfn_unregister(struct nf_ct_helper_expectfn *n); ++void nf_ct_helper_expectfn_destroy(const struct nf_ct_helper_expectfn *n); + struct nf_ct_helper_expectfn * + nf_ct_helper_expectfn_find_by_name(const char *name); + struct nf_ct_helper_expectfn * +diff --git a/net/ipv4/netfilter/nf_nat_h323.c b/net/ipv4/netfilter/nf_nat_h323.c +index faee20a..10e1b08 100644 +--- a/net/ipv4/netfilter/nf_nat_h323.c ++++ b/net/ipv4/netfilter/nf_nat_h323.c +@@ -555,6 +555,8 @@ static void __exit nf_nat_h323_fini(void) + nf_ct_helper_expectfn_unregister(&q931_nat); + nf_ct_helper_expectfn_unregister(&callforwarding_nat); + synchronize_rcu(); ++ nf_ct_helper_expectfn_destroy(&q931_nat); ++ nf_ct_helper_expectfn_destroy(&callforwarding_nat); + } + + /****************************************************************************/ +diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c +index 17e971b..2c5a717 100644 +--- a/net/netfilter/nf_conntrack_helper.c ++++ b/net/netfilter/nf_conntrack_helper.c +@@ -283,6 +283,25 @@ void nf_ct_helper_expectfn_unregister(struct nf_ct_helper_expectfn *n) + } + EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_unregister); + ++static bool expect_iter_expectfn(struct nf_conntrack_expect *exp, void *data) ++{ ++ const struct nf_ct_helper_expectfn *n = data; ++ ++ /* Relies on registered expectfn descriptors having unique ->expectfn ++ * pointers, which holds for the in-tree NAT helpers. ++ */ ++ return exp->expectfn == n->expectfn; ++} ++ ++/* Destroy expectations still pointing at @n->expectfn; call after the ++ * caller's RCU grace period so none outlives the (often modular) callback. ++ */ ++void nf_ct_helper_expectfn_destroy(const struct nf_ct_helper_expectfn *n) ++{ ++ nf_ct_expect_iterate_destroy(expect_iter_expectfn, (void *)n); ++} ++EXPORT_SYMBOL_GPL(nf_ct_helper_expectfn_destroy); ++ + /* Caller should hold the rcu lock */ + struct nf_ct_helper_expectfn * + nf_ct_helper_expectfn_find_by_name(const char *name) +diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c +index d380e1a..12bc8c9 100644 +--- a/net/netfilter/nf_nat_core.c ++++ b/net/netfilter/nf_nat_core.c +@@ -1355,6 +1355,7 @@ static int __init nf_nat_init(void) + RCU_INIT_POINTER(nf_nat_hook, NULL); + nf_ct_helper_expectfn_unregister(&follow_master_nat); + synchronize_net(); ++ nf_ct_helper_expectfn_destroy(&follow_master_nat); + unregister_pernet_subsys(&nat_net_ops); + kvfree(nf_nat_bysource); + } +@@ -1372,6 +1373,7 @@ static void __exit nf_nat_cleanup(void) + RCU_INIT_POINTER(nf_nat_hook, NULL); + + synchronize_net(); ++ nf_ct_helper_expectfn_destroy(&follow_master_nat); + kvfree(nf_nat_bysource); + unregister_pernet_subsys(&nat_net_ops); + } +diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c +index 9fbfc6b..00838c0 100644 +--- a/net/netfilter/nf_nat_sip.c ++++ b/net/netfilter/nf_nat_sip.c +@@ -655,6 +655,7 @@ static void __exit nf_nat_sip_fini(void) + RCU_INIT_POINTER(nf_nat_sip_hooks, NULL); + nf_ct_helper_expectfn_unregister(&sip_nat); + synchronize_rcu(); ++ nf_ct_helper_expectfn_destroy(&sip_nat); + } + + static const struct nf_nat_sip_hooks sip_hooks = { diff --git a/1168-netfilter-nf-log-validate-mac-header-was-set-before-dumping-it.patch b/1168-netfilter-nf-log-validate-mac-header-was-set-before-dumping-it.patch new file mode 100644 index 000000000..fc957f7ad --- /dev/null +++ b/1168-netfilter-nf-log-validate-mac-header-was-set-before-dumping-it.patch @@ -0,0 +1,63 @@ +From c38d41134085193efd5b237cf513ad5b3421a60d Mon Sep 17 00:00:00 2001 +From: Xiang Mei +Date: Tue, 9 Jun 2026 15:55:02 -0700 +Subject: [PATCH] netfilter: nf_log: validate MAC header was set before dumping + it + +[ Upstream commit a84b6fedbc97078788be78dbdd7517d143ad1a77 ] + +The fallback path of dump_mac_header() guards the MAC header access +only with "skb->mac_header != skb->network_header", without checking +skb_mac_header_was_set(). When the MAC header is unset, mac_header is +0xffff, so the test passes and skb_mac_header(skb) returns +skb->head + 0xffff, ~64 KiB past the buffer; the loop then reads +dev->hard_header_len bytes out of bounds into the kernel log. + +This is reachable via the netdev logger: nf_log_unknown_packet() calls +dump_mac_header() unconditionally, and an skb sent through AF_PACKET +with PACKET_QDISC_BYPASS reaches the egress hook with mac_header still +unset (__dev_queue_xmit(), which would reset it, is bypassed). + +Add the skb_mac_header_was_set() check the ARPHRD_ETHER path already +uses, and replace the open-coded MAC header length test with +skb_mac_header_len(). Only skbs with an unset MAC header are affected; +valid ones are dumped as before. + + BUG: KASAN: slab-out-of-bounds in dump_mac_header (net/netfilter/nf_log_syslog.c:831) + Read of size 1 at addr ffff88800ea49d3f by task exploit/148 + Call Trace: + kasan_report (mm/kasan/report.c:595) + dump_mac_header (net/netfilter/nf_log_syslog.c:831) + nf_log_netdev_packet (net/netfilter/nf_log_syslog.c:938 net/netfilter/nf_log_syslog.c:963) + nf_log_packet (net/netfilter/nf_log.c:260) + nft_log_eval (net/netfilter/nft_log.c:60) + nft_do_chain (net/netfilter/nf_tables_core.c:285) + nft_do_chain_netdev (net/netfilter/nft_chain_filter.c:307) + nf_hook_slow (net/netfilter/core.c:619) + nf_hook_direct_egress (net/packet/af_packet.c:257) + packet_xmit (net/packet/af_packet.c:280) + packet_sendmsg (net/packet/af_packet.c:3114) + __sys_sendto (net/socket.c:2265) + +Fixes: 7eb9282cd0ef ("netfilter: ipt_LOG/ip6t_LOG: add option to print decoded MAC header") +Reported-by: Weiming Shi +Assisted-by: Claude:claude-opus-4-8 +Signed-off-by: Xiang Mei +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/netfilter/nf_log_syslog.c b/net/netfilter/nf_log_syslog.c +index 5840222..09b9152 100644 +--- a/net/netfilter/nf_log_syslog.c ++++ b/net/netfilter/nf_log_syslog.c +@@ -799,8 +799,8 @@ static void dump_mac_header(struct nf_log_buf *m, + + fallback: + nf_log_buf_add(m, "MAC="); +- if (dev->hard_header_len && +- skb->mac_header != skb->network_header) { ++ if (dev->hard_header_len && skb_mac_header_was_set(skb) && ++ skb_mac_header_len(skb) != 0) { + const unsigned char *p = skb_mac_header(skb); + unsigned int i; + diff --git a/1169-netfilter-nft-exthdr-fix-register-tracking-for-f-present-flag.patch b/1169-netfilter-nft-exthdr-fix-register-tracking-for-f-present-flag.patch new file mode 100644 index 000000000..d0aa5a96b --- /dev/null +++ b/1169-netfilter-nft-exthdr-fix-register-tracking-for-f-present-flag.patch @@ -0,0 +1,38 @@ +From 67b27434c43b68a97becda98c9f0c8cf6cba2134 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Tue, 9 Jun 2026 21:28:09 +0200 +Subject: [PATCH] netfilter: nft_exthdr: fix register tracking for F_PRESENT + flag + +[ Upstream commit 772cecf198da732faebb5dcfc46d66a505be8495 ] + +nft_exthdr_init() passes user-controlled priv->len to +nft_parse_register_store(), which marks that many bytes in the +register bitmap as initialized. However, when NFT_EXTHDR_F_PRESENT +is set, the eval paths write only 1 byte (nft_reg_store8) or +4 bytes (*dest = 0 on TCP/DCCP error path). When len > 4, +registers beyond the first are never written, retaining +uninitialized stack data from nft_regs. + +Bail out if userspace requests too much data when F_PRESENT is set. + +Reported-by: Ji'an Zhou +Fixes: c078ca3b0c5b ("netfilter: nft_exthdr: Add support for existence check") +Signed-off-by: Florian Westphal +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +diff --git a/net/netfilter/nft_exthdr.c b/net/netfilter/nft_exthdr.c +index c74012c..1fc2a94 100644 +--- a/net/netfilter/nft_exthdr.c ++++ b/net/netfilter/nft_exthdr.c +@@ -530,6 +530,9 @@ static int nft_exthdr_init(const struct nft_ctx *ctx, + return err; + } + ++ if ((flags & NFT_EXTHDR_F_PRESENT) && len != 1) ++ return -EINVAL; ++ + priv->type = nla_get_u8(tb[NFTA_EXTHDR_TYPE]); + priv->offset = offset; + priv->len = len; diff --git a/1170-netfilter-nft-fib-fix-stale-stack-leak-via-the-oifname-register.patch b/1170-netfilter-nft-fib-fix-stale-stack-leak-via-the-oifname-register.patch new file mode 100644 index 000000000..b97d289fb --- /dev/null +++ b/1170-netfilter-nft-fib-fix-stale-stack-leak-via-the-oifname-register.patch @@ -0,0 +1,83 @@ +From 8c84885e9790823828bb8084736ea15769b1ac16 Mon Sep 17 00:00:00 2001 +From: Davide Ornaghi +Date: Mon, 15 Jun 2026 09:34:53 -0400 +Subject: [PATCH] netfilter: nft_fib: fix stale stack leak via the OIFNAME + register + +[ Upstream commit ab185e0c4fb82dfba6fb86f8271e06f931d9c64c ] + +For NFT_FIB_RESULT_OIFNAME the destination register is declared with +len = IFNAMSIZ (four 32-bit registers), but on the lookup-fail, +RTN_LOCAL and oif-mismatch paths nft_fib{4,6}_eval() only writes one +register via "*dest = 0". The remaining three registers are left as +whatever was on the stack in nft_do_chain()'s struct nft_regs, and a +downstream expression that loads the register span can leak that +uninitialised kernel stack to userspace. + +The NFTA_FIB_F_PRESENT existence check has the same shape: it is only +meaningful for NFT_FIB_RESULT_OIF, yet it was accepted for any result type +while the eval stores a single byte via nft_reg_store8(), leaving the rest +of the declared span stale. + +Fix both: + + - replace the bare "*dest = 0" in the eval with nft_fib_store_result(), + which strscpy_pad()s the whole IFNAMSIZ for OIFNAME (and is already + used on the other early-return path), and + + - restrict NFTA_FIB_F_PRESENT to NFT_FIB_RESULT_OIF and declare its + destination as a single u8, so the marked span matches the one byte + the eval writes. + +Fixes: f6d0cbcf09c5 ("netfilter: nf_tables: add fib expression") +Suggested-by: Florian Westphal +Cc: stable@vger.kernel.org +Signed-off-by: Davide Ornaghi +Signed-off-by: Pablo Neira Ayuso +[ kept the tree's older `ip6_route_lookup()`/`rt6_info` IPv6 context and changed only `*dest = 0;` to `nft_fib_store_result(dest, priv, NULL);` ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman + +diff --git a/net/ipv4/netfilter/nft_fib_ipv4.c b/net/ipv4/netfilter/nft_fib_ipv4.c +index f514eb5..1c22ee4 100644 +--- a/net/ipv4/netfilter/nft_fib_ipv4.c ++++ b/net/ipv4/netfilter/nft_fib_ipv4.c +@@ -127,7 +127,7 @@ void nft_fib4_eval(const struct nft_expr *expr, struct nft_regs *regs, + fl4.saddr = get_saddr(iph->daddr); + } + +- *dest = 0; ++ nft_fib_store_result(dest, priv, NULL); + + if (fib_lookup(nft_net(pkt), &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE)) + return; +diff --git a/net/ipv6/netfilter/nft_fib_ipv6.c b/net/ipv6/netfilter/nft_fib_ipv6.c +index 421036a..3005dfb 100644 +--- a/net/ipv6/netfilter/nft_fib_ipv6.c ++++ b/net/ipv6/netfilter/nft_fib_ipv6.c +@@ -192,7 +192,7 @@ void nft_fib6_eval(const struct nft_expr *expr, struct nft_regs *regs, + + lookup_flags = nft_fib6_flowi_init(&fl6, priv, pkt, oif, iph); + +- *dest = 0; ++ nft_fib_store_result(dest, priv, NULL); + rt = (void *)ip6_route_lookup(nft_net(pkt), &fl6, pkt->skb, + lookup_flags); + if (rt->dst.error) +diff --git a/net/netfilter/nft_fib.c b/net/netfilter/nft_fib.c +index 96e02a8..2284613 100644 +--- a/net/netfilter/nft_fib.c ++++ b/net/netfilter/nft_fib.c +@@ -107,6 +107,12 @@ int nft_fib_init(const struct nft_ctx *ctx, const struct nft_expr *expr, + return -EINVAL; + } + ++ if (priv->flags & NFTA_FIB_F_PRESENT) { ++ if (priv->result != NFT_FIB_RESULT_OIF) ++ return -EINVAL; ++ len = sizeof(u8); ++ } ++ + err = nft_parse_register_store(ctx, tb[NFTA_FIB_DREG], &priv->dreg, + NULL, NFT_DATA_VALUE, len); + if (err < 0) diff --git a/1171-netfilter-nf-dup-netdev-add-nf-dev-xmit-recursion-helpers-and-use-them.patch b/1171-netfilter-nf-dup-netdev-add-nf-dev-xmit-recursion-helpers-and-use-them.patch new file mode 100644 index 000000000..937bd4d1c --- /dev/null +++ b/1171-netfilter-nf-dup-netdev-add-nf-dev-xmit-recursion-helpers-and-use-them.patch @@ -0,0 +1,185 @@ +From 2354e975932dabb06fad239f07a3b68fd1809737 Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Wed, 10 Jun 2026 00:03:19 +0200 +Subject: [PATCH] netfilter: nf_dup_netdev: add nf_dev_xmit_recursion*() + helpers and use them + +Update nft_dup and nft_fwd to use the nf_dev_xmit_recursion() helpers. +This patch also disables BH when transmitting the skb to address a +possible migration to different CPU leading to imbalanced decrementation +of the recursion counters. + +This is modeled after Florian Westphal's dev_xmit_recursion*() API +available since commit 97cdcf37b57e ("net: place xmit recursion in +softnet data") according to its current state in the tree. + +Fixes: 1d47b55b36d2 ("netfilter: nft_fwd_netdev: use recursion counter in neigh egress path") +Fixes: f37ad9127039 ("netfilter: nf_dup_netdev: Move the recursion counter struct netdev_xmit") +Signed-off-by: Pablo Neira Ayuso + +diff --git a/include/net/netfilter/nf_dup_netdev.h b/include/net/netfilter/nf_dup_netdev.h +index 609bcf4..b175d27 100644 +--- a/include/net/netfilter/nf_dup_netdev.h ++++ b/include/net/netfilter/nf_dup_netdev.h +@@ -3,23 +3,10 @@ + #define _NF_DUP_NETDEV_H_ + + #include +-#include +-#include + + void nf_dup_netdev_egress(const struct nft_pktinfo *pkt, int oif); + void nf_fwd_netdev_egress(const struct nft_pktinfo *pkt, int oif); + +-#define NF_RECURSION_LIMIT 2 +- +-static inline u8 *nf_get_nf_dup_skb_recursion(void) +-{ +-#ifndef CONFIG_PREEMPT_RT +- return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion); +-#else +- return ¤t->net_xmit.nf_dup_skb_recursion; +-#endif +-} +- + struct nft_offload_ctx; + struct nft_flow_rule; + +diff --git a/net/netfilter/nf_dup_netdev.c b/net/netfilter/nf_dup_netdev.c +index 516bcf4..ab39733 100644 +--- a/net/netfilter/nf_dup_netdev.c ++++ b/net/netfilter/nf_dup_netdev.c +@@ -17,28 +17,24 @@ + + static DEFINE_PER_CPU(u8, nf_dup_skb_recursion); + +-#define NF_RECURSION_LIMIT 2 +- +-#ifndef CONFIG_PREEMPT_RT +-static u8 *nf_get_nf_dup_skb_recursion(void) ++static bool nf_dev_xmit_recursion(void) + { +- return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion); ++ return unlikely(__this_cpu_read(nf_dup_skb_recursion) > NF_RECURSION_LIMIT); + } +-#else + +-static u8 *nf_get_nf_dup_skb_recursion(void) ++static void nf_dev_xmit_recursion_inc(void) + { +- return ¤t->net_xmit.nf_dup_skb_recursion; ++ __this_cpu_inc(nf_dup_skb_recursion); + } + +-#endif ++static void nf_dev_xmit_recursion_dec(void) ++{ ++ __this_cpu_dec(nf_dup_skb_recursion); ++} + + static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev, + enum nf_dev_hooks hook) + { +- if (__this_cpu_read(nf_dup_skb_recursion) > NF_RECURSION_LIMIT) +- goto err; +- + if (hook == NF_NETDEV_INGRESS && skb_mac_header_was_set(skb)) { + if (skb_cow_head(skb, skb->mac_len)) + goto err; +@@ -48,9 +44,15 @@ static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev, + + skb->dev = dev; + skb_clear_tstamp(skb); +- __this_cpu_inc(nf_dup_skb_recursion); ++ local_bh_disable(); ++ if (nf_dev_xmit_recursion()) { ++ local_bh_enable(); ++ goto err; ++ } ++ nf_dev_xmit_recursion_inc(); + dev_queue_xmit(skb); +- __this_cpu_dec(nf_dup_skb_recursion); ++ nf_dev_xmit_recursion_dec(); ++ local_bh_enable(); + return; + err: + kfree_skb(skb); +diff --git a/net/netfilter/nft_fwd_netdev.c b/net/netfilter/nft_fwd_netdev.c +index a9743a1..8b0de2d 100644 +--- a/net/netfilter/nft_fwd_netdev.c ++++ b/net/netfilter/nft_fwd_netdev.c +@@ -21,6 +21,25 @@ struct nft_fwd_netdev { + u8 sreg_dev; + }; + ++ ++#define NF_RECURSION_LIMIT 2 ++static DEFINE_PER_CPU(u8, nf_dup_skb_recursion); ++ ++static bool nf_dev_xmit_recursion(void) ++{ ++ return unlikely(__this_cpu_read(nf_dup_skb_recursion) > NF_RECURSION_LIMIT); ++} ++ ++static void nf_dev_xmit_recursion_inc(void) ++{ ++ __this_cpu_inc(nf_dup_skb_recursion); ++} ++ ++static void nf_dev_xmit_recursion_dec(void) ++{ ++ __this_cpu_dec(nf_dup_skb_recursion); ++} ++ + static void nft_fwd_netdev_eval(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) +@@ -95,7 +114,6 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) + { +- u8 *nf_dup_skb_recursion = nf_get_nf_dup_skb_recursion(); + struct nft_fwd_neigh *priv = nft_expr_priv(expr); + void *addr = ®s->data[priv->sreg_addr]; + int oif = regs->data[priv->sreg_dev]; +@@ -144,13 +162,15 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + goto out; + } + +- if (*nf_dup_skb_recursion > NF_RECURSION_LIMIT) { ++ dev = dev_get_by_index_rcu(nft_net(pkt), oif); ++ if (!dev) { + verdict = NF_DROP; + goto out; + } + +- dev = dev_get_by_index_rcu(nft_net(pkt), oif); +- if (dev == NULL) { ++ local_bh_disable(); ++ if (nf_dev_xmit_recursion()) { ++ local_bh_enable(); + verdict = NF_DROP; + goto out; + } +@@ -159,16 +179,18 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, + if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) { + skb = skb_expand_head(skb, hh_len); + if (!skb) { +- verdict = NF_STOLEN; ++ local_bh_enable(); + goto out; + } + } + + skb->dev = dev; + skb_clear_tstamp(skb); +- (*nf_dup_skb_recursion)++; ++ ++ nf_dev_xmit_recursion_inc(); + neigh_xmit(neigh_table, dev, addr, skb); +- (*nf_dup_skb_recursion)--; ++ nf_dev_xmit_recursion_dec(); ++ local_bh_enable(); + out: + regs->verdict.code = verdict; + } diff --git a/1172-netfilter-ctnetlink-use-nf-ct-exp-net-in-expectation-dump.patch b/1172-netfilter-ctnetlink-use-nf-ct-exp-net-in-expectation-dump.patch new file mode 100644 index 000000000..31a76ed36 --- /dev/null +++ b/1172-netfilter-ctnetlink-use-nf-ct-exp-net-in-expectation-dump.patch @@ -0,0 +1,37 @@ +From 13e0a1308f7e0d30a339e4d839576bddd419dd69 Mon Sep 17 00:00:00 2001 +From: Pratham Gupta +Date: Mon, 4 May 2026 22:11:57 -0700 +Subject: [PATCH] netfilter: ctnetlink: use nf_ct_exp_net() in expectation dump + +commit a7f57320bbbc67e347bf5fff4b4a9bab980d5956 upstream. + +Commit 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation") +introduced exp->net so RCU-only expectation paths no longer need to +dereference exp->master for netns lookups. + +Commit 3db5647984de ("netfilter: nf_conntrack_expect: skip expectations in other netns via proc") +updated the proc path accordingly, but ctnetlink_exp_dump_table() still +compares against nf_ct_net(exp->master). + +Use nf_ct_exp_net(exp) here as well so the netlink dump path matches +the rest of the March 2026 expectation netns/RCU cleanup. + +Fixes: 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation") +Cc: stable@vger.kernel.org +Signed-off-by: Pratham Gupta +Signed-off-by: Florian Westphal +Signed-off-by: Greg Kroah-Hartman + +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index caf0506..b375374 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3194,7 +3194,7 @@ ctnetlink_exp_dump_table(struct sk_buff *skb, struct netlink_callback *cb) + if (l3proto && exp->tuple.src.l3num != l3proto) + continue; + +- if (!net_eq(nf_ct_net(exp->master), net)) ++ if (!net_eq(nf_ct_exp_net(exp), net)) + continue; + + if (cb->args[1]) { diff --git a/1173-selftests-netfilter-nft-concat-range-sh-add-check-for-double-create-bug.patch b/1173-selftests-netfilter-nft-concat-range-sh-add-check-for-double-create-bug.patch new file mode 100644 index 000000000..278ac197b --- /dev/null +++ b/1173-selftests-netfilter-nft-concat-range-sh-add-check-for-double-create-bug.patch @@ -0,0 +1,116 @@ +From 42c3e9df13adbd54053eaacbfe7337844a243a2b Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 16:57:34 +0200 +Subject: [PATCH] selftests: netfilter: nft_concat_range.sh: add check for + double-create bug + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 94bd247bc25b + +commit 94bd247bc25b7f1560f96e9c912db3ec1fc878ea +Author: Florian Westphal +Date: Wed Sep 10 01:39:48 2025 +0200 + + selftests: netfilter: nft_concat_range.sh: add check for double-create bug + + Add a test case for bug resolved with: + 'netfilter: nft_set_pipapo_avx2: fix skip of expired entries'. + + It passes on nf.git (it uses the generic/C version for insertion + duplicate check) but fails on unpatched nf-next if AVX2 is supported: + + cannot create same element twice 0s [FAIL] + Could create element twice in same transaction + table inet filter { # handle 8 + [..] + elements = { 1.2.3.4 . 1.2.4.1 counter packets 0 bytes 0, + 1.2.4.1 . 1.2.3.4 counter packets 0 bytes 0, + 1.2.3.4 . 1.2.4.1 counter packets 0 bytes 0, + 1.2.4.1 . 1.2.3.4 counter packets 0 bytes 0 } + + Reviewed-by: Stefano Brivio + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/tools/testing/selftests/net/netfilter/nft_concat_range.sh b/tools/testing/selftests/net/netfilter/nft_concat_range.sh +index 20e76b3..ad97c62 100755 +--- a/tools/testing/selftests/net/netfilter/nft_concat_range.sh ++++ b/tools/testing/selftests/net/netfilter/nft_concat_range.sh +@@ -29,7 +29,7 @@ TYPES="net_port port_net net6_port port_proto net6_port_mac net6_port_mac_proto + net6_port_net6_port net_port_mac_proto_net" + + # Reported bugs, also described by TYPE_ variables below +-BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch" ++BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate" + + # List of possible paths to pktgen script from kernel tree for performance tests + PKTGEN_SCRIPT_PATHS=" +@@ -408,6 +408,18 @@ perf_duration 0 + " + + ++TYPE_doublecreate=" ++display cannot create same element twice ++type_spec ipv4_addr . ipv4_addr ++chain_spec ip saddr . ip daddr ++dst addr4 ++proto icmp ++ ++race_repeat 0 ++ ++perf_duration 0 ++" ++ + # Set template for all tests, types and rules are filled in depending on test + set_template=' + flush ruleset +@@ -1900,6 +1912,48 @@ test_bug_avx2_mismatch() + fi + } + ++test_bug_doublecreate() ++{ ++ local elements="1.2.3.4 . 1.2.4.1, 1.2.4.1 . 1.2.3.4" ++ local ret=1 ++ local i ++ ++ setup veth send_"${proto}" set || return ${ksft_skip} ++ ++ add "{ $elements }" || return 1 ++ # expected to work: 'add' on existing should be no-op. ++ add "{ $elements }" || return 1 ++ ++ # 'create' should return an error. ++ if nft create element inet filter test "{ $elements }" 2>/dev/null; then ++ err "Could create an existing element" ++ return 1 ++ fi ++nft -f - </dev/null ++flush set inet filter test ++create element inet filter test { $elements } ++create element inet filter test { $elements } ++EOF ++ ret=$? ++ if [ $ret -eq 0 ]; then ++ err "Could create element twice in one transaction" ++ err "$(nft -a list ruleset)" ++ return 1 ++ fi ++ ++nft -f - </dev/null ++flush set inet filter test ++create element inet filter test { $elements } ++EOF ++ ret=$? ++ if [ $ret -ne 0 ]; then ++ err "Could not flush and re-create element in one transaction" ++ return 1 ++ fi ++ ++ return 0 ++} ++ + test_reported_issues() { + eval test_bug_"${subtest}" + } diff --git a/1174-selftests-netfilter-nft-concat-range-sh-add-check-for-overlap-detection-bug.patch b/1174-selftests-netfilter-nft-concat-range-sh-add-check-for-overlap-detection-bug.patch new file mode 100644 index 000000000..874db2ce9 --- /dev/null +++ b/1174-selftests-netfilter-nft-concat-range-sh-add-check-for-overlap-detection-bug.patch @@ -0,0 +1,102 @@ +From 5f36590a1e7231d0c4575a7e243be208dbf96157 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:09:36 +0200 +Subject: [PATCH] selftests: netfilter: nft_concat_range.sh: add check for + overlap detection bug + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit a675d1caa204 + +commit a675d1caa2041f05f6343fad67b04f8babf32217 +Author: Florian Westphal +Date: Thu Dec 4 12:20:36 2025 +0100 + + selftests: netfilter: nft_concat_range.sh: add check for overlap detection bug + + without 'netfilter: nft_set_pipapo: fix range overlap detection': + + reject overlapping range on add 0s [FAIL] + Returned success for add { 1.2.3.4 . 1.2.4.1-1.2.4.2 } given set: + table inet filter { + [..] + elements = { 1.2.3.4 . 1.2.4.1 counter packets 0 bytes 0, + 1.2.3.0-1.2.3.4 . 1.2.4.2 counter packets 0 bytes 0 } + } + + The element collides with existing ones and was not added, but kernel + returned success to userspace. + + Signed-off-by: Florian Westphal + +Signed-off-by: Florian Westphal + +diff --git a/tools/testing/selftests/net/netfilter/nft_concat_range.sh b/tools/testing/selftests/net/netfilter/nft_concat_range.sh +index ad97c62..394166f 100755 +--- a/tools/testing/selftests/net/netfilter/nft_concat_range.sh ++++ b/tools/testing/selftests/net/netfilter/nft_concat_range.sh +@@ -29,7 +29,7 @@ TYPES="net_port port_net net6_port port_proto net6_port_mac net6_port_mac_proto + net6_port_net6_port net_port_mac_proto_net" + + # Reported bugs, also described by TYPE_ variables below +-BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate" ++BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate insert_overlap" + + # List of possible paths to pktgen script from kernel tree for performance tests + PKTGEN_SCRIPT_PATHS=" +@@ -420,6 +420,18 @@ race_repeat 0 + perf_duration 0 + " + ++TYPE_insert_overlap=" ++display reject overlapping range on add ++type_spec ipv4_addr . ipv4_addr ++chain_spec ip saddr . ip daddr ++dst addr4 ++proto icmp ++ ++race_repeat 0 ++ ++perf_duration 0 ++" ++ + # Set template for all tests, types and rules are filled in depending on test + set_template=' + flush ruleset +@@ -1954,6 +1966,37 @@ EOF + return 0 + } + ++add_fail() ++{ ++ if nft add element inet filter test "$1" 2>/dev/null ; then ++ err "Returned success for add ${1} given set:" ++ err "$(nft -a list set inet filter test )" ++ return 1 ++ fi ++ ++ return 0 ++} ++ ++test_bug_insert_overlap() ++{ ++ local elements="1.2.3.4 . 1.2.4.1" ++ ++ setup veth send_"${proto}" set || return ${ksft_skip} ++ ++ add "{ $elements }" || return 1 ++ ++ elements="1.2.3.0-1.2.3.4 . 1.2.4.1" ++ add_fail "{ $elements }" || return 1 ++ ++ elements="1.2.3.0-1.2.3.4 . 1.2.4.2" ++ add "{ $elements }" || return 1 ++ ++ elements="1.2.3.4 . 1.2.4.1-1.2.4.2" ++ add_fail "{ $elements }" || return 1 ++ ++ return 0 ++} ++ + test_reported_issues() { + eval test_bug_"${subtest}" + } diff --git a/1175-selftests-netfilter-nft-concat-range-sh-add-check-for-flush-reload-bug.patch b/1175-selftests-netfilter-nft-concat-range-sh-add-check-for-flush-reload-bug.patch new file mode 100644 index 000000000..266c010be --- /dev/null +++ b/1175-selftests-netfilter-nft-concat-range-sh-add-check-for-flush-reload-bug.patch @@ -0,0 +1,136 @@ +From 610978474e5b971198d6181be868483d0da92936 Mon Sep 17 00:00:00 2001 +From: Florian Westphal +Date: Wed, 13 May 2026 17:16:14 +0200 +Subject: [PATCH] selftests: netfilter: nft_concat_range.sh: add check for + flush+reload bug + +JIRA: https://redhat.atlassian.net/browse/RHEL-168848 +Upstream Status: commit 6caefcd9491c + +commit 6caefcd9491c408a4d161f7b60c8bb3d956526dd +Author: Florian Westphal +Date: Wed Mar 25 14:10:56 2026 +0100 + + selftests: netfilter: nft_concat_range.sh: add check for flush+reload bug + + This test will fail without + the preceding commit ("netfilter: nft_set_pipapo_avx2: fix match retart if found element is expired"): + + reject overlapping range on add 0s [ OK ] + reload with flush /dev/stdin:59:32-52: Error: Could not process rule: File exists + add element inet filter test { 10.0.0.29 . 10.0.2.29 } + + Reviewed-by: Stefano Brivio + Signed-off-by: Florian Westphal + Signed-off-by: Pablo Neira Ayuso + +Signed-off-by: Florian Westphal + +diff --git a/tools/testing/selftests/net/netfilter/nft_concat_range.sh b/tools/testing/selftests/net/netfilter/nft_concat_range.sh +index 394166f..a387266 100755 +--- a/tools/testing/selftests/net/netfilter/nft_concat_range.sh ++++ b/tools/testing/selftests/net/netfilter/nft_concat_range.sh +@@ -29,7 +29,8 @@ TYPES="net_port port_net net6_port port_proto net6_port_mac net6_port_mac_proto + net6_port_net6_port net_port_mac_proto_net" + + # Reported bugs, also described by TYPE_ variables below +-BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate insert_overlap" ++BUGS="flush_remove_add reload net_port_proto_match avx2_mismatch doublecreate ++ insert_overlap load_flush_load4 load_flush_load8" + + # List of possible paths to pktgen script from kernel tree for performance tests + PKTGEN_SCRIPT_PATHS=" +@@ -432,6 +433,30 @@ race_repeat 0 + perf_duration 0 + " + ++TYPE_load_flush_load4=" ++display reload with flush, 4bit groups ++type_spec ipv4_addr . ipv4_addr ++chain_spec ip saddr . ip daddr ++dst addr4 ++proto icmp ++ ++race_repeat 0 ++ ++perf_duration 0 ++" ++ ++TYPE_load_flush_load8=" ++display reload with flush, 8bit groups ++type_spec ipv4_addr . ipv4_addr ++chain_spec ip saddr . ip daddr ++dst addr4 ++proto icmp ++ ++race_repeat 0 ++ ++perf_duration 0 ++" ++ + # Set template for all tests, types and rules are filled in depending on test + set_template=' + flush ruleset +@@ -1981,6 +2006,12 @@ test_bug_insert_overlap() + { + local elements="1.2.3.4 . 1.2.4.1" + ++ # This test has to be skipped, RHEL-10.2 ntentionally lacks ++ # 7711f4bb4b36 ("netfilter: nft_set_pipapo: fix range overlap detection") ++ # because this fix could cause issues with existing deployments ++ # (ruleset restore failure). ++ return ${ksft_skip} ++ + setup veth send_"${proto}" set || return ${ksft_skip} + + add "{ $elements }" || return 1 +@@ -1997,6 +2028,49 @@ test_bug_insert_overlap() + return 0 + } + ++test_bug_load_flush_load4() ++{ ++ local i ++ ++ setup veth send_"${proto}" set || return ${ksft_skip} ++ ++ for i in $(seq 0 255); do ++ local addelem="add element inet filter test" ++ local j ++ ++ for j in $(seq 0 20); do ++ echo "$addelem { 10.$j.0.$i . 10.$j.1.$i }" ++ echo "$addelem { 10.$j.0.$i . 10.$j.2.$i }" ++ done ++ done > "$tmp" ++ ++ nft -f "$tmp" || return 1 ++ ++ ( echo "flush set inet filter test";cat "$tmp") | nft -f - ++ [ $? -eq 0 ] || return 1 ++ ++ return 0 ++} ++ ++test_bug_load_flush_load8() ++{ ++ local i ++ ++ setup veth send_"${proto}" set || return ${ksft_skip} ++ ++ for i in $(seq 1 100); do ++ echo "add element inet filter test { 10.0.0.$i . 10.0.1.$i }" ++ echo "add element inet filter test { 10.0.0.$i . 10.0.2.$i }" ++ done > "$tmp" ++ ++ nft -f "$tmp" || return 1 ++ ++ ( echo "flush set inet filter test";cat "$tmp") | nft -f - ++ [ $? -eq 0 ] || return 1 ++ ++ return 0 ++} ++ + test_reported_issues() { + eval test_bug_"${subtest}" + } diff --git a/1176-netfilter-nf-conntrack-expect-store-master-tuple-in-expectation.patch b/1176-netfilter-nf-conntrack-expect-store-master-tuple-in-expectation.patch new file mode 100644 index 000000000..a36e06c9e --- /dev/null +++ b/1176-netfilter-nf-conntrack-expect-store-master-tuple-in-expectation.patch @@ -0,0 +1,128 @@ +From 979c13114c0bb6ab9135e2c93e00c79c412aef09 Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Mon, 22 Jun 2026 21:35:14 +0200 +Subject: [PATCH] netfilter: nf_conntrack_expect: store master_tuple in + expectation + +Store master conntrack tuple in the expectation since exp->master might +refer to a different conntrack when accessed from rcu read side lock +area due to typesafe rcu rules. + +Fixes: 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation") +Signed-off-by: Pablo Neira Ayuso + +diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h +index 80f50fd..ec4dd61 100644 +--- a/include/net/netfilter/nf_conntrack_expect.h ++++ b/include/net/netfilter/nf_conntrack_expect.h +@@ -22,16 +22,10 @@ struct nf_conntrack_expect { + /* Hash member */ + struct hlist_node hnode; + +- /* Network namespace */ +- possible_net_t net; +- + /* We expect this tuple, with the following mask */ + struct nf_conntrack_tuple tuple; + struct nf_conntrack_tuple_mask mask; + +-#ifdef CONFIG_NF_CONNTRACK_ZONES +- struct nf_conntrack_zone zone; +-#endif + /* Usage count. */ + refcount_t use; + +@@ -48,9 +42,6 @@ struct nf_conntrack_expect { + /* 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; + +@@ -67,6 +58,15 @@ struct nf_conntrack_expect { + #endif + + struct rcu_head rcu; ++ ++/* Network namespace */ ++ RH_KABI_EXTEND(possible_net_t net) ++#ifdef CONFIG_NF_CONNTRACK_ZONES ++ RH_KABI_EXTEND(struct nf_conntrack_zone zone) ++#endif ++ /* Helper to assign to new connection */ ++ RH_KABI_EXTEND(struct nf_conntrack_helper __rcu *assign_helper) ++ RH_KABI_EXTEND(struct nf_conntrack_tuple master_tuple) + }; + + static inline struct net *nf_ct_exp_net(struct nf_conntrack_expect *exp) +diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c +index 75e53fd..46218c7 100644 +--- a/net/netfilter/nf_conntrack_broadcast.c ++++ b/net/netfilter/nf_conntrack_broadcast.c +@@ -59,6 +59,7 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, + if (exp == NULL) + goto out; + ++ exp->master_tuple = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + exp->tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple; + + helper = rcu_dereference(help->helper); +diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c +index 1bb5bf8..9f74935 100644 +--- a/net/netfilter/nf_conntrack_expect.c ++++ b/net/netfilter/nf_conntrack_expect.c +@@ -352,6 +352,8 @@ void nf_ct_expect_init(struct nf_conntrack_expect *exp, unsigned int class, + exp->tuple.src.l3num = family; + exp->tuple.dst.protonum = proto; + ++ exp->master_tuple = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; ++ + if (saddr) { + memcpy(&exp->tuple.src.u3, saddr, len); + if (sizeof(exp->tuple.src.u3) > len) +diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c +index b375374..8d69397 100644 +--- a/net/netfilter/nf_conntrack_netlink.c ++++ b/net/netfilter/nf_conntrack_netlink.c +@@ -3027,7 +3027,6 @@ static int + ctnetlink_exp_dump_expect(struct sk_buff *skb, + const struct nf_conntrack_expect *exp) + { +- struct nf_conn *master = exp->master; + long timeout = ((long)exp->timeout.expires - (long)jiffies) / HZ; + struct nf_conntrack_helper *helper; + #if IS_ENABLED(CONFIG_NF_NAT) +@@ -3043,9 +3042,7 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb, + goto nla_put_failure; + if (ctnetlink_exp_dump_mask(skb, &exp->tuple, &exp->mask) < 0) + goto nla_put_failure; +- if (ctnetlink_exp_dump_tuple(skb, +- &master->tuplehash[IP_CT_DIR_ORIGINAL].tuple, +- CTA_EXPECT_MASTER) < 0) ++ if (ctnetlink_exp_dump_tuple(skb, &exp->master_tuple, CTA_EXPECT_MASTER) < 0) + goto nla_put_failure; + + #if IS_ENABLED(CONFIG_NF_NAT) +@@ -3058,9 +3055,9 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb, + if (nla_put_be32(skb, CTA_EXPECT_NAT_DIR, htonl(exp->dir))) + goto nla_put_failure; + +- nat_tuple.src.l3num = nf_ct_l3num(master); ++ nat_tuple.src.l3num = exp->master_tuple.src.l3num; + nat_tuple.src.u3 = exp->saved_addr; +- nat_tuple.dst.protonum = nf_ct_protonum(master); ++ nat_tuple.dst.protonum = exp->master_tuple.dst.protonum; + nat_tuple.src.u = exp->saved_proto; + + if (ctnetlink_exp_dump_tuple(skb, &nat_tuple, +@@ -3606,6 +3603,7 @@ ctnetlink_alloc_expect(const struct nlattr * const cda[], struct nf_conn *ct, + #endif + rcu_assign_pointer(exp->helper, helper); + rcu_assign_pointer(exp->assign_helper, assign_helper); ++ exp->master_tuple = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple; + exp->tuple = *tuple; + exp->mask.src.u3 = mask->src.u3; + exp->mask.src.u.all = mask->src.u.all; diff --git a/1177-accel-ivpu-fix-signed-integer-truncation-in-ipc-receive.patch b/1177-accel-ivpu-fix-signed-integer-truncation-in-ipc-receive.patch new file mode 100644 index 000000000..82451f890 --- /dev/null +++ b/1177-accel-ivpu-fix-signed-integer-truncation-in-ipc-receive.patch @@ -0,0 +1,36 @@ +From 4788556d4dd9d717037e385de178974e9649231d Mon Sep 17 00:00:00 2001 +From: Andrzej Kacprowski +Date: Mon, 1 Jun 2026 18:16:43 +0200 +Subject: [PATCH] accel/ivpu: Fix signed integer truncation in IPC receive + +commit d9faef564438d1e4579c692c046603e7ada7bdf4 upstream. + +Fix potential buffer overflow where firmware-supplied data_size is cast +to signed int before being used in min_t(). Large unsigned values +(>= 0x80000000) become negative, causing unsigned wraparound and +oversized memcpy operations that can overflow the stack buffer. + +Change min_t(int, ...) to min() as both values are unsigned and can be +handled by min() without explicit cast. + +Fixes: 3b434a3445ff ("accel/ivpu: Use threaded IRQ to handle JOB done messages") +Cc: stable@vger.kernel.org # v6.12+ +Signed-off-by: Andrzej Kacprowski +Reviewed-by: Karol Wachowski +Signed-off-by: Karol Wachowski +Link: https://patch.msgid.link/20260601161643.229342-1-andrzej.kacprowski@linux.intel.com +Signed-off-by: Greg Kroah-Hartman + +diff --git a/drivers/accel/ivpu/ivpu_ipc.c b/drivers/accel/ivpu/ivpu_ipc.c +index 5f00809..7fea203 100644 +--- a/drivers/accel/ivpu/ivpu_ipc.c ++++ b/drivers/accel/ivpu/ivpu_ipc.c +@@ -276,7 +276,7 @@ int ivpu_ipc_receive(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons, + if (ipc_buf) + memcpy(ipc_buf, rx_msg->ipc_hdr, sizeof(*ipc_buf)); + if (rx_msg->jsm_msg) { +- u32 size = min_t(int, rx_msg->ipc_hdr->data_size, sizeof(*jsm_msg)); ++ u32 size = min(rx_msg->ipc_hdr->data_size, sizeof(*jsm_msg)); + + if (rx_msg->jsm_msg->result != VPU_JSM_STATUS_SUCCESS) { + ivpu_err(vdev, "IPC resp result error: %d\n", rx_msg->jsm_msg->result); diff --git a/1178-kvm-nvmx-put-vmcs12-pages-if-nested-vm-enter-fails-due-to-invalid-guest-state.patch b/1178-kvm-nvmx-put-vmcs12-pages-if-nested-vm-enter-fails-due-to-invalid-guest-state.patch new file mode 100644 index 000000000..3544294fd --- /dev/null +++ b/1178-kvm-nvmx-put-vmcs12-pages-if-nested-vm-enter-fails-due-to-invalid-guest-state.patch @@ -0,0 +1,45 @@ +From 6992f3340e823bbac7290990fb50008cda860209 Mon Sep 17 00:00:00 2001 +From: Aidan Wallace +Date: Wed, 22 Jul 2026 00:14:03 -0500 +Subject: [PATCH] KVM: nVMX: Put vmcs12 pages if nested VM-Enter fails due to + invalid guest state + +JIRA: https://redhat.atlassian.net/browse/RHEL-213327 + +KVM: nVMX: Put vmcs12 pages if nested VM-Enter fails due to invalid guest state + +Put all vmcs12 pages if KVM synthesizes a nested VM-Exit due to invalid +guest while emulating VMLAUNCH or VMRESUME. The invalid guest state path +doesn't use nested_vmx_vmexit() as that API is intended to be used if and +only if L2 is active, and the open coded equivalent neglects to put the +vmcs12 pages. Failure to put the vmcs12 pages leaks any pinned pages +(and/or mappings) if L1 retries VMLAUNCH/VMRESUME. + +Note, the !from_vmenter scenario doesn't suffer the same problem, as +vmx_get_nested_state_pages() only gets/pins/maps the vmcs12 pages if L2 is +active, i.e. if a "full" VM-Exit is guaranteed before KVM will retry +getting vmcs12 pages. + +Fixes: 96c66e87deee ("KVM/nVMX: Use kvm_vcpu_map when mapping the virtual APIC page") +Fixes: 3278e0492554 ("KVM/nVMX: Use kvm_vcpu_map when mapping the posted interrupt descriptor table") +Fixes: fe1911aa443e ("KVM: nVMX: Use kvm_vcpu_map() to get/pin vmcs12's APIC-access page") +Reported-by: Minh Nguyen +Cc: stable@vger.kernel.org +Signed-off-by: Sean Christopherson +Signed-off-by: Paolo Bonzini +(cherry picked from commit 2f2312c422fd2695da772cecb30c69994b795964) +Signed-off-by: Aidan Wallace + +diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c +index 7c55551..5570e0c 100644 +--- a/arch/x86/kvm/vmx/nested.c ++++ b/arch/x86/kvm/vmx/nested.c +@@ -3661,6 +3661,8 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu, + if (!from_vmentry) + return NVMX_VMENTRY_VMEXIT; + ++ nested_put_vmcs12_pages(vcpu); ++ + load_vmcs12_host_state(vcpu, vmcs12); + vmcs12->vm_exit_reason = exit_reason.full; + if (enable_shadow_vmcs || nested_vmx_is_evmptr12_valid(vmx)) diff --git a/1179-kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch b/1179-kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch new file mode 100644 index 000000000..132abd16a --- /dev/null +++ b/1179-kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch @@ -0,0 +1,88 @@ +From 45246f884605223c7ab63808e12c973c63aba44e Mon Sep 17 00:00:00 2001 +From: Aidan Wallace +Date: Wed, 22 Jul 2026 00:15:53 -0500 +Subject: [PATCH] KVM: x86: Check for invalid/obsolete root *after* making MMU + pages available + +JIRA: https://redhat.atlassian.net/browse/RHEL-213327 + +KVM: x86: Check for invalid/obsolete root *after* making MMU pages available + +Check for a "stale" page fault, i.e. for an invalid and/or obsolete root, +after making MMU pages available for the shadow MMU. If reclaiming shadow +pages zaps an in-use root, i.e. marks it invalid, then KVM will attempt to +map memory into an invalid root. On its own, populating an invalid root is +"fine", but because child shadow pages inherit their parent's role, any +children created during the map/fetch will be created as invalid pages, +thus violating KVM's invariant that invalid pages are never on the list of +active MMU pages. + +Note, the underlying flaw has existed since KVM first started tracking +invalid roots in 2008 (commit 2e53d63acba7, "KVM: MMU: ignore zapped root +pagetables"), but the true badness only came along in 2020 (Linux 5.9) +with the invariant that invalid shadow pages can't be on the list of +active pages. + +Note #2, inheriting role.invalid when creating child shadow pages is also +far from ideal; that flaw will be addressed separately. + +Reported-by: Hyunwoo Kim +Fixes: f95eec9bed76 ("KVM: x86/mmu: Don't put invalid SPs back on the list of active pages") +Cc: stable@vger.kernel.org +Signed-off-by: Sean Christopherson +Signed-off-by: Paolo Bonzini +(cherry picked from commit 2abd5287f08319fa35764566b15c6e22cb1068db) +Signed-off-by: Aidan Wallace + +diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c +index 6415891..ce15356 100644 +--- a/arch/x86/kvm/mmu/mmu.c ++++ b/arch/x86/kvm/mmu/mmu.c +@@ -4788,16 +4788,17 @@ static int direct_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault + if (r != RET_PF_CONTINUE) + return r; + +- r = RET_PF_RETRY; + write_lock(&vcpu->kvm->mmu_lock); + +- if (is_page_fault_stale(vcpu, fault)) +- goto out_unlock; +- + r = make_mmu_pages_available(vcpu); + if (r) + goto out_unlock; + ++ if (is_page_fault_stale(vcpu, fault)) { ++ r = RET_PF_RETRY; ++ goto out_unlock; ++ } ++ + r = direct_map(vcpu, fault); + + out_unlock: +diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h +index ed762bb..af220c9 100644 +--- a/arch/x86/kvm/mmu/paging_tmpl.h ++++ b/arch/x86/kvm/mmu/paging_tmpl.h +@@ -827,15 +827,17 @@ static int FNAME(page_fault)(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault + } + #endif + +- r = RET_PF_RETRY; + write_lock(&vcpu->kvm->mmu_lock); + +- if (is_page_fault_stale(vcpu, fault)) +- goto out_unlock; +- + r = make_mmu_pages_available(vcpu); + if (r) + goto out_unlock; ++ ++ if (is_page_fault_stale(vcpu, fault)) { ++ r = RET_PF_RETRY; ++ goto out_unlock; ++ } ++ + r = FNAME(fetch)(vcpu, fault, &walker); + + out_unlock: diff --git a/1180-kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch b/1180-kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch new file mode 100644 index 000000000..42d767de0 --- /dev/null +++ b/1180-kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch @@ -0,0 +1,54 @@ +From 665c3ad7f18b63fba85a93250a75d44a5454734d Mon Sep 17 00:00:00 2001 +From: Aidan Wallace +Date: Wed, 22 Jul 2026 00:43:20 -0500 +Subject: [PATCH] KVM: nVMX: Hide shadow VMCS right after VMCLEAR + +JIRA: https://redhat.atlassian.net/browse/RHEL-213327 + +KVM: nVMX: Hide shadow VMCS right after VMCLEAR + +free_nested() frees the shadow VMCS while vmcs01 still points to it. But +because it is asynchronous with respect to loaded_vmcs_clear(), the vCPU +might migrate before the pointer is cleared and __loaded_vmcs_clear() +may then execute VMCLEAR. + +The VMCS needs to stay attached until its explicit VMCLEAR completes, but +then it can be hidden and the page safely freed. + +Fixes: 355f4fb1405e ("kvm: nVMX: VMCLEAR an active shadow VMCS after last use") +Cc: stable@vger.kernel.org +Signed-off-by: Hyunwoo Kim +Signed-off-by: Paolo Bonzini +(cherry picked from commit 622ebfac01ba4f9c0060cebd41257fe46fc4a0b3) +Signed-off-by: Aidan Wallace + +diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c +index 5570e0c..49d7cda 100644 +--- a/arch/x86/kvm/vmx/nested.c ++++ b/arch/x86/kvm/vmx/nested.c +@@ -331,6 +331,7 @@ static void nested_put_vmcs12_pages(struct kvm_vcpu *vcpu) + static void free_nested(struct kvm_vcpu *vcpu) + { + struct vcpu_vmx *vmx = to_vmx(vcpu); ++ struct vmcs *shadow_vmcs; + + if (WARN_ON_ONCE(vmx->loaded_vmcs != &vmx->vmcs01)) + vmx_switch_vmcs(vcpu, &vmx->vmcs01); +@@ -348,9 +349,15 @@ static void free_nested(struct kvm_vcpu *vcpu) + vmx->nested.current_vmptr = INVALID_GPA; + if (enable_shadow_vmcs) { + vmx_disable_shadow_vmcs(vmx); +- vmcs_clear(vmx->vmcs01.shadow_vmcs); +- free_vmcs(vmx->vmcs01.shadow_vmcs); ++ ++ /* ++ * Keep the pointer visible until after VMCLEAR, so migration ++ * can clear an active shadow VMCS on the old CPU. ++ */ ++ shadow_vmcs = vmx->vmcs01.shadow_vmcs; ++ vmcs_clear(shadow_vmcs); + vmx->vmcs01.shadow_vmcs = NULL; ++ free_vmcs(shadow_vmcs); + } + kfree(vmx->nested.cached_vmcs12); + vmx->nested.cached_vmcs12 = NULL; diff --git a/1181-kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-checking-max-mapping-level.patch b/1181-kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-checking-max-mapping-level.patch new file mode 100644 index 000000000..fa004dbdd --- /dev/null +++ b/1181-kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-checking-max-mapping-level.patch @@ -0,0 +1,131 @@ +From 25252dcdc03485bdfaaf4ff5bae084b5e6176ac0 Mon Sep 17 00:00:00 2001 +From: Aidan Wallace +Date: Wed, 22 Jul 2026 01:05:50 -0500 +Subject: [PATCH] KVM: x86/mmu: Ensure hugepage is in by slot before checking + max mapping level + +JIRA: https://redhat.atlassian.net/browse/RHEL-213327 +CVE: CVE-2026-63807 + +KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level + +When recovering hugepages in the shadow MMU, verify that the base gfn of +the shadow page is actually contained within the target memslot, *before* +querying the max mapping level given the shadow page's gfn. Failure to +pre-check the validity of the gfn can lead to an out-of-bounds access to +the slot's lpage_info (which typically manifests as a host #PF because the +lpage_info is vmalloc'd) if the guest creates a hugepage mapping (in its +PTEs) that extends "below" the bounds of a memslot. + +When faulting in memory for a guest, and the size of the guest mapping is +greater than KVM's (current) max mapping, then KVM will create a "direct" +shadow page (direct in that there are no gPTEs to shadow, and so the target +gfn is a direct calculation given the base gfn of the shadow page). The +hugepage recovery flow looks for such direct shadow pages, as forcing 4KiB +mappings when dirty logging generates the guest > host mapping size case. +When the 4KiB restriction is lifted, then KVM can replace the shadow page +with a hugepage. + +But if KVM originally used a smaller mapping than the guest because the +range of memory covered by the guest hugepage exceeds the bounds of a +memslot, then KVM will link a direct shadow page with a gfn that is outside +the bounds of the memslot being used to fault in memory. The rmap entry +added for the leaf mapping is correct and within bounds, but the gfn of the +leaf SPTE's parent shadow page will be out of bounds. + + BUG: unable to handle page fault for address: ffffc90000806ffc + #PF: supervisor read access in kernel mode + #PF: error_code(0x0000) - not-present page + PGD 100000067 P4D 100000067 PUD 1002a7067 PMD 10612f067 PTE 0 + Oops: Oops: 0000 [#1] SMP + CPU: 13 UID: 1000 PID: 757 Comm: mmu_stress_test Not tainted 7.1.0-rc1-48ce1e26eace-x86_pir_to_irr_comments-vm #341 PREEMPT + Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 + RIP: 0010:kvm_mmu_max_mapping_level+0x79/0x2b0 [kvm] + Call Trace: + + kvm_mmu_recover_huge_pages+0x21b/0x320 [kvm] + kvm_set_memslot+0x1ee/0x590 [kvm] + kvm_set_memory_region.part.0+0x3a1/0x4d0 [kvm] + kvm_vm_ioctl+0x9bf/0x15d0 [kvm] + __x64_sys_ioctl+0x8a/0xd0 + do_syscall_64+0xb7/0xbb0 + entry_SYSCALL_64_after_hwframe+0x4b/0x53 + RIP: 0033:0x7f21c0f1a9bf + + +Don't bother pre-checking the bounds of the potential hugepage, i.e. don't +check that e.g. sp->gfn + KVM_PAGES_PER_HPAGE(sp->role.level + 1) is also +within the memslot, as the checks performed by kvm_mmu_max_mapping_level() +are a superset of the basic bounds checks. I.e. pre-checking the full +range would be a dubious micro-optimization. + +Fixes: 9eba50f8d7fc ("KVM: x86/mmu: Consult max mapping level when zapping collapsible SPTEs") +Cc: stable@vger.kernel.org +Cc: David Matlack +Cc: James Houghton +Cc: Alexander Bulekov +Cc: Fred Griffoul +Cc: Alexander Graf +Cc: David Woodhouse +Cc: Filippo Sironi +Cc: Ivan Orlov +Signed-off-by: Sean Christopherson +Signed-off-by: Paolo Bonzini +(cherry picked from commit ef057cbf825e03b63f6edf5980f96abf3c53089d) +Signed-off-by: Aidan Wallace + +diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c +index ce15356..04591cf 100644 +--- a/arch/x86/kvm/mmu/mmu.c ++++ b/arch/x86/kvm/mmu/mmu.c +@@ -7163,13 +7163,19 @@ static bool kvm_mmu_zap_collapsible_spte(struct kvm *kvm, + sp = sptep_to_sp(sptep); + + /* +- * We cannot do huge page mapping for indirect shadow pages, +- * which are found on the last rmap (level = 1) when not using +- * tdp; such shadow pages are synced with the page table in +- * the guest, and the guest page table is using 4K page size +- * mapping if the indirect sp has level = 1. ++ * Direct shadow page can be replaced by a hugepage if the host ++ * mapping level allows it and the memslot maps all of the host ++ * hugepage. Note! If the memslot maps only part of the ++ * hugepage, sp->gfn may be below slot->base_gfn, and querying ++ * the max mapping level would cause an out-of-bounds lpage_info ++ * access. So the gfn bounds check *must* be done first. ++ * ++ * Indirect shadow pages are created when the guest page tables ++ * are using 4K pages. Since the host mapping is always ++ * constrained by the page size in the guest, indirect shadow ++ * pages are never collapsible. + */ +- if (sp->role.direct && ++ if (sp->role.direct && is_gfn_in_memslot(slot, sp->gfn) && + sp->role.level < kvm_mmu_max_mapping_level(kvm, slot, sp->gfn)) { + kvm_zap_one_rmap_spte(kvm, rmap_head, sptep); + +diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h +index 83ed7a0..2790aed 100644 +--- a/include/linux/kvm_host.h ++++ b/include/linux/kvm_host.h +@@ -1768,6 +1768,11 @@ void kvm_unregister_irq_ack_notifier(struct kvm *kvm, + struct kvm_irq_ack_notifier *kian); + bool kvm_arch_irqfd_allowed(struct kvm *kvm, struct kvm_irqfd *args); + ++static inline bool is_gfn_in_memslot(const struct kvm_memory_slot *slot, gfn_t gfn) ++{ ++ return gfn >= slot->base_gfn && gfn < slot->base_gfn + slot->npages; ++} ++ + /* + * Returns a pointer to the memslot if it contains gfn. + * Otherwise returns NULL. +@@ -1778,7 +1783,7 @@ try_get_memslot(struct kvm_memory_slot *slot, gfn_t gfn) + if (!slot) + return NULL; + +- if (gfn >= slot->base_gfn && gfn < slot->base_gfn + slot->npages) ++ if (is_gfn_in_memslot(slot, gfn)) + return slot; + else + return NULL; diff --git a/1182-kvm-x86-hyper-v-validate-all-gvas-during-pv-tlb-flush.patch b/1182-kvm-x86-hyper-v-validate-all-gvas-during-pv-tlb-flush.patch new file mode 100644 index 000000000..143bcf109 --- /dev/null +++ b/1182-kvm-x86-hyper-v-validate-all-gvas-during-pv-tlb-flush.patch @@ -0,0 +1,65 @@ +From 8b624c005e6d08f4ee577aa8beb4852420d57c3f Mon Sep 17 00:00:00 2001 +From: Maxim Levitsky +Date: Fri, 24 Apr 2026 11:43:56 -0400 +Subject: [PATCH] KVM: x86: hyper-v: Validate all GVAs during PV TLB flush + +JIRA: https://issues.redhat.com/browse/RHEL-151869 + +commit a5264387c2ee42fca92ac792199008fc60ee82f1 +Author: Manuel Andreas +Date: Thu Feb 19 21:05:49 2026 +0100 + + KVM: x86: hyper-v: Validate all GVAs during PV TLB flush + + In KVM guests with Hyper-V hypercalls enabled, the hypercalls + HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST and HVCALL_FLUSH_VIRTUAL_ADDRESS_LIST_EX + allow a guest to request invalidation of portions of a virtual TLB. + For this, the hypercall parameter includes a list of GVAs that are supposed + to be invalidated. + + Currently, only the base GVA is checked to be canonical. In reality, this + check needs to be performed for the entire range of GVAs, as checking only + the base GVA enables guests running on Intel hardware to trigger a + WARN_ONCE in the host (see Fixes commit below). + + Move the check for non-canonical addresses to be performed for every GVA + of the supplied range to avoid the splat, and to be more in line with the + Hyper-V specification, since, although unlikely, a range starting with an + invalid GVA may still contain GVAs that are valid. + + Fixes: fa787ac07b3c ("KVM: x86/hyper-v: Skip non-canonical addresses during PV TLB flush") + Signed-off-by: Manuel Andreas + Reviewed-by: Vitaly Kuznetsov + Link: https://patch.msgid.link/00a7a31b-573b-4d92-91f8-7d7e2f88ea48@tum.de + [sean: massage changelog] + Signed-off-by: Sean Christopherson + Signed-off-by: Paolo Bonzini + +Signed-off-by: Maxim Levitsky + +diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c +index 75cd48d..4ea49a4 100644 +--- a/arch/x86/kvm/hyperv.c ++++ b/arch/x86/kvm/hyperv.c +@@ -1983,16 +1983,17 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) + if (entries[i] == KVM_HV_TLB_FLUSHALL_ENTRY) + goto out_flush_all; + +- if (is_noncanonical_invlpg_address(entries[i], vcpu)) +- continue; +- + /* + * Lower 12 bits of 'address' encode the number of additional + * pages to flush. + */ + gva = entries[i] & PAGE_MASK; +- for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1; j++) ++ for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1; j++) { ++ if (is_noncanonical_invlpg_address(gva + j * PAGE_SIZE, vcpu)) ++ continue; ++ + kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE); ++ } + + ++vcpu->stat.tlb_flush; + } diff --git a/1183-kvm-svm-make-svm-flush-tlb-gva-do-a-full-asid-flush-if-npt-enabled.patch b/1183-kvm-svm-make-svm-flush-tlb-gva-do-a-full-asid-flush-if-npt-enabled.patch new file mode 100644 index 000000000..7602c88ca --- /dev/null +++ b/1183-kvm-svm-make-svm-flush-tlb-gva-do-a-full-asid-flush-if-npt-enabled.patch @@ -0,0 +1,217 @@ +From 26505e1b5b546e2fa9a0296b951ca158460c72d8 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Thu, 23 Jul 2026 10:15:22 +0200 +Subject: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT + enabled + +Red Hat is seeing multiple reports of Windows memory corruptions +(and consequent BSODs) with hv-tlbflush=on, on AMD processors only. +The crashes, while extremely rare, happen even with a stock configuration, +but with Driver Verifier enabled they can be detected after approximately +200 VM hours. In particular, Alexander Lougovski measured the following: + +- on AMD Turin, 15 crashes in 3300 VM hours + +- on AMD Milan, 2 crashes in 500 VM hours (there are fewer hours + here due to the host being smaller) + +- on Intel Sapphire Rapids, 0 crashes in 8000 VM hours + +- on AMD Turin with full TLB flush (not exactly this patch but + similar), no crashes in ~2 weeks of run time which should also + be ~7000 VM hours + +For Turin, the microcode version was 0x0b002162, which (assuming +this is the same issue) should not be affected by the problem listed in +https://knowledge.broadcom.com/external/article/419026/bsod-on-virtual-machines-running-on-amd.html; +on the other hand that problem should not apply to earlier processors. +AMD has not provided any information or analysis yet, and when we asked +we didn't know yet that it reproduced on Milan as well. + +As to the workload, Alexander threw more or less everything at the same +time at the VM: + +- a full Windows Defender scan every 30 minutes + +- a disk I/O job + +- a loop doing repeated mmap of system files (mostly to hope that + it triggers some consistency check in the Windows memory manager) + +- SQL Express 2022 + StressDB (1.6M rows), with the host doing queries + (75% write/25% read) via sqlcmd + +Driver Verifier is able to detect BSODs more or less at the same time as +the pages are freed. They mostly happen in the Windows Defender filter +driver, but occasionally also in the networking stack (e.g., afd.sys) +or elsewhere in the filesystem stack (e.g., fltmgr.sys). + +The flush is issued from kvm_hv_vcpu_flush_tlb(), which receives the +cross-CPU requests from the Hyper-V TLB flush hypercalls via a kfifo +and is invoked by the KVM_REQ_HV_TLB_FLUSH request. The mechanism is +the same for both Intel and AMD, and the handler for both vendors is +a simple INVVPID(ADDR)/INVLPGA instruction. + +Because the request is handled on the destination CPU, there is a question +of what happens if the VM is migrated across physical CPUs. In that case, +the INVLPGA instruction would use a stale svm->vmcb->control.asid; but +if anything that might do an *unnecessary* flush (on an asid that's being +used for another VM) and then pre_svm_run() would force a full TLB rebuild. + +So, for lack of better ideas, this patch forces a full ASID bump in +svm_flush_tlb_gva(). To avoid paying the price on Intel and also to +avoid unnecessary loops on AMD, the flush_tlb_gva op now returns whether +it did a full flush or not; kvm_hv_vcpu_flush_tlb() takes note and exits +its loops immediately. While there is an obvious performance impact, +about half of the benefit from Hyper-V tlbflush is preserved (10% vs. 20% +on the SQL Server workload). + +kvm_mmu_invalidate_addr() is the only other caller of the flush_tlb_gva op. +The change would have a performance impact on every intercepted INVLPG and, +for nested SVM, on every L1 INVLPGA. For INVLPGA specifically, this covers +the same suspected issue but for nested hypervisors, so it is correct to +apply the workaround; for INVLPG on shadow paging, instead, the impact +would be stronger and, due to lack of data, for now the use of INVLPGA is +left in place in svm_flush_tlb_gva(). + +Analyzed-by: Vitaly Kuznetsov +Analyzed-by: Alexander Lougovski +Signed-off-by: Paolo Bonzini + +diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h +index 871c7ff..d378cdf 100644 +--- a/arch/x86/include/asm/kvm_host.h ++++ b/arch/x86/include/asm/kvm_host.h +@@ -1761,7 +1761,7 @@ struct kvm_x86_ops { + * Can potentially get non-canonical addresses through INVLPGs, which + * the implementation may choose to ignore if appropriate. + */ +- void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr); ++ void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr, bool *full); + + /* + * Flush any TLB entries created by the guest. Like tlb_flush_gva(), +diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c +index 4ea49a4..861a5d8 100644 +--- a/arch/x86/kvm/hyperv.c ++++ b/arch/x86/kvm/hyperv.c +@@ -1971,6 +1971,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) + u64 entries[KVM_HV_TLB_FLUSH_FIFO_SIZE]; + int i, j, count; + gva_t gva; ++ bool full = false; + + if (!tdp_enabled || !hv_vcpu) + return -EINVAL; +@@ -1979,7 +1980,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) + + count = kfifo_out(&tlb_flush_fifo->entries, entries, KVM_HV_TLB_FLUSH_FIFO_SIZE); + +- for (i = 0; i < count; i++) { ++ for (i = 0; i < count && !full; i++) { + if (entries[i] == KVM_HV_TLB_FLUSHALL_ENTRY) + goto out_flush_all; + +@@ -1988,11 +1989,11 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu) + * pages to flush. + */ + gva = entries[i] & PAGE_MASK; +- for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1; j++) { ++ for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1 && !full; j++) { + if (is_noncanonical_invlpg_address(gva + j * PAGE_SIZE, vcpu)) + continue; + +- kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE); ++ kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE, &full); + } + + ++vcpu->stat.tlb_flush; +diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c +index 04591cf..66bda34 100644 +--- a/arch/x86/kvm/mmu/mmu.c ++++ b/arch/x86/kvm/mmu/mmu.c +@@ -6434,7 +6434,7 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu, + if (is_noncanonical_invlpg_address(addr, vcpu)) + return; + +- kvm_x86_call(flush_tlb_gva)(vcpu, addr); ++ kvm_x86_call(flush_tlb_gva)(vcpu, addr, NULL); + } + + if (!mmu->sync_spte) +diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c +index 23cb4be..4b80716 100644 +--- a/arch/x86/kvm/svm/svm.c ++++ b/arch/x86/kvm/svm/svm.c +@@ -4021,11 +4021,24 @@ static void svm_flush_tlb_all(struct kvm_vcpu *vcpu) + svm_flush_tlb_asid(vcpu); + } + +-static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva) ++static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva, bool *full) + { + struct vcpu_svm *svm = to_svm(vcpu); + +- invlpga(gva, svm->vmcb->control.asid); ++ /* ++ * INVLPGA has had errata on Genoa and Turin, and even on older ++ * generations there were reports of Windows BSODs if INVLPGA ++ * was used for Hyper-V tlbflush. Use it only for shadow paging ++ * where it seems to be okay. ++ */ ++ if (!npt_enabled) { ++ invlpga(gva, svm->vmcb->control.asid); ++ return; ++ } ++ ++ svm_flush_tlb_asid(vcpu); ++ if (full) ++ *full = true; + } + + static inline void sync_cr8_to_lapic(struct kvm_vcpu *vcpu) +diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c +index dbab1c1..f99cae0 100644 +--- a/arch/x86/kvm/vmx/main.c ++++ b/arch/x86/kvm/vmx/main.c +@@ -530,12 +530,12 @@ static void vt_flush_tlb_current(struct kvm_vcpu *vcpu) + vmx_flush_tlb_current(vcpu); + } + +-static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr) ++static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full) + { + if (is_td_vcpu(vcpu)) + return; + +- vmx_flush_tlb_gva(vcpu, addr); ++ vmx_flush_tlb_gva(vcpu, addr, full); + } + + static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu) +diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c +index 2e68776..5fd9614 100644 +--- a/arch/x86/kvm/vmx/vmx.c ++++ b/arch/x86/kvm/vmx/vmx.c +@@ -3202,7 +3202,7 @@ void vmx_flush_tlb_current(struct kvm_vcpu *vcpu) + vpid_sync_context(vmx_get_current_vpid(vcpu)); + } + +-void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr) ++void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full) + { + /* + * vpid_sync_vcpu_addr() is a nop if vpid==0, see the comment in +diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h +index 2b3424f..5fa4e7c 100644 +--- a/arch/x86/kvm/vmx/x86_ops.h ++++ b/arch/x86/kvm/vmx/x86_ops.h +@@ -82,7 +82,7 @@ void vmx_set_rflags(struct kvm_vcpu *vcpu, unsigned long rflags); + bool vmx_get_if_flag(struct kvm_vcpu *vcpu); + void vmx_flush_tlb_all(struct kvm_vcpu *vcpu); + void vmx_flush_tlb_current(struct kvm_vcpu *vcpu); +-void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr); ++void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full); + void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu); + void vmx_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask); + u32 vmx_get_interrupt_shadow(struct kvm_vcpu *vcpu); diff --git a/1184-net-sched-act-api-use-rcu-with-deferred-freeing-for-action-lifecycle.patch b/1184-net-sched-act-api-use-rcu-with-deferred-freeing-for-action-lifecycle.patch new file mode 100644 index 000000000..d637a5e80 --- /dev/null +++ b/1184-net-sched-act-api-use-rcu-with-deferred-freeing-for-action-lifecycle.patch @@ -0,0 +1,85 @@ +From 5dd51e09020c65aa53cf128e5e3517cd53b3c113 Mon Sep 17 00:00:00 2001 +From: Jamal Hadi Salim +Date: Sun, 31 May 2026 12:08:12 -0400 +Subject: [PATCH] net/sched: act_api: use RCU with deferred freeing for action + lifecycle + +[ Upstream commit 5057e1aca011e51ef51498c940ef96f3d3e8a305 ] + +When NEWTFILTER and DELFILTER are run concurrently it is possible to create a +race with an associated action. + +Let's illustrate with CPU0 running NEWTFILTER and CPU1 running DELFILTER: + + 0: mutex_lock() <-- holds the idr lock + 0: rcu_read_lock() + 0: p = idr_find(idr, index) <-- action p is valid (RCU protects IDR) + 0: mutex_unlock() <-- releases the idr lock + 1: refcount_dec_and_mutex_lock() <-- refcnt 1->0, mutex held + 1: idr_remove(idr, index) <-- Action removed from IDR + 1: mutex_unlock() <-- mutex released allowing us to delete the action + 1: tcf_action_cleanup(p); kfree(p) <-- Kfrees p immediately, no deferral + 0: refcount_inc_not_zero(&p->tcfa_refcnt) <-- ouch, UAF p points to freed memory + +This patch fixes the race condition between NEWTFILTER and DELFILTER by +adding struct rcu_head to tc_action used in the deferral and introducing a +call_rcu() in the delete path to defer the final kfree(). + +Note: this is a revert of commit d7fb60b9cafb ("net_sched: get rid of tcfa_rcu") +but also modernization/simplification to directly use kfree_rcu(). + +Let's illustrate the new restored code path: + + 0: rcu_read_lock() + 1: refcount_dec_and_mutex_lock() <-- refcnt 1->0, mutex held + 1: idr_remove(idr, index) + 1: mutex_unlock() + 1: call_rcu(&p->tcfa_rcu, tcf_action_rcu_free) <-- defer kfree after grace period + 0: p = idr_find(idr, index) + 0: refcount_inc_not_zero(&p->tcfa_refcnt) <-- fails, refcnt already 0 + 1: rcu_read_unlock() <-- release so freeing can run after grace period + +After CPU1 calls idr_remove(), the object is no longer reachable through the IDR. +CPU0's subsequent idr_find() will return NULL, and even if it still held a +stale pointer, the immediate kfree() is now deferred until after the RCU grace +period, so no UAF can occur. + +Fixes: d7fb60b9cafb ("net_sched: get rid of tcfa_rcu") +Suggested-by: Jakub Kicinski +Reported-by: Kyle Zeng +Tested-by: Victor Nogueira +Tested-by: syzbot@syzkaller.appspotmail.com +Signed-off-by: Jamal Hadi Salim +Tested-by: Kyle Zeng +Reviewed-by: Pedro Tammela +Reviewed-by: Eric Dumazet +Reviewed-by: Victor Nogueira +Link: https://patch.msgid.link/20260531160812.68020-1-jhs@mojatatu.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin + +diff --git a/net/sched/act_api.c b/net/sched/act_api.c +index 8397900..8b1402c 100644 +--- a/net/sched/act_api.c ++++ b/net/sched/act_api.c +@@ -112,11 +112,6 @@ struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action, + } + EXPORT_SYMBOL(tcf_action_set_ctrlact); + +-/* XXX: For standalone actions, we don't need a RCU grace period either, because +- * actions are always connected to filters and filters are already destroyed in +- * RCU callbacks, so after a RCU grace period actions are already disconnected +- * from filters. Readers later can not find us. +- */ + static void free_tcf(struct tc_action *p) + { + struct tcf_chain *chain = rcu_dereference_protected(p->goto_chain, 1); +@@ -129,7 +124,7 @@ static void free_tcf(struct tc_action *p) + if (chain) + tcf_chain_put_by_act(chain); + +- kfree(p); ++ kfree_rcu_mightsleep(p); + } + + static void offload_action_hw_count_set(struct tc_action *act, diff --git a/1185-perf-aux-fix-page-uaf-in-map-range.patch b/1185-perf-aux-fix-page-uaf-in-map-range.patch new file mode 100644 index 000000000..330aa2532 --- /dev/null +++ b/1185-perf-aux-fix-page-uaf-in-map-range.patch @@ -0,0 +1,51 @@ +From 5948aaf64f81f217a25dcc2bf6c0779bca19566c Mon Sep 17 00:00:00 2001 +From: Lee Jia Jie +Date: Thu, 9 Jul 2026 21:56:19 +0800 +Subject: [PATCH] perf/aux: Fix page UAF in map_range() + +map_range() reads rb->aux_pages[], rb->aux_nr_pages and rb->aux_pgoff via +perf_mmap_to_page() while holding only event->mmap_mutex. Those fields are +serialized by rb->aux_mutex, and mmap_mutex is per event. + +Thus, two events sharing one rb via PERF_EVENT_IOC_SET_OUTPUT can race +rb_alloc_aux() with map_range(), leading to a page-UAF scenario as follows: + + CPU 0 CPU 1 + ===== ===== + rb_alloc_aux() map_range() + [1]: allocate rb->aux_pages[0] + [2]: rb->aux_nr_pages++ + [3]: perf_mmap_to_page() + returns rb->aux_pages[0] + [4]: map it as VM_PFNMAP + [5]: rb->aux_pgoff = 1 + + munmap the page + [6]: free rb->aux_pages[0] + +Pages mapped as VM_PFNMAP have no refcount protection, so CPU 1 holds a +mapping to a freed physical frame. + +Fix this by taking rb->aux_mutex across the page walk in map_range(). + +Fixes: b709eb872e19 ("perf: map pages in advance") +Signed-off-by: Lee Jia Jie +Signed-off-by: Ingo Molnar +Cc: stable@vger.kernel.org +Cc: Peter Zijlstra +Cc: Arnaldo Carvalho de Melo +Cc: Namhyung Kim + +diff --git a/kernel/events/core.c b/kernel/events/core.c +index 43cee52..e2e7bb8 100644 +--- a/kernel/events/core.c ++++ b/kernel/events/core.c +@@ -6896,6 +6896,8 @@ static int map_range(struct perf_buffer *rb, struct vm_area_struct *vma) + int err = 0; + unsigned long pagenum; + ++ guard(mutex)(&rb->aux_mutex); ++ + /* + * We map this as a VM_PFNMAP VMA. + * diff --git a/1186-scsi-target-iscsi-bound-iscsi-encode-text-output-appends-to-rsp-buf.patch b/1186-scsi-target-iscsi-bound-iscsi-encode-text-output-appends-to-rsp-buf.patch new file mode 100644 index 000000000..6c78d27f2 --- /dev/null +++ b/1186-scsi-target-iscsi-bound-iscsi-encode-text-output-appends-to-rsp-buf.patch @@ -0,0 +1,200 @@ +From cb0d8148eaca238c8ee3e3a1c3351503194ab167 Mon Sep 17 00:00:00 2001 +From: Maurizio Lombardi +Date: Tue, 9 Jun 2026 07:52:02 +0200 +Subject: [PATCH] scsi: target: iscsi: Bound iscsi_encode_text_output() appends + to rsp_buf + +JIRA: https://redhat.atlassian.net/browse/RHEL-163760 + +iscsi_encode_text_output() concatenates "key=value\0" records into +login->rsp_buf, an 8192-byte kzalloc(MAX_KEY_VALUE_PAIRS) buffer +allocated in iscsit_alloc_login_setup_buffer(). The three sprintf() call +sites in this function (lines 1398, 1411, 1424 in v7.1-rc2) never check +the remaining buffer capacity: + + *length += sprintf(output_buf, "%s=%s", er->key, er->value); + *length += 1; + output_buf = textbuf + *length; + +The 8192-byte ceiling at iscsi_target_check_login_request() bounds the +*input* Login PDU payload, but a single PDU can carry up to 2048 minimal +four-byte "a=b\0" pairs, each unknown key expanding to a 16-byte +"a=NotUnderstood\0" output record via iscsi_add_notunderstood_response(). +2048 * 16 = 32 KiB of output into an 8 KiB buffer, producing a ~24 KiB +heap overrun in the kmalloc-8k slab. + +The fix introduces a static iscsi_encode_text_record() helper that uses +snprintf() with a per-call bounds check against the remaining buffer, +and threads a u32 textbuf_size parameter through +iscsi_encode_text_output(). Both call sites in +iscsi_target_handle_csg_zero() (PHASE_SECURITY) and +iscsi_target_handle_csg_one() (PHASE_OPERATIONAL) pass +MAX_KEY_VALUE_PAIRS. On overflow the encoder logs the condition, calls +iscsi_release_extra_responses() to drop queued records, and returns -1; +both caller sites now emit ISCSI_STATUS_CLS_INITIATOR_ERR / +ISCSI_LOGIN_STATUS_INIT_ERR via iscsit_tx_login_rsp() before returning, +so the initiator sees an explicit failed-login response rather than a +silent connection drop. (Prior to this patch only the PHASE_OPERATIONAL +caller did that; the PHASE_SECURITY caller is converted to the same +shape.) + +Fixes: e48354ce078c ("iscsi-target: Add iSCSI fabric support for target v4.1") +Cc: stable@vger.kernel.org +Assisted-by: Claude:claude-opus-4-7 +Signed-off-by: Michael Bommarito +Tested-by: John Garry +Reviewed-by: John Garry +Signed-off-by: Martin K. Petersen +(cherry picked from commit bf33e01f88388c43e285492a63e539df6ffed64c) +Signed-off-by: Maurizio Lombardi + +diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c +index 108e253..a1fb869 100644 +--- a/drivers/target/iscsi/iscsi_target_nego.c ++++ b/drivers/target/iscsi/iscsi_target_nego.c +@@ -899,10 +899,14 @@ static int iscsi_target_handle_csg_zero( + SENDER_TARGET, + login->rsp_buf, + &login->rsp_length, ++ MAX_KEY_VALUE_PAIRS, + conn->param_list, + conn->tpg->tpg_attrib.login_keys_workaround); +- if (ret < 0) ++ if (ret < 0) { ++ iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_INITIATOR_ERR, ++ ISCSI_LOGIN_STATUS_INIT_ERR); + return -1; ++ } + + if (!iscsi_check_negotiated_keys(conn->param_list)) { + bool auth_required = iscsi_conn_auth_required(conn); +@@ -986,6 +990,7 @@ static int iscsi_target_handle_csg_one(struct iscsit_conn *conn, struct iscsi_lo + SENDER_TARGET, + login->rsp_buf, + &login->rsp_length, ++ MAX_KEY_VALUE_PAIRS, + conn->param_list, + conn->tpg->tpg_attrib.login_keys_workaround); + if (ret < 0) { +diff --git a/drivers/target/iscsi/iscsi_target_parameters.c b/drivers/target/iscsi/iscsi_target_parameters.c +index 1d4e178..14eef58 100644 +--- a/drivers/target/iscsi/iscsi_target_parameters.c ++++ b/drivers/target/iscsi/iscsi_target_parameters.c +@@ -1371,19 +1371,42 @@ int iscsi_decode_text_input( + return -1; + } + ++/* ++ * Append "key=value" plus a trailing NUL into @textbuf at *@length. ++ * Returns 0 on success and advances *@length, or -EMSGSIZE if the ++ * record (including the NUL) would not fit in the remaining buffer. ++ */ ++static int iscsi_encode_text_record(char *textbuf, u32 *length, ++ u32 textbuf_size, ++ const char *key, const char *value) ++{ ++ int n; ++ u32 avail; ++ ++ if (*length >= textbuf_size) ++ return -EMSGSIZE; ++ ++ avail = textbuf_size - *length; ++ n = snprintf(textbuf + *length, avail, "%s=%s", key, value); ++ if (n < 0 || (u32)n + 1 > avail) ++ return -EMSGSIZE; ++ ++ *length += n + 1; ++ return 0; ++} ++ + int iscsi_encode_text_output( + u8 phase, + u8 sender, + char *textbuf, + u32 *length, ++ u32 textbuf_size, + struct iscsi_param_list *param_list, + bool keys_workaround) + { +- char *output_buf = NULL; + struct iscsi_extra_response *er; + struct iscsi_param *param; +- +- output_buf = textbuf + *length; ++ int ret; + + if (iscsi_enforce_integrity_rules(phase, param_list) < 0) + return -1; +@@ -1395,10 +1418,12 @@ int iscsi_encode_text_output( + !IS_PSTATE_RESPONSE_SENT(param) && + !IS_PSTATE_REPLY_OPTIONAL(param) && + (param->phase & phase)) { +- *length += sprintf(output_buf, "%s=%s", +- param->name, param->value); +- *length += 1; +- output_buf = textbuf + *length; ++ ret = iscsi_encode_text_record(textbuf, length, ++ textbuf_size, ++ param->name, ++ param->value); ++ if (ret < 0) ++ goto err_overflow; + SET_PSTATE_RESPONSE_SENT(param); + pr_debug("Sending key: %s=%s\n", + param->name, param->value); +@@ -1408,10 +1433,12 @@ int iscsi_encode_text_output( + !IS_PSTATE_ACCEPTOR(param) && + !IS_PSTATE_PROPOSER(param) && + (param->phase & phase)) { +- *length += sprintf(output_buf, "%s=%s", +- param->name, param->value); +- *length += 1; +- output_buf = textbuf + *length; ++ ret = iscsi_encode_text_record(textbuf, length, ++ textbuf_size, ++ param->name, ++ param->value); ++ if (ret < 0) ++ goto err_overflow; + SET_PSTATE_PROPOSER(param); + iscsi_check_proposer_for_optional_reply(param, + keys_workaround); +@@ -1421,14 +1448,21 @@ int iscsi_encode_text_output( + } + + list_for_each_entry(er, ¶m_list->extra_response_list, er_list) { +- *length += sprintf(output_buf, "%s=%s", er->key, er->value); +- *length += 1; +- output_buf = textbuf + *length; ++ ret = iscsi_encode_text_record(textbuf, length, textbuf_size, ++ er->key, er->value); ++ if (ret < 0) ++ goto err_overflow; + pr_debug("Sending key: %s=%s\n", er->key, er->value); + } + iscsi_release_extra_responses(param_list); + + return 0; ++ ++err_overflow: ++ pr_err("iSCSI login response buffer (%u bytes) exhausted, dropping login.\n", ++ textbuf_size); ++ iscsi_release_extra_responses(param_list); ++ return -1; + } + + int iscsi_check_negotiated_keys(struct iscsi_param_list *param_list) +diff --git a/drivers/target/iscsi/iscsi_target_parameters.h b/drivers/target/iscsi/iscsi_target_parameters.h +index c672a97..38d2238 100644 +--- a/drivers/target/iscsi/iscsi_target_parameters.h ++++ b/drivers/target/iscsi/iscsi_target_parameters.h +@@ -43,7 +43,7 @@ extern struct iscsi_param *iscsi_find_param_from_key(char *, struct iscsi_param_ + extern int iscsi_extract_key_value(char *, char **, char **); + extern int iscsi_update_param_value(struct iscsi_param *, char *); + extern int iscsi_decode_text_input(u8, u8, char *, u32, struct iscsit_conn *); +-extern int iscsi_encode_text_output(u8, u8, char *, u32 *, ++extern int iscsi_encode_text_output(u8, u8, char *, u32 *, u32, + struct iscsi_param_list *, bool); + extern int iscsi_check_negotiated_keys(struct iscsi_param_list *); + extern void iscsi_set_connection_parameters(struct iscsi_conn_ops *, diff --git a/Makefile.rhelver b/Makefile.rhelver index eaceef239..3dd309dd8 100644 --- a/Makefile.rhelver +++ b/Makefile.rhelver @@ -12,7 +12,7 @@ RHEL_MINOR = 2 # # Use this spot to avoid future merge conflicts. # Do not trim this comment. -RHEL_RELEASE = 211.46.1 +RHEL_RELEASE = 211.47.1 # # RHEL_REBASE_NUM diff --git a/kernel.changelog b/kernel.changelog index a455ff318..e55e8dfdf 100644 --- a/kernel.changelog +++ b/kernel.changelog @@ -1,3 +1,93 @@ +* Wed Aug 12 2026 CKI KWF Bot [6.12.0-211.47.1.el10_2] +- scsi: target: iscsi: Bound iscsi_encode_text_output() appends to rsp_buf (Maurizio Lombardi) [RHEL-213198] {CVE-2026-63887} +- perf/aux: Fix page UAF in map_range() (CKI Backport Bot) [RHEL-218475] {CVE-2026-64300} +- net/sched: act_api: use RCU with deferred freeing for action lifecycle (CKI Backport Bot) [RHEL-218188] {CVE-2026-53264} +- KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled (Paolo Bonzini) [RHEL-214436] +- KVM: x86: hyper-v: Validate all GVAs during PV TLB flush (Paolo Bonzini) [RHEL-214436] +- KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level (Aidan Wallace) [RHEL-213472] {CVE-2026-63807} +- KVM: nVMX: Hide shadow VMCS right after VMCLEAR (Aidan Wallace) [RHEL-213472] +- KVM: x86: Check for invalid/obsolete root *after* making MMU pages available (Aidan Wallace) [RHEL-213472] +- KVM: nVMX: Put vmcs12 pages if nested VM-Enter fails due to invalid guest state (Aidan Wallace) [RHEL-213472] +- accel/ivpu: Fix signed integer truncation in IPC receive (CKI Backport Bot) [RHEL-190054] {CVE-2026-53202} +- netfilter: nf_conntrack_expect: store master_tuple in expectation (Florian Westphal) [RHEL-185311] +- selftests: netfilter: nft_concat_range.sh: add check for flush+reload bug (Florian Westphal) [RHEL-185311] +- selftests: netfilter: nft_concat_range.sh: add check for overlap detection bug (Florian Westphal) [RHEL-185311] +- selftests: netfilter: nft_concat_range.sh: add check for double-create bug (Florian Westphal) [RHEL-185311] +- netfilter: ctnetlink: use nf_ct_exp_net() in expectation dump (Florian Westphal) [RHEL-185311] +- netfilter: nf_dup_netdev: add nf_dev_xmit_recursion*() helpers and use them (Florian Westphal) [RHEL-185311] +- netfilter: nft_fib: fix stale stack leak via the OIFNAME register (Florian Westphal) [RHEL-185311] +- netfilter: nft_exthdr: fix register tracking for F_PRESENT flag (Florian Westphal) [RHEL-185311] +- netfilter: nf_log: validate MAC header was set before dumping it (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack: destroy stale expectfn expectations on unregister (Florian Westphal) [RHEL-185311] +- netfilter: revalidate bridge ports (Florian Westphal) [RHEL-185311] +- netfilter: nft_ct: bail out on template ct in get eval (Florian Westphal) [RHEL-185311] +- netfilter: nft_tunnel: fix use-after-free on object destroy (Florian Westphal) [RHEL-185311] +- netfilter: conntrack_irc: fix possible out-of-bounds read (Florian Westphal) [RHEL-185311] +- netfilter: synproxy: add mutex to guard hook reference counting (Florian Westphal) [RHEL-185311] +- netfilter: disable payload mangling in userns (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_gre: fix gre keymap list corruption (Florian Westphal) [RHEL-185311] +- netfilter: synproxy: refresh tcphdr after skb_ensure_writable (Florian Westphal) [RHEL-185311] +- netfilter: conntrack: tcp: do not force CLOSE on invalid-seq RST without direction check (Florian Westphal) [RHEL-185311] +- netfilter: nf_queue: hold bridge skb->dev while queued (Florian Westphal) [RHEL-185311] +- netfilter: br_netfilter: Reallocate headroom if necessary in neigh_hh_bridge() (Florian Westphal) [RHEL-185311] +- netfilter: ip6t_hbh: reject oversized option lists (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_helper: fix possible null deref during error log (Florian Westphal) [RHEL-185311] +- netfilter: nft_ct: fix missing expect put in obj eval (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_sip: get helper before allocating expectation (Florian Westphal) [RHEL-185311] +- netfilter: ctnetlink: check tuple and mask in expectations created via nfqueue (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_expect: restore helper propagation via expectation (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: fix netdev hook allocation memleak with dormant tables (Florian Westphal) [RHEL-185311] +- netfilter: xt_CT: fix usersize for v1 and v2 revision (Florian Westphal) [RHEL-185311] +- netfilter: nft_compat: run xt_check_hooks_{match,target}() from .validate (Florian Westphal) [RHEL-185311] +- netfilter: x_tables: add .check_hooks to matches and targets (Florian Westphal) [RHEL-185311] +- netfilter: xtables: restrict several matches to inet family (Florian Westphal) [RHEL-185311] +- netfilter: nft_fwd_netdev: use recursion counter in neigh egress path (Florian Westphal) [RHEL-185311] +- netfilter: nft_fwd_netdev: add device and headroom validate with neigh forwarding (Florian Westphal) [RHEL-185311] +- netfilter: replace skb_try_make_writable() by skb_ensure_writable() (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_sip: don't use simple_strtoul (Florian Westphal) [RHEL-185311] +- netfilter: xt_policy: fix strict mode inbound policy matching (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: add hook transactions for device deletions (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: join hook list via splice_list_rcu() in commit phase (Florian Westphal) [RHEL-185311] +- rculist: add list_splice_rcu() for private lists (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: use list_del_rcu for netlink hooks (Florian Westphal) [RHEL-185311] {CVE-2026-46324} +- netfilter: nfnetlink_osf: fix potential NULL dereference in ttl check (Florian Westphal) [RHEL-185311] +- netfilter: nfnetlink_osf: fix out-of-bounds read on option matching (Florian Westphal) [RHEL-185311] +- netfilter: nat: use kfree_rcu to release ops (Florian Westphal) [RHEL-185311] +- netfilter: conntrack: remove sprintf usage (Florian Westphal) [RHEL-185311] +- netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO (Florian Westphal) [RHEL-185311] {CVE-2026-45841} +- nfnetlink_osf: validate individual option lengths in fingerprints (Florian Westphal) [RHEL-185311] {CVE-2026-23397} +- netfilter: nft_osf: restrict it to ipv4 (Florian Westphal) [RHEL-185311] +- netfilter: nft_ct: fix use-after-free in timeout object destroy (Florian Westphal) [RHEL-185311] {CVE-2026-31665} +- netfilter: xt_multiport: validate range encoding in checkentry (Florian Westphal) [RHEL-185311] {CVE-2026-31681} +- netfilter: nfnetlink_log: initialize nfgenmsg in NLMSG_DONE terminator (Florian Westphal) [RHEL-185311] {CVE-2026-43085} +- netfilter: nf_tables: reject immediate NF_QUEUE verdict (Florian Westphal) [RHEL-185311] {CVE-2026-43024} +- netfilter: x_tables: restrict xt_check_match/xt_check_target extensions for NFPROTO_ARP (Florian Westphal) [RHEL-185311] {CVE-2026-31424} +- netfilter: ctnetlink: ignore explicit helper on new expectations (Florian Westphal) [RHEL-185311] {CVE-2026-43025} +- netfilter: ctnetlink: zero expect NAT fields when CTA_EXPECT_NAT absent (Florian Westphal) [RHEL-185311] {CVE-2026-43026} +- netfilter: ipset: use nla_strcmp for IPSET_ATTR_NAME attr (Florian Westphal) [RHEL-185311] +- netfilter: x_tables: ensure names are nul-terminated (Florian Westphal) [RHEL-185311] {CVE-2026-43028} +- netfilter: nfnetlink_log: account for netlink header size (Florian Westphal) [RHEL-185311] {CVE-2026-31416} +- netfilter: ctnetlink: use netlink policy range checks (Florian Westphal) [RHEL-185311] {CVE-2026-31495} +- netfilter: ip6t_rt: reject oversized addrnr in rt_mt6_check() (Florian Westphal) [RHEL-185311] {CVE-2026-31674} +- netfilter: nf_conntrack_expect: store netns and zone in expectation (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_expect: use expect->helper (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_expect: honor expectation helper field (Florian Westphal) [RHEL-185311] +- netfilter: nfnetlink_log: fix uninitialized padding leak in NFULA_PAYLOAD (Florian Westphal) [RHEL-185311] {CVE-2026-31428} +- netfilter: nft_set_pipapo_avx2: don't return non-matching entry on expiry (Florian Westphal) [RHEL-185311] {CVE-2026-43114} +- nf_tables: nft_dynset: fix possible stateful expression memleak in error path (Florian Westphal) [RHEL-185311] {CVE-2026-23399} +- netfilter: nf_conntrack_h323: fix OOB read in decode_int() CONS case (Florian Westphal) [RHEL-185311] {CVE-2026-23456} +- netfilter: nf_conntrack_sip: fix Content-Length u32 truncation in sip_help_tcp() (Florian Westphal) [RHEL-185311] {CVE-2026-23457} +- netfilter: conntrack: add missing netlink policy validations (Florian Westphal) [RHEL-185311] {CVE-2026-31407} +- netfilter: ctnetlink: fix use-after-free in ctnetlink_dump_exp_ct() (Florian Westphal) [RHEL-185311] {CVE-2026-23458} +- netfilter: nfnetlink_queue: fix entry leak in bridge verdict error path (Florian Westphal) [RHEL-185311] {CVE-2026-43451} +- netfilter: nft_set_pipapo: fix stack out-of-bounds read in pipapo_drop() (Florian Westphal) [RHEL-185311] {CVE-2026-43453} +- netfilter: nf_tables: unconditionally bump set->nelems before insertion (Florian Westphal) [RHEL-185311] {CVE-2026-23272} +- netfilter: nf_conntrack_h323: fix OOB read in decode_choice() (Florian Westphal) [RHEL-185311] {CVE-2026-43233} +- netfilter: nft_set_hash: fix get operation on big endian (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: always walk all pending catchall elements (Florian Westphal) [RHEL-185311] {CVE-2026-23278} +- netfilter: nft_set_pipapo: split gc into unlink and reclaim phase (Florian Westphal) [RHEL-185311] {CVE-2026-23351} +Resolves: RHEL-185311, RHEL-190054, RHEL-213198, RHEL-213472, RHEL-214436, RHEL-218188, RHEL-218475 + * Mon Aug 10 2026 CKI KWF Bot [6.12.0-211.46.1.el10_2] - mm/slab: do not limit zeroing to orig_size when only red zoning is enabled (Rafael Aquini) [RHEL-223405] {CVE-2026-64368} Resolves: RHEL-223405 diff --git a/kernel.spec b/kernel.spec index 9fd6a67a9..6c7f5a957 100644 --- a/kernel.spec +++ b/kernel.spec @@ -176,13 +176,13 @@ Summary: The Linux kernel %define specrpmversion 6.12.0 %define specversion 6.12.0 %define patchversion 6.12 -%define pkgrelease 211.46.1 +%define pkgrelease 211.47.1 %define kversion 6 %define tarfile_release 6.12.0-211.46.1.el10_2 # This is needed to do merge window version magic %define patchlevel 12 # This allows pkg_release to have configurable %%{?dist} tag -%define specrelease 211.46.1%{?buildid}%{?dist} +%define specrelease 211.47.1%{?buildid}%{?dist} # This defines the kabi tarball version %define kabiversion 6.12.0-211.46.1.el10_2 @@ -1127,6 +1127,95 @@ Patch1: patch-%{patchversion}-redhat.patch # empty final patch to facilitate testing of kernel patches Patch999999: linux-kernel-test.patch +# Backports for 6.12.0-211.47.1.el10_2 +Patch1100: 1100-netfilter-nft-set-pipapo-split-gc-into-unlink-and-reclaim-phase.patch +Patch1101: 1101-netfilter-nf-tables-always-walk-all-pending-catchall-elements.patch +Patch1102: 1102-netfilter-nft-set-hash-fix-get-operation-on-big-endian.patch +Patch1103: 1103-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-choice.patch +Patch1104: 1104-netfilter-nf-tables-unconditionally-bump-set-nelems-before-insertion.patch +Patch1105: 1105-netfilter-nft-set-pipapo-fix-stack-out-of-bounds-read-in-pipapo-drop.patch +Patch1106: 1106-netfilter-nfnetlink-queue-fix-entry-leak-in-bridge-verdict-error-path.patch +Patch1107: 1107-netfilter-ctnetlink-fix-use-after-free-in-ctnetlink-dump-exp-ct.patch +Patch1108: 1108-netfilter-conntrack-add-missing-netlink-policy-validations.patch +Patch1109: 1109-netfilter-nf-conntrack-sip-fix-content-length-u32-truncation-in-sip-help-tcp.patch +Patch1110: 1110-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-int-cons-case.patch +Patch1111: 1111-nf-tables-nft-dynset-fix-possible-stateful-expression-memleak-in-error-path.patch +Patch1112: 1112-netfilter-nft-set-pipapo-avx2-don-t-return-non-matching-entry-on-expiry.patch +Patch1113: 1113-netfilter-nfnetlink-log-fix-uninitialized-padding-leak-in-nfula-payload.patch +Patch1114: 1114-netfilter-nf-conntrack-expect-honor-expectation-helper-field.patch +Patch1115: 1115-netfilter-nf-conntrack-expect-use-expect-helper.patch +Patch1116: 1116-netfilter-nf-conntrack-expect-store-netns-and-zone-in-expectation.patch +Patch1117: 1117-netfilter-ip6t-rt-reject-oversized-addrnr-in-rt-mt6-check.patch +Patch1118: 1118-netfilter-ctnetlink-use-netlink-policy-range-checks.patch +Patch1119: 1119-netfilter-nfnetlink-log-account-for-netlink-header-size.patch +Patch1120: 1120-netfilter-x-tables-ensure-names-are-nul-terminated.patch +Patch1121: 1121-netfilter-ipset-use-nla-strcmp-for-ipset-attr-name-attr.patch +Patch1122: 1122-netfilter-ctnetlink-zero-expect-nat-fields-when-cta-expect-nat-absent.patch +Patch1123: 1123-netfilter-ctnetlink-ignore-explicit-helper-on-new-expectations.patch +Patch1124: 1124-netfilter-x-tables-restrict-xt-check-match-xt-check-target-extensions-for-nfprot.patch +Patch1125: 1125-netfilter-nf-tables-reject-immediate-nf-queue-verdict.patch +Patch1126: 1126-netfilter-nfnetlink-log-initialize-nfgenmsg-in-nlmsg-done-terminator.patch +Patch1127: 1127-netfilter-xt-multiport-validate-range-encoding-in-checkentry.patch +Patch1128: 1128-netfilter-nft-ct-fix-use-after-free-in-timeout-object-destroy.patch +Patch1129: 1129-netfilter-nft-osf-restrict-it-to-ipv4.patch +Patch1130: 1130-nfnetlink-osf-validate-individual-option-lengths-in-fingerprints.patch +Patch1131: 1131-netfilter-nfnetlink-osf-fix-divide-by-zero-in-osf-wss-modulo.patch +Patch1132: 1132-netfilter-conntrack-remove-sprintf-usage.patch +Patch1133: 1133-netfilter-nat-use-kfree-rcu-to-release-ops.patch +Patch1134: 1134-netfilter-nfnetlink-osf-fix-out-of-bounds-read-on-option-matching.patch +Patch1135: 1135-netfilter-nfnetlink-osf-fix-potential-null-dereference-in-ttl-check.patch +Patch1136: 1136-netfilter-nf-tables-use-list-del-rcu-for-netlink-hooks.patch +Patch1137: 1137-rculist-add-list-splice-rcu-for-private-lists.patch +Patch1138: 1138-netfilter-nf-tables-join-hook-list-via-splice-list-rcu-in-commit-phase.patch +Patch1139: 1139-netfilter-nf-tables-add-hook-transactions-for-device-deletions.patch +Patch1140: 1140-netfilter-xt-policy-fix-strict-mode-inbound-policy-matching.patch +Patch1141: 1141-netfilter-nf-conntrack-sip-don-t-use-simple-strtoul.patch +Patch1142: 1142-netfilter-replace-skb-try-make-writable-by-skb-ensure-writable.patch +Patch1143: 1143-netfilter-nft-fwd-netdev-add-device-and-headroom-validate-with-neigh-forwarding.patch +Patch1144: 1144-netfilter-nft-fwd-netdev-use-recursion-counter-in-neigh-egress-path.patch +Patch1145: 1145-netfilter-xtables-restrict-several-matches-to-inet-family.patch +Patch1146: 1146-netfilter-x-tables-add-check-hooks-to-matches-and-targets.patch +Patch1147: 1147-netfilter-nft-compat-run-xt-check-hooks-match-target-from-validate.patch +Patch1148: 1148-netfilter-xt-ct-fix-usersize-for-v1-and-v2-revision.patch +Patch1149: 1149-netfilter-nf-tables-fix-netdev-hook-allocation-memleak-with-dormant-tables.patch +Patch1150: 1150-netfilter-nf-conntrack-expect-restore-helper-propagation-via-expectation.patch +Patch1151: 1151-netfilter-ctnetlink-check-tuple-and-mask-in-expectations-created-via-nfqueue.patch +Patch1152: 1152-netfilter-nf-conntrack-sip-get-helper-before-allocating-expectation.patch +Patch1153: 1153-netfilter-nft-ct-fix-missing-expect-put-in-obj-eval.patch +Patch1154: 1154-netfilter-nf-conntrack-helper-fix-possible-null-deref-during-error-log.patch +Patch1155: 1155-netfilter-ip6t-hbh-reject-oversized-option-lists.patch +Patch1156: 1156-netfilter-br-netfilter-reallocate-headroom-if-necessary-in-neigh-hh-bridge.patch +Patch1157: 1157-netfilter-nf-queue-hold-bridge-skb-dev-while-queued.patch +Patch1158: 1158-netfilter-conntrack-tcp-do-not-force-close-on-invalid-seq-rst-without-direction-.patch +Patch1159: 1159-netfilter-synproxy-refresh-tcphdr-after-skb-ensure-writable.patch +Patch1160: 1160-netfilter-nf-conntrack-gre-fix-gre-keymap-list-corruption.patch +Patch1161: 1161-netfilter-disable-payload-mangling-in-userns.patch +Patch1162: 1162-netfilter-synproxy-add-mutex-to-guard-hook-reference-counting.patch +Patch1163: 1163-netfilter-conntrack-irc-fix-possible-out-of-bounds-read.patch +Patch1164: 1164-netfilter-nft-tunnel-fix-use-after-free-on-object-destroy.patch +Patch1165: 1165-netfilter-nft-ct-bail-out-on-template-ct-in-get-eval.patch +Patch1166: 1166-netfilter-revalidate-bridge-ports.patch +Patch1167: 1167-netfilter-nf-conntrack-destroy-stale-expectfn-expectations-on-unregister.patch +Patch1168: 1168-netfilter-nf-log-validate-mac-header-was-set-before-dumping-it.patch +Patch1169: 1169-netfilter-nft-exthdr-fix-register-tracking-for-f-present-flag.patch +Patch1170: 1170-netfilter-nft-fib-fix-stale-stack-leak-via-the-oifname-register.patch +Patch1171: 1171-netfilter-nf-dup-netdev-add-nf-dev-xmit-recursion-helpers-and-use-them.patch +Patch1172: 1172-netfilter-ctnetlink-use-nf-ct-exp-net-in-expectation-dump.patch +Patch1173: 1173-selftests-netfilter-nft-concat-range-sh-add-check-for-double-create-bug.patch +Patch1174: 1174-selftests-netfilter-nft-concat-range-sh-add-check-for-overlap-detection-bug.patch +Patch1175: 1175-selftests-netfilter-nft-concat-range-sh-add-check-for-flush-reload-bug.patch +Patch1176: 1176-netfilter-nf-conntrack-expect-store-master-tuple-in-expectation.patch +Patch1177: 1177-accel-ivpu-fix-signed-integer-truncation-in-ipc-receive.patch +Patch1178: 1178-kvm-nvmx-put-vmcs12-pages-if-nested-vm-enter-fails-due-to-invalid-guest-state.patch +Patch1179: 1179-kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch +Patch1180: 1180-kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch +Patch1181: 1181-kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-checking-max-mapping-level.patch +Patch1182: 1182-kvm-x86-hyper-v-validate-all-gvas-during-pv-tlb-flush.patch +Patch1183: 1183-kvm-svm-make-svm-flush-tlb-gva-do-a-full-asid-flush-if-npt-enabled.patch +Patch1184: 1184-net-sched-act-api-use-rcu-with-deferred-freeing-for-action-lifecycle.patch +Patch1185: 1185-perf-aux-fix-page-uaf-in-map-range.patch +Patch1186: 1186-scsi-target-iscsi-bound-iscsi-encode-text-output-appends-to-rsp-buf.patch + # END OF PATCH DEFINITIONS %description @@ -1974,6 +2063,95 @@ ApplyOptionalPatch patch-%{patchversion}-redhat.patch ApplyOptionalPatch linux-kernel-test.patch +# Applying backports for 6.12.0-211.47.1.el10_2 +ApplyPatch 1100-netfilter-nft-set-pipapo-split-gc-into-unlink-and-reclaim-phase.patch +ApplyPatch 1101-netfilter-nf-tables-always-walk-all-pending-catchall-elements.patch +ApplyPatch 1102-netfilter-nft-set-hash-fix-get-operation-on-big-endian.patch +ApplyPatch 1103-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-choice.patch +ApplyPatch 1104-netfilter-nf-tables-unconditionally-bump-set-nelems-before-insertion.patch +ApplyPatch 1105-netfilter-nft-set-pipapo-fix-stack-out-of-bounds-read-in-pipapo-drop.patch +ApplyPatch 1106-netfilter-nfnetlink-queue-fix-entry-leak-in-bridge-verdict-error-path.patch +ApplyPatch 1107-netfilter-ctnetlink-fix-use-after-free-in-ctnetlink-dump-exp-ct.patch +ApplyPatch 1108-netfilter-conntrack-add-missing-netlink-policy-validations.patch +ApplyPatch 1109-netfilter-nf-conntrack-sip-fix-content-length-u32-truncation-in-sip-help-tcp.patch +ApplyPatch 1110-netfilter-nf-conntrack-h323-fix-oob-read-in-decode-int-cons-case.patch +ApplyPatch 1111-nf-tables-nft-dynset-fix-possible-stateful-expression-memleak-in-error-path.patch +ApplyPatch 1112-netfilter-nft-set-pipapo-avx2-don-t-return-non-matching-entry-on-expiry.patch +ApplyPatch 1113-netfilter-nfnetlink-log-fix-uninitialized-padding-leak-in-nfula-payload.patch +ApplyPatch 1114-netfilter-nf-conntrack-expect-honor-expectation-helper-field.patch +ApplyPatch 1115-netfilter-nf-conntrack-expect-use-expect-helper.patch +ApplyPatch 1116-netfilter-nf-conntrack-expect-store-netns-and-zone-in-expectation.patch +ApplyPatch 1117-netfilter-ip6t-rt-reject-oversized-addrnr-in-rt-mt6-check.patch +ApplyPatch 1118-netfilter-ctnetlink-use-netlink-policy-range-checks.patch +ApplyPatch 1119-netfilter-nfnetlink-log-account-for-netlink-header-size.patch +ApplyPatch 1120-netfilter-x-tables-ensure-names-are-nul-terminated.patch +ApplyPatch 1121-netfilter-ipset-use-nla-strcmp-for-ipset-attr-name-attr.patch +ApplyPatch 1122-netfilter-ctnetlink-zero-expect-nat-fields-when-cta-expect-nat-absent.patch +ApplyPatch 1123-netfilter-ctnetlink-ignore-explicit-helper-on-new-expectations.patch +ApplyPatch 1124-netfilter-x-tables-restrict-xt-check-match-xt-check-target-extensions-for-nfprot.patch +ApplyPatch 1125-netfilter-nf-tables-reject-immediate-nf-queue-verdict.patch +ApplyPatch 1126-netfilter-nfnetlink-log-initialize-nfgenmsg-in-nlmsg-done-terminator.patch +ApplyPatch 1127-netfilter-xt-multiport-validate-range-encoding-in-checkentry.patch +ApplyPatch 1128-netfilter-nft-ct-fix-use-after-free-in-timeout-object-destroy.patch +ApplyPatch 1129-netfilter-nft-osf-restrict-it-to-ipv4.patch +ApplyPatch 1130-nfnetlink-osf-validate-individual-option-lengths-in-fingerprints.patch +ApplyPatch 1131-netfilter-nfnetlink-osf-fix-divide-by-zero-in-osf-wss-modulo.patch +ApplyPatch 1132-netfilter-conntrack-remove-sprintf-usage.patch +ApplyPatch 1133-netfilter-nat-use-kfree-rcu-to-release-ops.patch +ApplyPatch 1134-netfilter-nfnetlink-osf-fix-out-of-bounds-read-on-option-matching.patch +ApplyPatch 1135-netfilter-nfnetlink-osf-fix-potential-null-dereference-in-ttl-check.patch +ApplyPatch 1136-netfilter-nf-tables-use-list-del-rcu-for-netlink-hooks.patch +ApplyPatch 1137-rculist-add-list-splice-rcu-for-private-lists.patch +ApplyPatch 1138-netfilter-nf-tables-join-hook-list-via-splice-list-rcu-in-commit-phase.patch +ApplyPatch 1139-netfilter-nf-tables-add-hook-transactions-for-device-deletions.patch +ApplyPatch 1140-netfilter-xt-policy-fix-strict-mode-inbound-policy-matching.patch +ApplyPatch 1141-netfilter-nf-conntrack-sip-don-t-use-simple-strtoul.patch +ApplyPatch 1142-netfilter-replace-skb-try-make-writable-by-skb-ensure-writable.patch +ApplyPatch 1143-netfilter-nft-fwd-netdev-add-device-and-headroom-validate-with-neigh-forwarding.patch +ApplyPatch 1144-netfilter-nft-fwd-netdev-use-recursion-counter-in-neigh-egress-path.patch +ApplyPatch 1145-netfilter-xtables-restrict-several-matches-to-inet-family.patch +ApplyPatch 1146-netfilter-x-tables-add-check-hooks-to-matches-and-targets.patch +ApplyPatch 1147-netfilter-nft-compat-run-xt-check-hooks-match-target-from-validate.patch +ApplyPatch 1148-netfilter-xt-ct-fix-usersize-for-v1-and-v2-revision.patch +ApplyPatch 1149-netfilter-nf-tables-fix-netdev-hook-allocation-memleak-with-dormant-tables.patch +ApplyPatch 1150-netfilter-nf-conntrack-expect-restore-helper-propagation-via-expectation.patch +ApplyPatch 1151-netfilter-ctnetlink-check-tuple-and-mask-in-expectations-created-via-nfqueue.patch +ApplyPatch 1152-netfilter-nf-conntrack-sip-get-helper-before-allocating-expectation.patch +ApplyPatch 1153-netfilter-nft-ct-fix-missing-expect-put-in-obj-eval.patch +ApplyPatch 1154-netfilter-nf-conntrack-helper-fix-possible-null-deref-during-error-log.patch +ApplyPatch 1155-netfilter-ip6t-hbh-reject-oversized-option-lists.patch +ApplyPatch 1156-netfilter-br-netfilter-reallocate-headroom-if-necessary-in-neigh-hh-bridge.patch +ApplyPatch 1157-netfilter-nf-queue-hold-bridge-skb-dev-while-queued.patch +ApplyPatch 1158-netfilter-conntrack-tcp-do-not-force-close-on-invalid-seq-rst-without-direction-.patch +ApplyPatch 1159-netfilter-synproxy-refresh-tcphdr-after-skb-ensure-writable.patch +ApplyPatch 1160-netfilter-nf-conntrack-gre-fix-gre-keymap-list-corruption.patch +ApplyPatch 1161-netfilter-disable-payload-mangling-in-userns.patch +ApplyPatch 1162-netfilter-synproxy-add-mutex-to-guard-hook-reference-counting.patch +ApplyPatch 1163-netfilter-conntrack-irc-fix-possible-out-of-bounds-read.patch +ApplyPatch 1164-netfilter-nft-tunnel-fix-use-after-free-on-object-destroy.patch +ApplyPatch 1165-netfilter-nft-ct-bail-out-on-template-ct-in-get-eval.patch +ApplyPatch 1166-netfilter-revalidate-bridge-ports.patch +ApplyPatch 1167-netfilter-nf-conntrack-destroy-stale-expectfn-expectations-on-unregister.patch +ApplyPatch 1168-netfilter-nf-log-validate-mac-header-was-set-before-dumping-it.patch +ApplyPatch 1169-netfilter-nft-exthdr-fix-register-tracking-for-f-present-flag.patch +ApplyPatch 1170-netfilter-nft-fib-fix-stale-stack-leak-via-the-oifname-register.patch +ApplyPatch 1171-netfilter-nf-dup-netdev-add-nf-dev-xmit-recursion-helpers-and-use-them.patch +ApplyPatch 1172-netfilter-ctnetlink-use-nf-ct-exp-net-in-expectation-dump.patch +ApplyPatch 1173-selftests-netfilter-nft-concat-range-sh-add-check-for-double-create-bug.patch +ApplyPatch 1174-selftests-netfilter-nft-concat-range-sh-add-check-for-overlap-detection-bug.patch +ApplyPatch 1175-selftests-netfilter-nft-concat-range-sh-add-check-for-flush-reload-bug.patch +ApplyPatch 1176-netfilter-nf-conntrack-expect-store-master-tuple-in-expectation.patch +ApplyPatch 1177-accel-ivpu-fix-signed-integer-truncation-in-ipc-receive.patch +ApplyPatch 1178-kvm-nvmx-put-vmcs12-pages-if-nested-vm-enter-fails-due-to-invalid-guest-state.patch +ApplyPatch 1179-kvm-x86-check-for-invalid-obsolete-root-after-making-mmu-pages-available.patch +ApplyPatch 1180-kvm-nvmx-hide-shadow-vmcs-right-after-vmclear.patch +ApplyPatch 1181-kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-checking-max-mapping-level.patch +ApplyPatch 1182-kvm-x86-hyper-v-validate-all-gvas-during-pv-tlb-flush.patch +ApplyPatch 1183-kvm-svm-make-svm-flush-tlb-gva-do-a-full-asid-flush-if-npt-enabled.patch +ApplyPatch 1184-net-sched-act-api-use-rcu-with-deferred-freeing-for-action-lifecycle.patch +ApplyPatch 1185-perf-aux-fix-page-uaf-in-map-range.patch +ApplyPatch 1186-scsi-target-iscsi-bound-iscsi-encode-text-output-appends-to-rsp-buf.patch + %{log_msg "End of patch applications"} # END OF PATCH APPLICATIONS @@ -4491,6 +4669,95 @@ fi\ # # %changelog +* Wed Aug 12 2026 CKI KWF Bot [6.12.0-211.47.1.el10_2] +- scsi: target: iscsi: Bound iscsi_encode_text_output() appends to rsp_buf (Maurizio Lombardi) [RHEL-213198] {CVE-2026-63887} +- perf/aux: Fix page UAF in map_range() (CKI Backport Bot) [RHEL-218475] {CVE-2026-64300} +- net/sched: act_api: use RCU with deferred freeing for action lifecycle (CKI Backport Bot) [RHEL-218188] {CVE-2026-53264} +- KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled (Paolo Bonzini) [RHEL-214436] +- KVM: x86: hyper-v: Validate all GVAs during PV TLB flush (Paolo Bonzini) [RHEL-214436] +- KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level (Aidan Wallace) [RHEL-213472] {CVE-2026-63807} +- KVM: nVMX: Hide shadow VMCS right after VMCLEAR (Aidan Wallace) [RHEL-213472] +- KVM: x86: Check for invalid/obsolete root *after* making MMU pages available (Aidan Wallace) [RHEL-213472] +- KVM: nVMX: Put vmcs12 pages if nested VM-Enter fails due to invalid guest state (Aidan Wallace) [RHEL-213472] +- accel/ivpu: Fix signed integer truncation in IPC receive (CKI Backport Bot) [RHEL-190054] {CVE-2026-53202} +- netfilter: nf_conntrack_expect: store master_tuple in expectation (Florian Westphal) [RHEL-185311] +- selftests: netfilter: nft_concat_range.sh: add check for flush+reload bug (Florian Westphal) [RHEL-185311] +- selftests: netfilter: nft_concat_range.sh: add check for overlap detection bug (Florian Westphal) [RHEL-185311] +- selftests: netfilter: nft_concat_range.sh: add check for double-create bug (Florian Westphal) [RHEL-185311] +- netfilter: ctnetlink: use nf_ct_exp_net() in expectation dump (Florian Westphal) [RHEL-185311] +- netfilter: nf_dup_netdev: add nf_dev_xmit_recursion*() helpers and use them (Florian Westphal) [RHEL-185311] +- netfilter: nft_fib: fix stale stack leak via the OIFNAME register (Florian Westphal) [RHEL-185311] +- netfilter: nft_exthdr: fix register tracking for F_PRESENT flag (Florian Westphal) [RHEL-185311] +- netfilter: nf_log: validate MAC header was set before dumping it (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack: destroy stale expectfn expectations on unregister (Florian Westphal) [RHEL-185311] +- netfilter: revalidate bridge ports (Florian Westphal) [RHEL-185311] +- netfilter: nft_ct: bail out on template ct in get eval (Florian Westphal) [RHEL-185311] +- netfilter: nft_tunnel: fix use-after-free on object destroy (Florian Westphal) [RHEL-185311] +- netfilter: conntrack_irc: fix possible out-of-bounds read (Florian Westphal) [RHEL-185311] +- netfilter: synproxy: add mutex to guard hook reference counting (Florian Westphal) [RHEL-185311] +- netfilter: disable payload mangling in userns (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_gre: fix gre keymap list corruption (Florian Westphal) [RHEL-185311] +- netfilter: synproxy: refresh tcphdr after skb_ensure_writable (Florian Westphal) [RHEL-185311] +- netfilter: conntrack: tcp: do not force CLOSE on invalid-seq RST without direction check (Florian Westphal) [RHEL-185311] +- netfilter: nf_queue: hold bridge skb->dev while queued (Florian Westphal) [RHEL-185311] +- netfilter: br_netfilter: Reallocate headroom if necessary in neigh_hh_bridge() (Florian Westphal) [RHEL-185311] +- netfilter: ip6t_hbh: reject oversized option lists (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_helper: fix possible null deref during error log (Florian Westphal) [RHEL-185311] +- netfilter: nft_ct: fix missing expect put in obj eval (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_sip: get helper before allocating expectation (Florian Westphal) [RHEL-185311] +- netfilter: ctnetlink: check tuple and mask in expectations created via nfqueue (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_expect: restore helper propagation via expectation (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: fix netdev hook allocation memleak with dormant tables (Florian Westphal) [RHEL-185311] +- netfilter: xt_CT: fix usersize for v1 and v2 revision (Florian Westphal) [RHEL-185311] +- netfilter: nft_compat: run xt_check_hooks_{match,target}() from .validate (Florian Westphal) [RHEL-185311] +- netfilter: x_tables: add .check_hooks to matches and targets (Florian Westphal) [RHEL-185311] +- netfilter: xtables: restrict several matches to inet family (Florian Westphal) [RHEL-185311] +- netfilter: nft_fwd_netdev: use recursion counter in neigh egress path (Florian Westphal) [RHEL-185311] +- netfilter: nft_fwd_netdev: add device and headroom validate with neigh forwarding (Florian Westphal) [RHEL-185311] +- netfilter: replace skb_try_make_writable() by skb_ensure_writable() (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_sip: don't use simple_strtoul (Florian Westphal) [RHEL-185311] +- netfilter: xt_policy: fix strict mode inbound policy matching (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: add hook transactions for device deletions (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: join hook list via splice_list_rcu() in commit phase (Florian Westphal) [RHEL-185311] +- rculist: add list_splice_rcu() for private lists (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: use list_del_rcu for netlink hooks (Florian Westphal) [RHEL-185311] {CVE-2026-46324} +- netfilter: nfnetlink_osf: fix potential NULL dereference in ttl check (Florian Westphal) [RHEL-185311] +- netfilter: nfnetlink_osf: fix out-of-bounds read on option matching (Florian Westphal) [RHEL-185311] +- netfilter: nat: use kfree_rcu to release ops (Florian Westphal) [RHEL-185311] +- netfilter: conntrack: remove sprintf usage (Florian Westphal) [RHEL-185311] +- netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO (Florian Westphal) [RHEL-185311] {CVE-2026-45841} +- nfnetlink_osf: validate individual option lengths in fingerprints (Florian Westphal) [RHEL-185311] {CVE-2026-23397} +- netfilter: nft_osf: restrict it to ipv4 (Florian Westphal) [RHEL-185311] +- netfilter: nft_ct: fix use-after-free in timeout object destroy (Florian Westphal) [RHEL-185311] {CVE-2026-31665} +- netfilter: xt_multiport: validate range encoding in checkentry (Florian Westphal) [RHEL-185311] {CVE-2026-31681} +- netfilter: nfnetlink_log: initialize nfgenmsg in NLMSG_DONE terminator (Florian Westphal) [RHEL-185311] {CVE-2026-43085} +- netfilter: nf_tables: reject immediate NF_QUEUE verdict (Florian Westphal) [RHEL-185311] {CVE-2026-43024} +- netfilter: x_tables: restrict xt_check_match/xt_check_target extensions for NFPROTO_ARP (Florian Westphal) [RHEL-185311] {CVE-2026-31424} +- netfilter: ctnetlink: ignore explicit helper on new expectations (Florian Westphal) [RHEL-185311] {CVE-2026-43025} +- netfilter: ctnetlink: zero expect NAT fields when CTA_EXPECT_NAT absent (Florian Westphal) [RHEL-185311] {CVE-2026-43026} +- netfilter: ipset: use nla_strcmp for IPSET_ATTR_NAME attr (Florian Westphal) [RHEL-185311] +- netfilter: x_tables: ensure names are nul-terminated (Florian Westphal) [RHEL-185311] {CVE-2026-43028} +- netfilter: nfnetlink_log: account for netlink header size (Florian Westphal) [RHEL-185311] {CVE-2026-31416} +- netfilter: ctnetlink: use netlink policy range checks (Florian Westphal) [RHEL-185311] {CVE-2026-31495} +- netfilter: ip6t_rt: reject oversized addrnr in rt_mt6_check() (Florian Westphal) [RHEL-185311] {CVE-2026-31674} +- netfilter: nf_conntrack_expect: store netns and zone in expectation (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_expect: use expect->helper (Florian Westphal) [RHEL-185311] +- netfilter: nf_conntrack_expect: honor expectation helper field (Florian Westphal) [RHEL-185311] +- netfilter: nfnetlink_log: fix uninitialized padding leak in NFULA_PAYLOAD (Florian Westphal) [RHEL-185311] {CVE-2026-31428} +- netfilter: nft_set_pipapo_avx2: don't return non-matching entry on expiry (Florian Westphal) [RHEL-185311] {CVE-2026-43114} +- nf_tables: nft_dynset: fix possible stateful expression memleak in error path (Florian Westphal) [RHEL-185311] {CVE-2026-23399} +- netfilter: nf_conntrack_h323: fix OOB read in decode_int() CONS case (Florian Westphal) [RHEL-185311] {CVE-2026-23456} +- netfilter: nf_conntrack_sip: fix Content-Length u32 truncation in sip_help_tcp() (Florian Westphal) [RHEL-185311] {CVE-2026-23457} +- netfilter: conntrack: add missing netlink policy validations (Florian Westphal) [RHEL-185311] {CVE-2026-31407} +- netfilter: ctnetlink: fix use-after-free in ctnetlink_dump_exp_ct() (Florian Westphal) [RHEL-185311] {CVE-2026-23458} +- netfilter: nfnetlink_queue: fix entry leak in bridge verdict error path (Florian Westphal) [RHEL-185311] {CVE-2026-43451} +- netfilter: nft_set_pipapo: fix stack out-of-bounds read in pipapo_drop() (Florian Westphal) [RHEL-185311] {CVE-2026-43453} +- netfilter: nf_tables: unconditionally bump set->nelems before insertion (Florian Westphal) [RHEL-185311] {CVE-2026-23272} +- netfilter: nf_conntrack_h323: fix OOB read in decode_choice() (Florian Westphal) [RHEL-185311] {CVE-2026-43233} +- netfilter: nft_set_hash: fix get operation on big endian (Florian Westphal) [RHEL-185311] +- netfilter: nf_tables: always walk all pending catchall elements (Florian Westphal) [RHEL-185311] {CVE-2026-23278} +- netfilter: nft_set_pipapo: split gc into unlink and reclaim phase (Florian Westphal) [RHEL-185311] {CVE-2026-23351} + * Mon Aug 10 2026 CKI KWF Bot [6.12.0-211.46.1.el10_2] - mm/slab: do not limit zeroing to orig_size when only red zoning is enabled (Rafael Aquini) [RHEL-223405] {CVE-2026-64368}