Skip to content

[LTS 8.6] CVE-2026-43027, CVE-2026-43116, CVE-2026-43125, CVE-2024-27397 - #1619

Open
pvts-mat wants to merge 5 commits into
ctrliq:ciqlts8_6from
pvts-mat:CVE-batch-45_ciqlts8_6
Open

pvts-mat wants to merge 5 commits into
ctrliq:ciqlts8_6from
pvts-mat:CVE-batch-45_ciqlts8_6

Conversation

@pvts-mat

Copy link
Copy Markdown
Contributor

[LTS 8.6]

CVE-2026-43027 VULN-183687
CVE-2026-43116 VULN-184338
CVE-2026-43125 VULN-184372
CVE-2024-27397 VULN-68359

Commits

CVE-2026-43027

netfilter: nf_conntrack_helper: pass helper to expect cleanup

jira VULN-183687
cve CVE-2026-43027
commit-author Qi Tang <tpluszz77@gmail.com>
commit a242a9ae58aa46ff7dae51ce64150a93957abe65

CVE-2026-43116

netfilter: ctnetlink: ensure safe access to master conntrack

jira VULN-184338
cve CVE-2026-43116
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit bffcaad9afdfe45d7fc777397d3b83c1e3ebffe5
upstream-diff In `ctnetlink_get_expect()' preserved the intent of the
  change (spinlock protection spanning from `nf_ct_expect_find_get()' to
  `nf_ct_expect_put()' + moving `nlmsg_new()' out of the critical
  section) and adjusted for the existing goto-ladder teardown pattern.
  In the upstream it was scrapped in
  e0241ae6ac59ffa318255640c047f7c90457fbe5 ("netfilter: use
  nfnetlink_unicast()"), but that commit introduces way too many
  unrealated changes to be backported just for this port's convenience.

Commit's intent

The fixing commit has two goals, which overlap, but not fully, so it may be a bit confusing.

  1. Annotate functions dereferencing nf_conntrack_expect::master as requiring the nf_conntrack_expect_lock held when called:
    • nf_ct_expect_event_report(),
    • nf_ct_unlink_expect_report(),
    • nf_ct_remove_expect(),
    • nf_ct_find_expectation(),
    • __nf_ct_expect_check().
  2. Expand (or introduce) the spin lock protection in functions calling lower-level functions operating on nf_conntrack_expect::master:
    • nf_ct_expect_related_report(): The extended lock covers the newly annotated function nf_ct_expect_event_report().
    • ctnetlink_get_expect(): The newly introduced lock doesn't cover any of the annotated functions. However, it protects the nf_ct_expect_find_get(), nf_ct_expect_put() and ctnetlink_exp_fill_info() calls, which could not have been annotated.
    • ctnetlink_del_expect(): Similarly to ctnetlink_get_expect(), the (expanded, this time) lock protection covers nf_ct_expect_find_get() and nf_ct_expect_put() calls. The nf_ct_unlink_expect_report(), already being under lock before, was among those annotated.

So, in total, the bffcaad fix

  • nf_conntrack_expect_lock-annotates the functions which were not put under the lock in the same commit, because it was already done (nf_ct_unlink_expect_report(), nf_ct_remove_expect(), nf_ct_find_expectation(), __nf_ct_expect_check()),
  • covers with the nf_conntrack_expect_lock the calls of functions which were not annotated in the same commit, and won't be (ctnetlink_del_expect(), ctnetlink_get_expect()), and
  • combines both, by covering the newly annotated function with nf_conntrack_expect_lock (nf_ct_expect_related_report() and nf_ct_expect_event_report() call).

Including the nf_ct_expect_find_get() call under the nf_conntrack_expect_lock lock achieves what was identified in the commit message as sufficient condition for accessing nf_conntrack_expect::master safely:

To access exp->master safely:

  • Grab the nf_conntrack_expect_lock, this gets serialized with
    clean_from_lists() which also holds this lock when the master
    conntrack goes away.

  • Hold reference on master conntrack via nf_conntrack_find_get().
    Not so easy since the master tuple to look up for the master conntrack
    is not available in the existing problematic paths.

In the backported version the annotated functions were checked if their invariant also holds for all their calls in LTS 8.6. It does, so no new locks were introduced besides those the upstream fix introduced.

The CVE-2026-80668 issue

Formally, the CVE-2026-43116 fix bffcaad has a bugfix b8b09dc netfilter: nf_conntrack_expect: use conntrack GC to reap expectations on the upstream, which has its own CVE-2026-80668. (Additionally it has its own bugfixes 322371b and 4aa6384.) However, the CVE-2026-80668 issue is not something CVE-2026-43116 fix introduced. Tag "Fixes: bffcaad" in this context means more should have addressed this problem as well but didn't, here's the missing piece rather than introduced a new problem, here's the fix for it.

The CVE-2026-43116 fix covers control-plane reader vs. background conntrack reclamation race. The follow-up covers a race where both sides are kernel-internal: the expectation expiry timer vs. whichever code is synchronously removing that same expectation - whoever loses the timer_delete() call loses the race.

Taking this into account, and the fact that the CVE-2026-80668 fix is challenging to backport on LTS 8.6 (~250 lines, extensive conflicts in all 9 modified files, complicated synchronization problem) with no precedent backports to support, as well as this CVE not being addressed by RH for any version yet, it was decided to not include it in this PR.

CVE-2026-43125

dlm: validate length in dlm_search_rsb_tree

jira VULN-184372
cve CVE-2026-43125
commit-author Ezrak1e <ezrakiez@gmail.com>
commit 080e5563f878c64e697b89e7439d730d0daad882
upstream-diff Kept the `len' check within the `dlm_search_rsb_tree'
  function like in the upstream, despite the fact that in LTS 8.6 the
  out-of-bounds write can happen not in `dlm_search_rsb_tree' but in the
  `rsb_cmp' called by it. Function `rsb_cmp' is not suited for returning
  error codes, assumed to be always called with correct arguments. The
  code analysis established that `dlm_search_rsb_tree' was the last
  place where this may not have been the case.

The modified dlm_search_rsb_tree function underwent major rewrite in the non-backported 6c64803 dlm: switch to use rhashtable for rsbs. In the upstream, at the moment right before the CVE-2026-43125 fix 080e556, the out-of-bounds condition can occur directly in dlm_search_rsb_tree function:

int dlm_search_rsb_tree(struct rhashtable *rhash, const void *name, int len,
struct dlm_rsb **r_ret)
{
char key[DLM_RESNAME_MAXLEN] = {};
memcpy(key, name, len);
*r_ret = rhashtable_lookup_fast(rhash, &key, dlm_rhash_rsb_params);
if (*r_ret)
return 0;
return -EBADR;
}

In LTS 8.6 this may happen in the rsb_cmp function:

static int rsb_cmp(struct dlm_rsb *r, const char *name, int nlen)
{
char maxname[DLM_RESNAME_MAXLEN];
memset(maxname, 0, DLM_RESNAME_MAXLEN);
memcpy(maxname, name, nlen);
return memcmp(r->res_name, maxname, DLM_RESNAME_MAXLEN);
}

called from dlm_search_rsb_tree:

rc = rsb_cmp(r, name, len);

The rsb_cmp function is not suited for performing the validity check of the len argument - the return value decodes the result of strings comparison. The check can be done in the dlm_search_rsb_tree, however, as long as no other rsb_cmp calls are vulnerable to this problem.

Beside dlm_search_rsb_tree the rsb_cmp function is called in two places, both of them invoking rsb_cmp properly, what is shown below.

wait_pending_remove

!rsb_cmp(r, ls->ls_remove_name, ls->ls_remove_len)) {

The only non-zero assignments of ls->ls_remove_len are in send_repeat_remove:

ls->ls_remove_len = len;

and in shrink_bucket:

ls->ls_remove_len = len;

In shrink_bucket, before ls->ls_remove_len = len assignment with potentially wrong len, the function dlm_search_rsb_tree will be called, and with the newly introduced check it will fail and skip the iteration.

kernel-src-tree/fs/dlm/lock.c

Lines 1742 to 1747 in 67edd7c

rv = dlm_search_rsb_tree(&ls->ls_rsbtbl[b].toss, name, len, &r);
if (rv) {
spin_unlock(&ls->ls_rsbtbl[b].lock);
log_debug(ls, "remove_name not toss %s", name);
continue;
}

On the other hand the validity of ls->ls_remove_len = len assignment in the send_repeat_remove function depends on what this particular function was called with. It's called only in receive_request with the value of namelen:

send_repeat_remove(ls, ms->m_extra, namelen);

For the send_repeat_remove to be called with too big namelen the code has to go through

kernel-src-tree/fs/dlm/lock.c

Lines 4100 to 4103 in 67edd7c

namelen = receive_extralen(ms);
error = find_rsb(ls, ms->m_extra, namelen, from_nodeid,
R_RECEIVE_REQUEST, &r);

(the first two goto fail will yield namelen == 0 from its initialization). But find_rsb already checks len and returns -EINVAL

if (len > DLM_RESNAME_MAXLEN)
return -EINVAL;

Then the send_repeat_remove call under the fail label is not reached because namelen && error == -EBADR is false.

This means that the ls->ls_remove_len passed as len in wait_pending_remove is never greater than DLM_RESNAME_MAXLEN.

rsb_insert

rc = rsb_cmp(cur, rsb->res_name, rsb->res_length);

The rsb->res_length, passed as len, is only assigned in get_rsb_struct

r->res_length = len;

The get_rsb_struct function doesn't modify or check len in any way, so the check is passed to its callers. It's called in 3 places:

  1. find_rsb_dir
    error = get_rsb_struct(ls, name, len, &r);
  2. find_rsb_nodir
    error = get_rsb_struct(ls, name, len, &r);
  3. dlm_master_lookup
    error = get_rsb_struct(ls, name, len, &r);

In case of the last one the issue is simple, because len is checked right at the beginning of the function and not modified later:

kernel-src-tree/fs/dlm/lock.c

Lines 1268 to 1269 in 1416bd5

if (len > DLM_RESNAME_MAXLEN)
return -EINVAL;

Functions find_rsb_dir and find_rsb_nodir, in turn, are always called with the proper len - their only usage is in the find_rsb function

kernel-src-tree/fs/dlm/lock.c

Lines 1081 to 1104 in 1416bd5

static int find_rsb(struct dlm_ls *ls, const void *name, int len,
int from_nodeid, unsigned int flags,
struct dlm_rsb **r_ret)
{
int dir_nodeid;
uint32_t hash;
int rv;
if (len > DLM_RESNAME_MAXLEN)
return -EINVAL;
hash = jhash(name, len, 0);
dir_nodeid = dlm_hash2nodeid(ls, hash);
rcu_read_lock();
if (dlm_no_directory(ls))
rv = find_rsb_nodir(ls, name, len, hash, dir_nodeid,
from_nodeid, flags, r_ret);
else
rv = find_rsb_dir(ls, name, len, hash, dir_nodeid,
from_nodeid, flags, r_ret);
rcu_read_unlock();
return rv;
}

CVE-2024-27397

netfilter: nf_tables: use timestamp to check for set element timeout

jira VULN-68359
cve CVE-2024-27397
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit 7395dfacfff65e9938ac0889dafa1ab01e987d15
upstream-diff This commit combines the upstream fix 7395dfacf and
  fragments of 0854db2aaef3fcdd3498a9d299c60adea2aa3dc6 (`netfilter:
  nf_tables: use net_generic infra for transaction data') and its
  follow-up d59d2f82f984df44b31c5d7837fc2f62268b7571 (`netfilter:
  nftables: add nft_pernet() helper function'). Commit 0854db2aa moves
  most of the nft-related data out of the general `net' struct to a
  `net_generic' extension. The CVE-2024-27397 fix introduces a new field
  to that extension. It could not have been put where the other moved
  fields resided originally (`struct netns_nftables') because it would
  break kABI. At the same time 0854db2aa could not have been backported
  fully because it would also break kABI. The solution was to introduce
  the `net_generic' extension as in 0854db2aa but to keep it narrowed to
  just the `tstamp' field needed by the CVE-2024-27397 bugfix.  As a
  result the following differences from the upstream fix can be observed:
  nf_tables.h
        1. No `nftables_pernet' defined to place `tstamp' in (done in
           7395dfacf), so defined the singleton.
        2. Added `nf_tables_net_id' global, `nft_pernet' helper and
           `generic.h' inclusion - taken directly from d59d2f82f.
  nf_tables_api.c
        1. Defined `nf_tables_net_id' as it was done in 0854db2aa. It
           required `EXPORT_SYMBOL_GPL' in LTS 8.6 for the proper loading
           of the compiled `nf_tables_set.ko' module.
        2. Omitted changes to `nft_trans_gc_catchall_sync()', because LTS
           8.6 doesn't have this function nor any equivalent.
        3. In `nf_tables_valid_genid()' added the `nft_net' initialization
           (0854db2aa + d59d2f82f).
        4. Added fields `.id' and `.size' to the `nf_tables_net_ops'
           struct as it was done in 0854db2aa.
  net/netfilter/nft_set_pipapo.c
        1. Context conflicts in `nft_pipapo_get()' due to missing
           9dad402b89e81a0516bad5e0ac009b7a0a80898f, otherwise no diffs.
        2. In `nft_pipapo_insert()' addded `tstamp' argument to the two
           `pipapo_get' calls, just like in the upstream, although in the
           upstream these two are further apart.
        3. In `pipapo_gc()' needed to include `net' variable.
        4. Function `__nft_rbtree_insert' underwent some substantial
           changes across many non-backported commits, but they were
           irrelevant to the introduced change. Preserved the spirit of
           the fix by changing all `nft_set_elem_expired(&rbe->ext)' calls
           to `__nft_set_elem_expired(&rbe->ext, tstamp)'.
        5. In `nft_rbtree_gc()' adapted the change to read `net' from
           `set' obtained from `work' provided as argument instead of from
           `set' given in the argument directly like it's done in the
           upstream.
netfilter: nft_set_rbtree: .deactivate fails if element has expired

jira VULN-68359
cve-pre CVE-2024-27397
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d111692a59c1470ae530cbb39bcf0346c950ecc7

As the upstream-diff explains, the netfilter: nf_tables: use timestamp to check for set element timeout backport is actually 3 commits in one:

  • 0854db2 netfilter: nf_tables: use net_generic infra for transaction data,
  • d59d2f8 netfilter: nftables: add nft_pernet() helper function,
  • 7395dfa netfilter: nf_tables: use timestamp to check for set element timeout.

Commit 0854db2 moves netfilter-related data out of the netns_nftables struct

struct netns_nftables {
struct list_head tables;
struct list_head commit_list;
struct list_head module_list;
struct list_head notify_list;
struct mutex commit_mutex;
unsigned int base_seq;
u8 gencursor;
u8 validate_state;
};

into a dynamically associaged net_generic struct nftables_pernet

struct nftables_pernet {
struct list_head tables;
struct list_head commit_list;
struct list_head module_list;
struct list_head notify_list;
struct mutex commit_mutex;
unsigned int base_seq;
u8 validate_state;
};

(Actually the removal of the fields in netns_nftables completing the migration takes place in a separate commit db3685b.) The issue is that netns_nftables is embedded in the net struct, heavily used in the data plane:

#if defined(CONFIG_NF_TABLES) || defined(CONFIG_NF_TABLES_MODULE)
struct netns_nftables nft;
#endif

The fix of CVE-2024-27397 adds a new tstamp field to nftables_pernet. This gives 3 backporting options:

  1. Backport 0854db2, then the fix. The problem is that the net struct in LTS 8.6 has frozen ABI. While multiple fields were added at the end
    RH_KABI_EXTEND(int ipv4_sysctl_ip_fwd_update_priority)
    RH_KABI_EXTEND(int ipv4_sysctl_tcp_min_snd_mss)
    RH_KABI_EXTEND(void *rh_unused) /* available for reuse */
    RH_KABI_EXTEND(siphash_key_t ipv4_ip_id_key)
    RH_KABI_EXTEND(u32 hash_mix)

    the netns_nftables struct is embedded in the middle. Modifying it in any way breaks kABI. Although nftables_pernet can be introduced without removing the migrated fields from netns_nftables this would make no technical sense.
  2. Add the tstamp field to the old struct netns_nftables. However, this also breaks kABI and none of the RH_KABI_* magic macros can be justified in silencing the checker.
  3. Create the nftables_pernet struct just for the tstamp field alone as part of the CVE-2024-27397 fix, backporting some of the necessary boiler plate code from 0854db2 to make the net_genric infrastructure functional.

The last option was chosen.

kABI check: passed

[1/2] kabi_check_kernel	Check ABI of kernel [ciqlts8_6-CVE-batch-45]	_kabi_check_kernel__x86_64--test--ciqlts8_6-CVE-batch-45
+ dist_git_version=el-8.6
+ local_version=ciqlts8_6-CVE-batch-45
+ arch=x86_64
+ user=pvts
+ buildmachine=x86_64--build--ciqlts8_6
+ virsh_timeout=600
+ ssh_daemon_wait=20
+ src_dir=/mnt/code/kernel-dist-git-el-8.6
+ build_dir=/mnt/build_files/kernel-src-tree-ciqlts8_6-CVE-batch-45
+ sudo chmod +x /data/src/ctrliq-github-haskell/kernel-dist-git-el-8.6/SOURCES/check-kabi
+ ninja-back/virssh.xsh --max 8 --shutdown-on-success --shutdown-on-failure --timeout 600 --ssh-daemon-wait 20 pvts x86_64--build--ciqlts8_6 ''\''/mnt/code/kernel-dist-git-el-8.6/SOURCES/check-kabi'\'' -k '\''/mnt/code/kernel-dist-git-el-8.6/SOURCES/Module.kabi_x86_64'\'' -s '\''/mnt/build_files/kernel-src-tree-ciqlts8_6-CVE-batch-45/Module.symvers'\'''
kABI check passed
+ touch state/kernels/ciqlts8_6-CVE-batch-45/x86_64/kabi_checked

Boot test: passed

boot-test.log

Kselftests: passed relative

Reference

kselftests–ciqlts8_6–run1.log
kselftests–ciqlts8_6–run2.log

Patch

kselftests–ciqlts8_6-CVE-batch-45–run1.log
kselftests–ciqlts8_6-CVE-batch-45–run2.log
kselftests–ciqlts8_6-CVE-batch-45–run3.log

Comparison

The tests results for the reference and the patch are the same.

$ ktests.xsh diff -d kselftests*.log

Column    File
--------  --------------------------------------------
Status0   kselftests--ciqlts8_6--run1.log
Status1   kselftests--ciqlts8_6--run2.log
Status2   kselftests--ciqlts8_6-CVE-batch-45--run1.log
Status3   kselftests--ciqlts8_6-CVE-batch-45--run2.log
Status4   kselftests--ciqlts8_6-CVE-batch-45--run3.log

full-test-results-comparison.log

jira VULN-68359
cve-pre CVE-2024-27397
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d111692

This allows to remove an expired element which is not possible in other
existing set backends, this is more noticeable if gc-interval is high so
expired elements remain in the tree. On-demand gc also does not help in
this case, because this is delete element path. Return NULL if element
has expired.

Fixes: 8d8540c ("netfilter: nft_set_rbtree: add timeout support")
	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
	Signed-off-by: Florian Westphal <fw@strlen.de>
(cherry picked from commit f8710dd1d382c766f639e05bdcd22443cd06e807)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-68359
cve CVE-2024-27397
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit 7395dfa
upstream-diff This commit combines the upstream fix 7395dfa and
  fragments of 0854db2 (`netfilter:
  nf_tables: use net_generic infra for transaction data') and its
  follow-up d59d2f8 (`netfilter:
  nftables: add nft_pernet() helper function'). Commit 0854db2 moves
  most of the nft-related data out of the general `net' struct to a
  `net_generic' extension. The CVE-2024-27397 fix introduces a new field
  to that extension. It could not have been put where the other moved
  fields resided originally (`struct netns_nftables') because it would
  break kABI. At the same time 0854db2 could not have been backported
  fully because it would also break kABI. The solution was to introduce
  the `net_generic' extension as in 0854db2 but to keep it narrowed to
  just the `tstamp' field needed by the CVE-2024-27397 bugfix.  As a
  result the following differences from the upstream fix can be observed:
  nf_tables.h
        1. No `nftables_pernet' defined to place `tstamp' in (done in
           7395dfa), so defined the singleton.
        2. Added `nf_tables_net_id' global, `nft_pernet' helper and
           `generic.h' inclusion - taken directly from d59d2f8.
  nf_tables_api.c
        1. Defined `nf_tables_net_id' as it was done in 0854db2. It
           required `EXPORT_SYMBOL_GPL' in LTS 8.6 for the proper loading
           of the compiled `nf_tables_set.ko' module.
        2. Omitted changes to `nft_trans_gc_catchall_sync()', because LTS
           8.6 doesn't have this function nor any equivalent.
        3. In `nf_tables_valid_genid()' added the `nft_net' initialization
           (0854db2 + d59d2f8).
        4. Added fields `.id' and `.size' to the `nf_tables_net_ops'
           struct as it was done in 0854db2.
  net/netfilter/nft_set_pipapo.c
        1. Context conflicts in `nft_pipapo_get()' due to missing
           9dad402, otherwise no diffs.
        2. In `nft_pipapo_insert()' addded `tstamp' argument to the two
           `pipapo_get' calls, just like in the upstream, although in the
           upstream these two are further apart.
        3. In `pipapo_gc()' needed to include `net' variable.
        4. Function `__nft_rbtree_insert' underwent some substantial
           changes across many non-backported commits, but they were
           irrelevant to the introduced change. Preserved the spirit of
           the fix by changing all `nft_set_elem_expired(&rbe->ext)' calls
           to `__nft_set_elem_expired(&rbe->ext, tstamp)'.
        5. In `nft_rbtree_gc()' adapted the change to read `net' from
           `set' obtained from `work' provided as argument instead of from
           `set' given in the argument directly like it's done in the
           upstream.

Add a timestamp field at the beginning of the transaction, store it
in the nftables per-netns area.

Update set backend .insert, .deactivate and sync gc path to use the
timestamp, this avoids that an element expires while control plane
transaction is still unfinished.

.lookup and .update, which are used from packet path, still use the
current time to check if the element has expired. And .get path and dump
also since this runs lockless under rcu read size lock. Then, there is
async gc which also needs to check the current time since it runs
asynchronously from a workqueue.

Fixes: c3e1b00 ("netfilter: nf_tables: add set element timeout support")
	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 6af6b4227e09d8b93ab9a2a26f24fa85cead58d1)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-184372
cve CVE-2026-43125
commit-author Ezrak1e <ezrakiez@gmail.com>
commit 080e556
upstream-diff Kept the `len' check within the `dlm_search_rsb_tree'
  function like in the upstream, despite the fact that in LTS 8.6 the
  out-of-bounds write can happen not in `dlm_search_rsb_tree' but in the
  `rsb_cmp' called by it. Function `rsb_cmp' is not suited for returning
  error codes, assumed to be always called with correct arguments. The
  code analysis established that `dlm_search_rsb_tree' was the last
  place where this may not have been the case.

The len parameter in dlm_dump_rsb_name() is not validated and comes
from network messages. When it exceeds DLM_RESNAME_MAXLEN, it can
cause out-of-bounds write in dlm_search_rsb_tree().

Add length validation to prevent potential buffer overflow.

	Signed-off-by: Ezrak1e <ezrakiez@gmail.com>
	Signed-off-by: Alexander Aring <aahringo@redhat.com>
	Signed-off-by: David Teigland <teigland@redhat.com>
(cherry picked from commit 118986f3da1b4b0687e6f061fdedc60e6dcb7d05)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-184338
cve CVE-2026-43116
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit bffcaad
upstream-diff In `ctnetlink_get_expect()' preserved the intent of the
  change (spinlock protection spanning from `nf_ct_expect_find_get()' to
  `nf_ct_expect_put()' + moving `nlmsg_new()' out of the critical
  section) and adjusted for the existing goto-ladder teardown pattern.
  In the upstream it was scrapped in
  e0241ae ("netfilter: use
  nfnetlink_unicast()"), but that commit introduces way too many
  unrealated changes to be backported just for this port's convenience.

Holding reference on the expectation is not sufficient, the master
conntrack object can just go away, making exp->master invalid.

To access exp->master safely:

- Grab the nf_conntrack_expect_lock, this gets serialized with
  clean_from_lists() which also holds this lock when the master
  conntrack goes away.

- Hold reference on master conntrack via nf_conntrack_find_get().
  Not so easy since the master tuple to look up for the master conntrack
  is not available in the existing problematic paths.

This patch goes for extending the nf_conntrack_expect_lock section
to address this issue for simplicity, in the cases that are described
below this is just slightly extending the lock section.

The add expectation command already holds a reference to the master
conntrack from ctnetlink_create_expect().

However, the delete expectation command needs to grab the spinlock
before looking up for the expectation. Expand the existing spinlock
section to address this to cover the expectation lookup. Note that,
the nf_ct_expect_iterate_net() calls already grabs the spinlock while
iterating over the expectation table, which is correct.

The get expectation command needs to grab the spinlock to ensure master
conntrack does not go away. This also expands the existing spinlock
section to cover the expectation lookup too. I needed to move the
netlink skb allocation out of the spinlock to keep it GFP_KERNEL.

For the expectation events, the IPEXP_DESTROY event is already delivered
under the spinlock, just move the delivery of IPEXP_NEW under the
spinlock too because the master conntrack event cache is reached through
exp->master.

While at it, add lockdep notations to help identify what codepaths need
to grab the spinlock.

	Signed-off-by: Florian Westphal <fw@strlen.de>
	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 79f21a8db667336089a743cbda1e03bf3e348266)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-183687
cve CVE-2026-43027
commit-author Qi Tang <tpluszz77@gmail.com>
commit a242a9a

nf_conntrack_helper_unregister() calls nf_ct_expect_iterate_destroy()
to remove expectations belonging to the helper being unregistered.
However, it passes NULL instead of the helper pointer as the data
argument, so expect_iter_me() never matches any expectation and all
of them survive the cleanup.

After unregister returns, nfnl_cthelper_del() frees the helper
object immediately.  Subsequent expectation dumps or packet-driven
init_conntrack() calls then dereference the freed exp->helper,
causing a use-after-free.

Pass the actual helper pointer so expectations referencing it are
properly destroyed before the helper object is freed.

  BUG: KASAN: slab-use-after-free in string+0x38f/0x430
  Read of size 1 at addr ffff888003b14d20 by task poc/103
  Call Trace:
   string+0x38f/0x430
   vsnprintf+0x3cc/0x1170
   seq_printf+0x17a/0x240
   exp_seq_show+0x2e5/0x560
   seq_read_iter+0x419/0x1280
   proc_reg_read+0x1ac/0x270
   vfs_read+0x179/0x930
   ksys_read+0xef/0x1c0
  Freed by task 103:
  The buggy address is located 32 bytes inside of
   freed 192-byte region [ffff888003b14d00, ffff888003b14dc0)

Fixes: ac7b848 ("netfilter: expect: add and use nf_ct_expect_iterate helpers")
	Signed-off-by: Qi Tang <tpluszz77@gmail.com>
	Reviewed-by: Phil Sutter <phil@nwl.cc>
	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit b1bf205e1e2a88fc5039fe80e221c10218119f05)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
@github-actions

Copy link
Copy Markdown

🤖 Validation Checks In Progress Workflow run: https://github.com/ctrliq/kernel-src-tree/actions/runs/35283014341

@github-actions

Copy link
Copy Markdown

🔍 Upstream Linux Kernel Commit Check

  • ⚠️ PR commit 73cd6bbc4baa (netfilter: ctnetlink: ensure safe access to master conntrack) references upstream commit
    bffcaad9afdf which has been referenced by a Fixes: tag in the upstream
    Linux kernel:
    b8b09dc2bf35 netfilter: nf_conntrack_expect: use conntrack GC to reap expectations (Pablo Neira Ayuso) (CVE-2026-80668)

This is an automated message from the kernel commit checker workflow.

@github-actions

Copy link
Copy Markdown

🔍 Interdiff Analysis

  • ⚠️ PR commit ce351102ccd4 (netfilter: nf_tables: use timestamp to check for set element timeout) → upstream 7395dfacfff6
    Differences found:
================================================================================
*    DELTA DIFFERENCES - code changes that differ between the patches          *
================================================================================

--- b/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -13,7 +13,6 @@
 #include <net/netfilter/nf_flow_table.h>
 #include <net/netlink.h>
 #include <net/flow_offload.h>
-#include <net/netns/generic.h>
 
 struct module;
 
@@ -1539,19 +1538,2 @@
 #endif
-
-struct nftables_pernet {
-	u64			tstamp;
-};
-
-extern unsigned int nf_tables_net_id;
-
-static inline struct nftables_pernet *nft_pernet(const struct net *net)
-{
-	return net_generic(net, nf_tables_net_id);
-}
-
-static inline u64 nft_net_tstamp(const struct net *net)
-{
-	return nft_pernet(net)->tstamp;
-}
-
 #endif /* _NET_NF_TABLES_H */
--- b/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -28,9 +28,6 @@
 
 #define NFT_MODULE_AUTOLOAD_LIMIT (MODULE_NAME_LEN - sizeof("nft-expr-255-"))
 
-unsigned int nf_tables_net_id __read_mostly;
-EXPORT_SYMBOL_GPL(nf_tables_net_id);
-
 static LIST_HEAD(nf_tables_expressions);
 static LIST_HEAD(nf_tables_objects);
 static LIST_HEAD(nf_tables_flowtables);
@@ -8141,11 +8138,9 @@
 
 static bool nf_tables_valid_genid(struct net *net, u32 genid)
 {
-	struct nftables_pernet *nft_net = nft_pernet(net);
 	bool genid_ok;
 
 	mutex_lock(&net->nft_commit_mutex);
-	nft_net->tstamp = get_jiffies_64();
 
 	genid_ok = genid == 0 || net->nft.base_seq == genid;
 	if (!genid_ok)
@@ -8763,8 +8758,6 @@
 	.init		= nf_tables_init_net,
 	.pre_exit	= nf_tables_pre_exit_net,
 	.exit		= nf_tables_exit_net,
-	.id		= &nf_tables_net_id,
-	.size		= sizeof(struct nftables_pernet),
 };
 
 static int __init nf_tables_module_init(void)
--- b/net/netfilter/nft_set_pipapo.c
+++ b/net/netfilter/nft_set_pipapo.c
@@ -743,7 +743,7 @@
 		     const struct nft_set_elem *elem, unsigned int flags)
 {
 	return pipapo_get(net, set, (const u8 *)elem->key.val.data,
-			  nft_genmask_cur(net), get_jiffies_64());
+			 nft_genmask_cur(net));
 }
 
 /**
@@ -1095,19 +1095,17 @@
 	union nft_pipapo_map_bucket rulemap[NFT_PIPAPO_MAX_FIELDS];
 	const u8 *start = (const u8 *)elem->key.val.data, *end;
 	struct nft_pipapo_elem *e = elem->priv, *dup;
-	u64 tstamp = nft_net_tstamp(net);
 	struct nft_pipapo *priv = nft_set_priv(set);
 	struct nft_pipapo_match *m = priv->clone;
 	u8 genmask = nft_genmask_next(net);
 	struct nft_pipapo_field *f;
 	int i, bsize_max, err = 0;
 
-	dup = pipapo_get(net, set, start, genmask, tstamp);
+	dup = pipapo_get(net, set, start, genmask);
 	if (PTR_ERR(dup) == -ENOENT) {
 		if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END)) {
 			end = (const u8 *)nft_set_ext_key_end(ext)->data;
-			dup = pipapo_get(net, set, end, nft_genmask_next(net),
-					 tstamp);
+			dup = pipapo_get(net, set, end, nft_genmask_next(net));
 		} else {
 			end = start;
 		}
@@ -1441,8 +1439,6 @@
 static void pipapo_gc(const struct nft_set *set, struct nft_pipapo_match *m)
 {
 	struct nft_pipapo *priv = nft_set_priv(set);
-	struct net *net = read_pnet(&set->net);
-	u64 tstamp = nft_net_tstamp(net);
 	int rules_f0, first_rule = 0;
 
 	while ((rules_f0 = pipapo_rules_same_key(m->f, first_rule))) {
@@ -1468,7 +1464,7 @@
 		f--;
 		i--;
 		e = f->mt[rulemap[i].to].e;
-		if (__nft_set_elem_expired(&e->ext, tstamp) &&
+		if (nft_set_elem_expired(&e->ext) &&
 		    !nft_set_elem_mark_busy(&e->ext)) {
 			priv->dirty = true;
 			pipapo_drop(m, rulemap);
--- b/net/netfilter/nft_set_rbtree.c
+++ b/net/netfilter/nft_set_rbtree.c
@@ -282,13 +281,13 @@
 			if (nft_rbtree_interval_start(new)) {
 				if (nft_rbtree_interval_end(rbe) &&
 				    nft_set_elem_active(&rbe->ext, genmask) &&
-				    !__nft_set_elem_expired(&rbe->ext, tstamp) && !*p)
+				    !nft_set_elem_expired(&rbe->ext) && !*p)
 					overlap = false;
 			} else {
 				overlap = nft_rbtree_interval_end(rbe) &&
 					  nft_set_elem_active(&rbe->ext,
 							      genmask) &&
-					  !__nft_set_elem_expired(&rbe->ext, tstamp);
+					  !nft_set_elem_expired(&rbe->ext);
 			}
 		} else if (d > 0) {
 			p = &parent->rb_right;
@@ -297,9 +296,9 @@
 				overlap = nft_rbtree_interval_end(rbe) &&
 					  nft_set_elem_active(&rbe->ext,
 							      genmask) &&
-					  !__nft_set_elem_expired(&rbe->ext, tstamp);
+					  !nft_set_elem_expired(&rbe->ext);
 			} else if (nft_set_elem_active(&rbe->ext, genmask) &&
-				   !__nft_set_elem_expired(&rbe->ext, tstamp)) {
+				   !nft_set_elem_expired(&rbe->ext)) {
 				overlap = nft_rbtree_interval_end(rbe);
 			}
 		} else {
@@ -308,17 +307,17 @@
 				p = &parent->rb_left;
 
 				if (nft_set_elem_active(&rbe->ext, genmask) &&
-				    !__nft_set_elem_expired(&rbe->ext, tstamp))
+				    !nft_set_elem_expired(&rbe->ext))
 					overlap = false;
 			} else if (nft_rbtree_interval_start(rbe) &&
 				   nft_rbtree_interval_end(new)) {
 				p = &parent->rb_right;
 
 				if (nft_set_elem_active(&rbe->ext, genmask) &&
-				    !__nft_set_elem_expired(&rbe->ext, tstamp))
+				    !nft_set_elem_expired(&rbe->ext))
 					overlap = false;
 			} else if (nft_set_elem_active(&rbe->ext, genmask) &&
-				   !__nft_set_elem_expired(&rbe->ext, tstamp)) {
+				   !nft_set_elem_expired(&rbe->ext)) {
 				*ext = &rbe->ext;
 				return -EEXIST;
 			} else {
@@ -469,11 +468,9 @@
 	struct nft_rbtree *priv;
 	struct rb_node *node;
 	struct nft_set *set;
-	u64 tstamp;
 
 	priv = container_of(work, struct nft_rbtree, gc_work.work);
 	set  = nft_set_container_of(priv);
-	tstamp = nft_net_tstamp(read_pnet(&set->net));
 
 	write_lock_bh(&priv->lock);
 	write_seqcount_begin(&priv->count);

################################################################################
!    REJECTED PATCH2 HUNKS - could not be compared; manual review needed       !
################################################################################

--- b/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1785,6 +1791,7 @@
 	struct list_head	notify_list;
 	struct mutex		commit_mutex;
 	u64			table_handle;
+	u64			tstamp;
 	unsigned int		base_seq;
 	unsigned int		gc_seq;
 	u8			validate_state;
@@ -1797,6 +1804,11 @@
 	return net_generic(net, nf_tables_net_id);
 }
 
+static inline u64 nft_net_tstamp(const struct net *net)
+{
+	return nft_pernet(net)->tstamp;
+}
+
 #define __NFT_REDUCE_READONLY	1UL
 #define NFT_REDUCE_READONLY	(void *)__NFT_REDUCE_READONLY
 
--- b/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -9827,6 +9827,7 @@
 struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc)
 {
 	struct nft_set_elem_catchall *catchall, *next;
+	u64 tstamp = nft_net_tstamp(gc->net);
 	const struct nft_set *set = gc->set;
 	struct nft_elem_priv *elem_priv;
 	struct nft_set_ext *ext;
@@ -9836,7 +9837,7 @@
 	list_for_each_entry_safe(catchall, next, &set->catchall_list, list) {
 		ext = nft_set_elem_ext(set, catchall->elem);
 
-		if (!nft_set_elem_expired(ext))
+		if (!__nft_set_elem_expired(ext, tstamp))
 			continue;
 
 		gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL);
@@ -10622,6 +10623,7 @@
 	bool genid_ok;
 
 	mutex_lock(&nft_net->commit_mutex);
+	nft_net->tstamp = get_jiffies_64();
 
 	genid_ok = genid == 0 || nft_net->base_seq == genid;
 	if (!genid_ok)
--- b/net/netfilter/nft_set_pipapo.c
+++ b/net/netfilter/nft_set_pipapo.c
@@ -608,7 +610,7 @@
 	struct nft_pipapo_elem *e;
 
 	e = pipapo_get(net, set, (const u8 *)elem->key.val.data,
-		       nft_genmask_cur(net));
+		       nft_genmask_cur(net), get_jiffies_64());
 	if (IS_ERR(e))
 		return ERR_CAST(e);
 
@@ -1175,6 +1177,7 @@
 	struct nft_pipapo_match *m = priv->clone;
 	u8 genmask = nft_genmask_next(net);
 	struct nft_pipapo_elem *e, *dup;
+	u64 tstamp = nft_net_tstamp(net);
 	struct nft_pipapo_field *f;
 	const u8 *start_p, *end_p;
 	int i, bsize_max, err = 0;
@@ -1184,7 +1187,7 @@
 	else
 		end = start;
 
-	dup = pipapo_get(net, set, start, genmask);
+	dup = pipapo_get(net, set, start, genmask, tstamp);
 	if (!IS_ERR(dup)) {
 		/* Check if we already have the same exact entry */
 		const struct nft_data *dup_key, *dup_end;
@@ -1206,7 +1209,7 @@
 
 	if (PTR_ERR(dup) == -ENOENT) {
 		/* Look for partially overlapping entries */
-		dup = pipapo_get(net, set, end, nft_genmask_next(net));
+		dup = pipapo_get(net, set, end, nft_genmask_next(net), tstamp);
 	}
 
 	if (PTR_ERR(dup) != -ENOENT) {
@@ -1562,6 +1565,7 @@
 {
 	struct nft_pipapo *priv = nft_set_priv(set);
 	struct net *net = read_pnet(&set->net);
+	u64 tstamp = nft_net_tstamp(net);
 	int rules_f0, first_rule = 0;
 	struct nft_pipapo_elem *e;
 	struct nft_trans_gc *gc;
@@ -1596,7 +1600,7 @@
 		/* synchronous gc never fails, there is no need to set on
 		 * NFT_SET_ELEM_DEAD_BIT.
 		 */
-		if (nft_set_elem_expired(&e->ext)) {
+		if (__nft_set_elem_expired(&e->ext, tstamp)) {
 			priv->dirty = true;
 
 			gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL);
--- b/net/netfilter/nft_set_rbtree.c
+++ b/net/netfilter/nft_set_rbtree.c
@@ -361,7 +362,7 @@
 		/* perform garbage collection to avoid bogus overlap reports
 		 * but skip new elements in this transaction.
 		 */
-		if (nft_set_elem_expired(&rbe->ext) &&
+		if (__nft_set_elem_expired(&rbe->ext, tstamp) &&
 		    nft_set_elem_active(&rbe->ext, cur_genmask)) {
 			const struct nft_rbtree_elem *removed_end;
 
@@ -552,6 +553,7 @@
 	const struct nft_rbtree *priv = nft_set_priv(set);
 	const struct rb_node *parent = priv->root.rb_node;
 	u8 genmask = nft_genmask_next(net);
+	u64 tstamp = nft_net_tstamp(net);
 	int d;
 
 	while (parent != NULL) {
@@ -625,6 +627,8 @@
 {
 	struct nft_rbtree *priv = nft_set_priv(set);
 	struct nft_rbtree_elem *rbe, *rbe_end = NULL;
+	struct net *net = read_pnet(&set->net);
+	u64 tstamp = nft_net_tstamp(net);
 	struct rb_node *node, *next;
 	struct nft_trans_gc *gc;
 	struct net *net;
@@ -627,7 +631,6 @@
 	struct nft_rbtree_elem *rbe, *rbe_end = NULL;
 	struct rb_node *node, *next;
 	struct nft_trans_gc *gc;
-	struct net *net;
 
 	set  = nft_set_container_of(priv);
 	net  = read_pnet(&set->net);

================================================================================
*    CONTEXT DIFFERENCES - surrounding code differences between the patches    *
================================================================================

--- b/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -8135,7 +8130,12 @@
 {
-	bool genid_ok;
+	struct nft_set_elem_catchall *catchall, *next;
+	const struct nft_set *set = gc->set;
+	struct nft_elem_priv *elem_priv;
+	struct nft_set_ext *ext;
+	list_for_each_entry_safe(catchall, next, &set->catchall_list, list) {
+		ext = nft_set_elem_ext(set, catchall->elem);
 
-	mutex_lock(&net->nft_commit_mutex);
+		if (!nft_set_elem_expired(ext))
+			continue;
 
-	genid_ok = genid == 0 || net->nft.base_seq == genid;
-	if (!genid_ok)
+		gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL);
@@ -8755,2 +10508,3 @@
 
-static int __init nf_tables_module_init(void)
+	genid_ok = genid == 0 || nft_net->base_seq == genid;
+	if (!genid_ok)
--- b/net/netfilter/nft_set_hash.c
+++ b/net/netfilter/nft_set_hash.c
@@ -59,5 +57,5 @@
-
-	if (memcmp(nft_set_ext_key(&he->ext), x->key, x->set->klen))
+		return 1;
+	if (nft_set_elem_is_dead(&he->ext))
 		return 1;
 	if (nft_set_elem_expired(&he->ext))
 		return 1;
--- b/net/netfilter/nft_set_pipapo.c
+++ b/net/netfilter/nft_set_pipapo.c
@@ -1094,6 +1170,10 @@
 	struct nft_pipapo_match *m = priv->clone;
 	u8 genmask = nft_genmask_next(net);
+	struct nft_pipapo_elem *e, *dup;
 	struct nft_pipapo_field *f;
+	const u8 *start_p, *end_p;
 	int i, bsize_max, err = 0;
+	else
+		end = start;
 
 	dup = pipapo_get(net, set, start, genmask);
@@ -1100,7 +1184,6 @@
 	if (PTR_ERR(dup) == -ENOENT) {
-		if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END)) {
-			end = (const u8 *)nft_set_ext_key_end(ext)->data;
-			dup = pipapo_get(net, set, end, nft_genmask_next(net));
-		} else {
-			end = start;
-		}
+		/* Look for partially overlapping entries */
+		dup = pipapo_get(net, set, end, nft_genmask_next(net));
+	}
+
+	if (PTR_ERR(dup) != -ENOENT) {
@@ -1435,5 +1557,6 @@
 {
 	struct nft_pipapo *priv = nft_set_priv(set);
+	struct net *net = read_pnet(&set->net);
 	int rules_f0, first_rule = 0;
-
-	while ((rules_f0 = pipapo_rules_same_key(m->f, first_rule))) {
+	struct nft_pipapo_elem *e;
+	struct nft_trans_gc *gc;
@@ -1459,7 +1586,7 @@
-		f--;
-		i--;
-		e = f->mt[rulemap[i].to].e;
-		if (nft_set_elem_expired(&e->ext) &&
-		    !nft_set_elem_mark_busy(&e->ext)) {
+		/* synchronous gc never fails, there is no need to set on
+		 * NFT_SET_ELEM_DEAD_BIT.
+		 */
+		if (nft_set_elem_expired(&e->ext)) {
 			priv->dirty = true;
-			pipapo_drop(m, rulemap);
+
+			gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL);
--- b/net/netfilter/nft_set_rbtree.c
+++ b/net/netfilter/nft_set_rbtree.c
@@ -278,7 +276,2 @@
 
-				if (nft_set_elem_active(&rbe->ext, genmask) &&
-				    !nft_set_elem_expired(&rbe->ext))
-					overlap = false;
-			} else if (nft_rbtree_interval_start(rbe) &&
-				   nft_rbtree_interval_end(new)) {
-				p = &parent->rb_right;
+	/* Descend the tree to search for an existing element greater than the
@@ -393,5 +437,4 @@
 	const struct rb_node *parent = priv->root.rb_node;
-	struct nft_rbtree_elem *rbe, *this = elem->priv;
 	u8 genmask = nft_genmask_next(net);
 	int d;
 
@@ -464,9 +619,9 @@
-	struct nft_rbtree *priv;
-	struct rb_node *node;
-	struct nft_set *set;
+{
+	struct nft_rbtree *priv = nft_set_priv(set);
+	struct nft_rbtree_elem *rbe, *rbe_end = NULL;
+	struct rb_node *node, *next;
+	struct nft_trans_gc *gc;
+	struct net *net;
 
-	priv = container_of(work, struct nft_rbtree, gc_work.work);
 	set  = nft_set_container_of(priv);
-
-	write_lock_bh(&priv->lock);
-	write_seqcount_begin(&priv->count);
+	net  = read_pnet(&set->net);
@@ -482,5 +650,5 @@
 		}
 		if (!nft_set_elem_expired(&rbe->ext))
 			continue;
-		if (nft_set_elem_mark_busy(&rbe->ext))
-			continue;
+
+		gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL);
  • ⚠️ PR commit c579921d18d9 (dlm: validate length in dlm_search_rsb_tree) → upstream 080e5563f878
    Differences found:
================================================================================
*    DELTA DIFFERENCES - code changes that differ between the patches          *
================================================================================

--- b/fs/dlm/lock.c
+++ b/fs/dlm/lock.c
@@ -451,9 +451,6 @@
 	struct dlm_rsb *r;
 	int rc;
 
-	if (len > DLM_RESNAME_MAXLEN)
-		return -EINVAL;
-
 	while (node) {
 		r = rb_entry(node, struct dlm_rsb, res_hashnode);
 		rc = rsb_cmp(r, name, len);

################################################################################
!    REJECTED PATCH2 HUNKS - could not be compared; manual review needed       !
################################################################################

--- b/fs/dlm/lock.c
+++ b/fs/dlm/lock.c
@@ -626,7 +626,8 @@
 			struct dlm_rsb **r_ret)
 {
 	char key[DLM_RESNAME_MAXLEN] = {};
-
+	if (len > DLM_RESNAME_MAXLEN)
+		return -EINVAL;
 	memcpy(key, name, len);
 	*r_ret = rhashtable_lookup_fast(rhash, &key, dlm_rhash_rsb_params);
 	if (*r_ret)

================================================================================
*    CONTEXT DIFFERENCES - surrounding code differences between the patches    *
================================================================================

--- b/fs/dlm/lock.c
+++ b/fs/dlm/lock.c
@@ -448,4 +451,4 @@
 
-	while (node) {
-		r = rb_entry(node, struct dlm_rsb, res_hashnode);
-		rc = rsb_cmp(r, name, len);
+	memcpy(key, name, len);
+	*r_ret = rhashtable_lookup_fast(rhash, &key, dlm_rhash_rsb_params);
+	if (*r_ret)
  • ⚠️ PR commit 73cd6bbc4baa (netfilter: ctnetlink: ensure safe access to master conntrack) → upstream bffcaad9afdf
    Differences found:
================================================================================
*    DELTA DIFFERENCES - code changes that differ between the patches          *
================================================================================

--- b/include/net/netfilter/nf_conntrack_core.h
+++ b/include/net/netfilter/nf_conntrack_core.h
@@ -77,6 +77,2 @@
 
-static inline void lockdep_nfct_expect_lock_held(void)
-{
-	lockdep_assert_held(&nf_conntrack_expect_lock);
-}
 #endif /* _NF_CONNTRACK_CORE_H */
--- b/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -3409,18 +3409,9 @@
 	if (err < 0)
 		return err;
 
-	err = -ENOMEM;
-	skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
-	if (skb2 == NULL) {
-		goto out;
-	}
-
-	spin_lock_bh(&nf_conntrack_expect_lock);
 	exp = nf_ct_expect_find_get(net, &zone, &tuple);
-	if (!exp) {
-		spin_unlock_bh(&nf_conntrack_expect_lock);
-		err = -ENOENT;
-		goto free;
+	if (!exp)
+		return -ENOENT;
 	}
 
 	if (cda[CTA_EXPECT_ID]) {
@@ -3429,18 +3420,23 @@
 		if (id != nf_expect_get_id(exp)) {
 			nf_ct_expect_put(exp);
 			spin_unlock_bh(&nf_conntrack_expect_lock);
-			err = -ENOENT;
-			goto free;
+			kfree_skb(skb2);
+			return -ENOENT;
 		}
 	}
 
+	err = -ENOMEM;
+	skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (skb2 == NULL) {
+		nf_ct_expect_put(exp);
+		goto out;
+	}
+
 	rcu_read_lock();
 	err = ctnetlink_exp_fill_info(skb2, NETLINK_CB(skb).portid,
 				      nlh->nlmsg_seq, IPCTNL_MSG_EXP_NEW, exp);
 	rcu_read_unlock();
 	nf_ct_expect_put(exp);
-	spin_unlock_bh(&nf_conntrack_expect_lock);
-
 	if (err <= 0)
 		goto free;
 

################################################################################
!    REJECTED PATCH2 HUNKS - could not be compared; manual review needed       !
################################################################################

--- b/include/net/netfilter/nf_conntrack_core.h
+++ b/include/net/netfilter/nf_conntrack_core.h
@@ -83,6 +83,11 @@
 
 extern spinlock_t nf_conntrack_expect_lock;
 
+static inline void lockdep_nfct_expect_lock_held(void)
+{
+	lockdep_assert_held(&nf_conntrack_expect_lock);
+}
+
 /* ctnetlink code shared by both ctnetlink and nf_conntrack_bpf */
 
 static inline void __nf_ct_set_timeout(struct nf_conn *ct, u64 timeout)
--- b/net/netfilter/nf_conntrack_expect.c
+++ b/net/netfilter/nf_conntrack_expect.c
@@ -119,6 +120,8 @@
 
 bool nf_ct_remove_expect(struct nf_conntrack_expect *exp)
 {
+	lockdep_nfct_expect_lock_held();
+
 	if (timer_delete(&exp->timeout)) {
 		nf_ct_unlink_expect(exp);
 		nf_ct_expect_put(exp);
@@ -178,6 +181,8 @@
 	struct nf_conntrack_expect *i, *exp = NULL;
 	unsigned int h;
 
+	lockdep_nfct_expect_lock_held();
+
 	if (!cnet->expect_count)
 		return NULL;
 
--- b/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -3355,6 +3355,11 @@
 	if (err < 0)
 		return err;
 
+	skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!skb2)
+		return -ENOMEM;
+
+	spin_lock_bh(&nf_conntrack_expect_lock);
 	exp = nf_ct_expect_find_get(info->net, &zone, &tuple);
 	if (!exp)
 		return -ENOENT;
@@ -3356,7 +3361,9 @@
 		return err;
 
 	exp = nf_ct_expect_find_get(info->net, &zone, &tuple);
-	if (!exp)
+	if (!exp) {
+		spin_unlock_bh(&nf_conntrack_expect_lock);
+		kfree_skb(skb2);
 		return -ENOENT;
 
 	if (cda[CTA_EXPECT_ID]) {
@@ -3367,12 +3377,6 @@
 		}
 	}
 
-	skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
-	if (!skb2) {
-		nf_ct_expect_put(exp);
-		return -ENOMEM;
-	}
-
 	rcu_read_lock();
 	err = ctnetlink_exp_fill_info(skb2, NETLINK_CB(skb).portid,
 				      info->nlh->nlmsg_seq, IPCTNL_MSG_EXP_NEW,
@@ -3376,6 +3380,8 @@
 				      exp);
 	rcu_read_unlock();
 	nf_ct_expect_put(exp);
+	spin_unlock_bh(&nf_conntrack_expect_lock);
+
 	if (err <= 0) {
 		kfree_skb(skb2);
 		return -ENOMEM;
@@ -3433,7 +3441,8 @@
 
 		/* bump usage count to 2 */
 		exp = nf_ct_expect_find_get(info->net, &zone, &tuple);
-		if (!exp)
+		if (!exp) {
+			spin_unlock_bh(&nf_conntrack_expect_lock);
 			return -ENOENT;
 
 		if (cda[CTA_EXPECT_ID]) {
@@ -3443,7 +3454,6 @@
 		}
 
 		/* after list removal, usage count == 1 */
-		spin_lock_bh(&nf_conntrack_expect_lock);
 		if (timer_delete(&exp->timeout)) {
 			nf_ct_unlink_expect_report(exp, NETLINK_CB(skb).portid,
 						   nlmsg_report(info->nlh));

================================================================================
*    CONTEXT DIFFERENCES - surrounding code differences between the patches    *
================================================================================

--- b/include/net/netfilter/nf_conntrack_core.h
+++ b/include/net/netfilter/nf_conntrack_core.h
@@ -72,4 +72,6 @@
 
 extern spinlock_t nf_conntrack_expect_lock;
 
-#endif /* _NF_CONNTRACK_CORE_H */
+/* ctnetlink code shared by both ctnetlink and nf_conntrack_bpf */
+
+static inline void __nf_ct_set_timeout(struct nf_conn *ct, u64 timeout)
--- b/net/netfilter/nf_conntrack_ecache.c
+++ b/net/netfilter/nf_conntrack_ecache.c
@@ -238,3 +239,3 @@
 	rcu_read_lock();
-	notify = rcu_dereference(net->ct.nf_expect_event_cb);
+	notify = rcu_dereference(net->ct.nf_conntrack_event_cb);
 	if (!notify)
--- b/net/netfilter/nf_conntrack_expect.c
+++ b/net/netfilter/nf_conntrack_expect.c
@@ -49,4 +48,5 @@
 	struct net *net = nf_ct_exp_net(exp);
+	struct nf_conntrack_net *cnet;
 
 	WARN_ON(!master_help);
 	WARN_ON(timer_pending(&exp->timeout));
@@ -105,5 +103,5 @@
 bool nf_ct_remove_expect(struct nf_conntrack_expect *exp)
 {
-	if (del_timer(&exp->timeout)) {
+	if (timer_delete(&exp->timeout)) {
 		nf_ct_unlink_expect(exp);
 		nf_ct_expect_put(exp);
@@ -162,5 +173,5 @@
 	unsigned int h;
 
-	if (!net->ct.expect_count)
+	if (!cnet->expect_count)
 		return NULL;
 
--- b/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -3352,7 +3352,7 @@
 	if (err < 0)
 		return err;
 
-	exp = nf_ct_expect_find_get(net, &zone, &tuple);
+	exp = nf_ct_expect_find_get(info->net, &zone, &tuple);
 	if (!exp)
 		return -ENOENT;
 
@@ -3423,14 +3369,14 @@
 	}
 
-	err = -ENOMEM;
 	skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
-	if (skb2 == NULL) {
+	if (!skb2) {
 		nf_ct_expect_put(exp);
-		goto out;
+		return -ENOMEM;
 	}
 
 	rcu_read_lock();
 	err = ctnetlink_exp_fill_info(skb2, NETLINK_CB(skb).portid,
-				      nlh->nlmsg_seq, IPCTNL_MSG_EXP_NEW, exp);
+				      info->nlh->nlmsg_seq, IPCTNL_MSG_EXP_NEW,
+				      exp);
 	rcu_read_unlock();
 	nf_ct_expect_put(exp);
@@ -3489,13 +3427,14 @@
 			return err;
 
 		/* bump usage count to 2 */
-		exp = nf_ct_expect_find_get(net, &zone, &tuple);
+		exp = nf_ct_expect_find_get(info->net, &zone, &tuple);
 		if (!exp)
 			return -ENOENT;
 
 		if (cda[CTA_EXPECT_ID]) {
 			__be32 id = nla_get_be32(cda[CTA_EXPECT_ID]);
-			if (ntohl(id) != (u32)(unsigned long)exp) {
+
+			if (id != nf_expect_get_id(exp)) {
 				nf_ct_expect_put(exp);
 				return -ENOENT;
 			}
@@ -3503,6 +3442,6 @@
 
 		/* after list removal, usage count == 1 */
 		spin_lock_bh(&nf_conntrack_expect_lock);
-		if (del_timer(&exp->timeout)) {
+		if (timer_delete(&exp->timeout)) {
 			nf_ct_unlink_expect_report(exp, NETLINK_CB(skb).portid,
-						   nlmsg_report(nlh));
+						   nlmsg_report(info->nlh));
  • ⚠️ PR commit 7e7ecec26236 (netfilter: nf_conntrack_helper: pass helper to expect cleanup) → upstream a242a9ae58aa
    Differences found:
================================================================================
*    CONTEXT DIFFERENCES - surrounding code differences between the patches    *
================================================================================

--- b/net/netfilter/nf_conntrack_helper.c
+++ b/net/netfilter/nf_conntrack_helper.c
@@ -412,7 +412,7 @@
 	 */
 	synchronize_rcu();
 
 	nf_ct_expect_iterate_destroy(expect_iter_me, NULL);
 	nf_ct_iterate_destroy(unhelp, me);
 
-	/* Maybe someone has gotten the helper already when unhelp above.
+	/* nf_ct_iterate_destroy() does an unconditional synchronize_rcu() as

This is an automated interdiff check for backported commits.

@github-actions

Copy link
Copy Markdown

JIRA PR Check Results

5 commit(s) with issues found:

Commit 7e7ecec26236

Summary: netfilter: nf_conntrack_helper: pass helper to expect cleanup

❌ Errors:

  • VULN-183687: Status is 'To Do', expected 'In Progress'

⚠️ Warnings:

  • VULN-183687: No time logged - please log time manually

Commit 73cd6bbc4baa

Summary: netfilter: ctnetlink: ensure safe access to master conntrack

❌ Errors:

  • VULN-184338: Status is 'To Do', expected 'In Progress'

⚠️ Warnings:

  • VULN-184338: No time logged - please log time manually

Commit c579921d18d9

Summary: dlm: validate length in dlm_search_rsb_tree

❌ Errors:

  • VULN-184372: Status is 'To Do', expected 'In Progress'

⚠️ Warnings:

  • VULN-184372: No time logged - please log time manually

Commit ce351102ccd4

Summary: netfilter: nf_tables: use timestamp to check for set element timeout

❌ Errors:

  • VULN-68359: CVE mismatch - Commit has CVE-2024-27397 but VULN ticket does not
  • VULN-68359: Status is 'To Do', expected 'In Progress'

⚠️ Warnings:

  • VULN-68359: VULN ticket has CVE-2025-38001 but commit does not
  • VULN-68359: No time logged - please log time manually

Commit e93fde0fe058

Summary: netfilter: nft_set_rbtree: .deactivate fails if element has expired

❌ Errors:

  • VULN-68359: CVE mismatch - Commit has CVE-2024-27397 but VULN ticket does not
  • VULN-68359: Status is 'To Do', expected 'In Progress'

⚠️ Warnings:

  • VULN-68359: VULN ticket has CVE-2025-38001 but commit does not
  • VULN-68359: No time logged - please log time manually

Summary: Checked 5 commit(s) total.

@github-actions

Copy link
Copy Markdown

Validation checks completed with issues View full results: https://github.com/ctrliq/kernel-src-tree/actions/runs/35283014341

@PlaidCat
PlaidCat requested a review from a team September 18, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant