Skip to content

[LTS 8.6] netfilter: CVE-2026-23392, CVE-2026-23231 - #1635

Open
pvts-mat wants to merge 8 commits into
ctrliq:ciqlts8_6from
pvts-mat:CVE-batch-46_ciqlts8_6
Open

pvts-mat wants to merge 8 commits into
ctrliq:ciqlts8_6from
pvts-mat:CVE-batch-46_ciqlts8_6

Conversation

@pvts-mat

Copy link
Copy Markdown
Contributor

[LTS 8.6]

CVE-2026-23392 VULN-179371
CVE-2026-23231 VULN-177693

PR structure

The PR addresses two closely related cves CVE-2026-23231 and CVE-2026-23392.

CVE-2026-23392 chains `netfilter: nf_tables: release flowtable after rcu grace period on error`
CVE-2026-23231 flowtables `netfilter: nf_tables: fix use-after-free in nf_tables_addchain()`

The fixing commits rest on a common prerequisite d472e98 netfilter: nf_tables: register hooks last when adding new chain/flowtable. The rest of the commits are the prerequisites for this prerequisite - marked all as cve-pre CVE-2026-23231 though could have been cve-pre CVE-2026-23392 as well.

While the fixes are very simple (one line added) the problem being solved was far from it, which necessitated deeper analysis before transfering them to LTS 8.6.

Commits

netfilter: nf_tables: release flowtable after rcu grace period on error

jira VULN-179371
cve CVE-2026-23392
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d73f4b53aaaea4c95f245e491aa5eeb8a21874ce
netfilter: nf_tables: fix use-after-free in nf_tables_addchain()

jira VULN-177693
cve CVE-2026-23231
commit-author Inseo An <y0un9sa@gmail.com>
commit 71e99ee20fc3f662555118cf1159443250647533
netfilter: nf_tables: register hooks last when adding new chain/flowtable

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d472e9853d7b46a6b094224d131d09ccd3a03daf
upstream-diff |
  1. Needed to add forward declaration of `nft_chain_del()' so that it
     could have been used in the earlier-defined
     `nf_tables_addchain()'. In the upstream the ordering of definitions
     doesn't play a role because `nft_chain_del()' was made public in
     d0e2c7de92c7f2b3d355ad76b0bb9fc43d1beb87 (`netfilter: nf_tables: add
     NFT_CHAIN_BINDING') and declared in the nf_tables.h header.
  2. In `nf_tables_addchain()' preserved the intent of the change (moving
     `nf_tables_register_hook()' call to the end of the function), while
     taking into account the deviations from the upstream.
     1. Re-arranged the cleanup ladder and goto targets to ensure every
        allocator is paired with the corresponding deallocator.
         Alloc                      Dealloc
        --------------------------------------------------------
         ctx->chain contruction     nf_tables_chain_destroy()
         nf_tables_register_hook()  nf_tables_unregister_hook()
         nft_chain_add()            nft_chain_del()
         nft_trans_chain_add()      nft_trans_destroy()
        Took into account the missing
        1689f25924ada8fe14a4a82c38925d04994c7142 (`netfilter: nf_tables:
        report use refcount overflow') which otherwise would require
        `nft_use_dec_restore()' to include.
     2. Introduced the new label names. This is a combined relabeling done
        by upstream commits 002f2176532093753cb6ced61e5ea7b8904c6cae
        (`netfilter: nf_tables: add userdata attributes to nft_chain') and
        this one, d472e9853. Commit 002f21765 wasn't backported separately
        because the relabeling was coupled with other, functional
        changes. The label names were chosen to correspond, on the
        upstream, to the ones marking the same deallocator
        (`nft_use_dec_restore()' is missing in LTS 8.6 so no `err_trans'
        label).
netfilter: nf_tables: rename function to destroy hook list

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit cdc32546632354305afdcf399a5431138a31c9e0
upstream-diff Ignored changes in functions `nft_commit_release',
  `nf_tables_abort_release', because the function being renamed is not
  used there (missing abadb2f865d72a223d691fc68e006943ecadf0d9 and
  78d9f48f7f44431a25da2b46b3a8812f6ff2b981).
netfilter: nf_tables: add nft_chain_add()

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit 04b7db414490ea9254d0c1d8930ea9571f8ce9f0
netfilter: nf_tables: add nft_flowtable_hooks_destroy()

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit 389a2cbcb7f15e2af9babdc0c63cec318537e7ed
netfilter: nf_tables: pass hook list to nft_{un,}register_flowtable_net_hooks()

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit f9382669cf5e75ebc7636bd78e637facf27d53f7
netfilter: nf_tables: generalise flowtable hook parsing

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d9246a53752fdb777ed176d5f091c4ac0e482bba

Explanation

CVE-2026-23392

Background

The bug is related to building a netfilter transaction, for example during the execution of a script like below

#!/usr/sbin/nft -f
flush ruleset
table inet filter {
    flowtable f {
        hook ingress priority filter;
        devices = { wan, lan0, lan1 };
    }
    chain input {
        type filter hook input priority 0;
        iif "lo" accept
        ct state invalid drop
        ct state established,related accept
        tcp dport 22 accept
        icmp type echo-request accept
        drop
    }
    chain forward {
        type filter hook forward priority 0;
        ct state established,related flow offload @f accept
        ct state invalid drop
        ip forward accept
        drop
    }
}

The modified function is what builds a flowtable, the object f in the example above. Regular firewall rules apply to each packet separately, despite most of them addressing a particular packet flow, defined as all the packets belonging to the same connection. Flowtables were introduced to leverage this and greatly optimize throughput by reducing the regular rules checking to only the handshake packets, the rest of connection bypassing the expensive checks, through a flowtable. Flowtables are inherently associated with network devices.

Tracking the UAF and problem explanation

The bugfixing commit mesasge mentions

Uncovered by KASAN reported as use-after-free from nfnetlink_hook path
when dumping hooks.

The exact KASAN log with the stack trace leading to UAF could not have been found. However, it most likely refers to the net/netfilter/nfnetlink_hook.c file, the .dump operation, as the entry point:

.dump = nfnl_hook_dump,

The UAF is strictly related to the flowtables, as the commit's subject states

netfilter: nf_tables: release flowtable after rcu grace period on error

(meaning what caused UAF was releasing flowtable before rcu grace period). The only place in net/netfilter/nfnetlink_hook.c dealing with flowtables is the nfnl_hook_put_nft_ft_info() function. This gives the most likely UAF stack trace:

  1. .dump = nfnl_hook_dump,
  2. nfnl_hook_dump() ::
    err = nfnl_hook_dump_one(nlskb, ctx, ops[i], family,
    cb->nlh->nlmsg_seq);
  3. nfnl_hook_dump_one() ::
    ret = nfnl_hook_put_nft_ft_info(nlskb, ctx, seq, ops->priv);
  4. nfnl_hook_put_nft_ft_info ::

Perhaps nfnl_hook_put_nft_ft_info is not even reached, with UAF occuring at the ops->priv dereference passed as nf_ft argument. This covers the UAF reached from the control plane, meaning from the administrator manipulating the settings, like calling nft -a list hooks which would reach the code above. Note that this functionality is not available in LTS 8.6 (started from the non-backported e2cf17d netfilter: add new hook nfnl subsystem), which means that the specific KASAN error path addressed by CVE-2026-23392 fix doesn't apply to this version.

However, the UAF is also possible from the data plane, meaning from the processing of packets. This is the packet path being mentioned in the bugfix message:

Call synchronize_rcu() after unregistering the hooks from error path,
since a hook that already refers to this flowtable can be already
registered, exposing this flowtable to packet path and nfnetlink_hook
control plane.

The entry point for both of these cases are the ingress hooks from the net_device struct. The complete chain of dereferences leading from the hooks to flowtables is rather obfuscated

  1. struct net_device

    struct nf_hook_entries __rcu *nf_hooks_ingress;

  2. struct nf_hook_entries

    * const struct nf_hook_ops *orig_ops[]

    It's a flexible array member, see how the field is obtained with nf_hook_entries_get_hook_ops():
    static inline struct nf_hook_ops **nf_hook_entries_get_hook_ops(const struct nf_hook_entries *e)
    {
    unsigned int n = e->num_hook_entries;
    const void *hook_end;
    hook_end = &e->hooks[n]; /* this is *past* ->hooks[]! */
    return (struct nf_hook_ops **)hook_end;
    }

  3. struct nf_hook_ops


    The void* member priv is an allocated nf_flowtable object, see
    hook->ops.priv = &flowtable->data;

    called from the fix-modified nf_tables_newflowtable(), at

    err = nf_tables_flowtable_parse_hook(&ctx, nla[NFTA_FLOWTABLE_HOOK],
    flowtable);

The core of the CVE-2026-23392 issue is that as long as nf_hooks_ingress pointer in net_device is alive the flowtable object accessible to it through the chain above must be alive as well. The deallocation of nf_hooks_ingress object is done during the rollback of registered hooks in case of nft_register_flowtable_net_hooks() error (nft_unregister_flowtable_hook(…)nf_unregister_net_hook(…)__nf_unregister_net_hook(…)nf_hook_entries_free(p)) and delegated the end of the RCU grace period

call_rcu(&head->head, __nf_hook_entries_free);

while the deallocation of a flowtable in the error path of nf_tables_newflowtable() function is simply done on demand, at the end of it

flowtable->data.type->free(&flowtable->data);

which may come earlier, chronologically.

The fix in commit d73f4b5 calls synchronize_rcu() before freeing the flowtable, which simply blocks the thread until the end of grace period. This is usually very costly time-wise, and that's why the author justifies the decision:

This error path is rare, it should only happen by reaching the maximum
number hooks or by failing to set up to hardware offload, just call
synchronize_rcu().

CVE-2026-23231

The CVE-2026-23231 problem is exactly the same as CVE-2026-23392, except with chains instead of flowtables. Unlike in CVE-2026-23392 case the commit message for the CVE-2026-23231 fix is well composed and clearly explains the problem and its solution, not requiring additional unpacking

nf_tables_addchain() publishes the chain to table->chains via
list_add_tail_rcu() (in nft_chain_add()) before registering hooks.
If nf_tables_register_hook() then fails, the error path calls
nft_chain_del() (list_del_rcu()) followed by nf_tables_chain_destroy()
with no RCU grace period in between.

This creates two use-after-free conditions:

  1. Control-plane: nf_tables_dump_chains() traverses table->chains
    under rcu_read_lock(). A concurrent dump can still be walking
    the chain when the error path frees it.

  2. Packet path: for NFPROTO_INET, nf_register_net_hook() briefly
    installs the IPv4 hook before IPv6 registration fails. Packets
    entering nft_do_chain() via the transient IPv4 hook can still be
    dereferencing chain->blob_gen_X when the error path frees the
    chain.

Add synchronize_rcu() between nft_chain_del() and the chain destroy
so that all RCU readers – both dump threads and in-flight packet
evaluation – have finished before the chain is freed.

A minor difference in the context of the LTS 8.6 backport is that the nf_tables_dump_chains() function exists in LTS 8.6, unlike nfnl_hook_dump()

.dump = nf_tables_dump_chains,

This means the bug is accessible from the control plane as well.

kABI check: passed

[0/1] kabi_check_kernel	Check ABI of kernel [ciqlts8_6-CVE-batch-46]	_kabi_check_kernel__x86_64--test--ciqlts8_6-CVE-batch-46
ninja explain: output state/kernels/ciqlts8_6-CVE-batch-46/x86_64/kabi_checked doesn't exist
ninja explain: state/kernels/ciqlts8_6-CVE-batch-46/x86_64/kabi_checked is dirty
+ dist_git_version=el-8.6
+ local_version=ciqlts8_6-CVE-batch-46
+ 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-46
+ 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-46/Module.symvers'\'''
kABI check passed
+ touch state/kernels/ciqlts8_6-CVE-batch-46/x86_64/kabi_checked

Boot test: passed

boot-test.log

Kselftests: passed relative

Reference

kselftests–ciqlts8_6–run1.log

Patch

kselftests–ciqlts8_6-CVE-batch-46–run1.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-CVE-batch-46--run1.log

Results for the netfilter subsystem specifically:

$ ktests.xsh diff --where "tests.TestCase LIKE 'netfilter%'" kselftests*.log

Column    File
--------  --------------------------------------------
Status0   kselftests--ciqlts8_6--run1.log
Status1   kselftests--ciqlts8_6-CVE-batch-46--run1.log

TestCase                              Status0  Status1  Summary
netfilter:conntrack_icmp_related.sh   fail     fail     same
netfilter:conntrack_tcp_unreplied.sh  fail     fail     same
netfilter:ipvs.sh                     skip     skip     same
netfilter:nft_flowtable.sh            fail     fail     same
netfilter:nft_meta.sh                 pass     pass     same
netfilter:nft_nat.sh                  skip     skip     same
netfilter:nft_queue.sh                skip     skip     same

full-test-results-comparison.log

More targeted tests: passed relative

In the general, single-boot test runs, some of the netfilter tests skip or fail as a result of selftests interference. Running them on a separate boot each gives better coverage.

Unfortunately the netfilter:nft_flowtable.sh test still fails, although more elaborately, and still in the same way as on the reference kernel.

Bundled test:

$ ./ktests.xsh show kselftests--ciqlts8_6-CVE-batch-46--run1.log --test netfilter:nft_flowtable.sh
# RTNETLINK answers: File exists
# RTNETLINK answers: File exists
# RTNETLINK answers: File exists
# Cannot find device "veth0"
# Cannot find device "veth0"
# Cannot find device "veth1"
# Cannot find device "veth1"
# Cannot find device "veth0"
# Cannot find device "veth0"
# Cannot find device "veth1"
# Cannot find device "veth1"
# Cannot find device "veth0"
not ok 1 selftests: netfilter: nft_flowtable.sh # exit=1

Separate test:

$ ./ktests.xsh show kselftests--nft_flowtable--ciqlts8_6-CVE-batch-46--run1.log --test netfilter:nft_flowtable.sh
# PASS: netns routing/connectivity: ns1 can reach ns2
# ./nft_flowtable.sh: line 252:  1670 Terminated              ip netns exec $nsb nc -w 5 -l -p 12345 < "$ns2in" > "$ns2out"
# ./nft_flowtable.sh: line 276:  1672 Terminated              ip netns exec $nsa nc -w 4 "$dstip" "$dstport" < "$ns1in" > "$ns1out"
# FAIL: file mismatch for ns1 -> ns2
# -rw-------. 1 root root 7709696 Sep 20 00:41 /tmp/tmp.4isOuqMkh0
# -rw-------. 1 root root 4630412 Sep 20 00:41 /tmp/tmp.fQIs6nAESP
# FAIL: flow offload for ns1/ns2:
# table inet filter {
# 	flowtable f1 {
# 		hook ingress priority filter
# 		devices = { veth0, veth1 }
# 	}
# 
# 	chain forward {
# 		type filter hook forward priority filter; policy drop;
# 		oif "veth1" tcp dport 12345 flow add @f1 counter packets 1 bytes 52
# 		tcp dport 12345 meta length > 200 ct mark set 0x00000001 counter packets 410 bytes 4740304
# 		tcp flags fin,rst ct mark set 0x00000000 accept
# 		meta length > 1500 accept comment "something-to-grep-for"
# 		tcp sport 12345 ct mark 0x00000001 counter packets 3 bytes 232 log prefix "mark failure " drop
# 		ct state established,related accept
# 		meta length < 200 oif "veth1" tcp dport 12345 counter packets 1 bytes 60 accept
# 		meta l4proto icmp accept
# 		meta l4proto ipv6-icmp accept
# 	}
# }
# ./nft_flowtable.sh: line 252:  1704 Terminated              ip netns exec $nsb nc -w 5 -l -p 12345 < "$ns2in" > "$ns2out"
# ./nft_flowtable.sh: line 276:  1706 Terminated              ip netns exec $nsa nc -w 4 "$dstip" "$dstport" < "$ns1in" > "$ns1out"
# FAIL: file mismatch for ns1 -> ns2
...

(see the log files for details)

netfilter:conntrack_icmp_related.sh

kselftests–conntrack_icmp_related–ciqlts8_6–run1.log
kselftests–conntrack_icmp_related–ciqlts8_6–run2.log
kselftests–conntrack_icmp_related–ciqlts8_6–run3.log
kselftests–conntrack_icmp_related–ciqlts8_6-CVE-batch-46–run1.log
kselftests–conntrack_icmp_related–ciqlts8_6-CVE-batch-46–run2.log
kselftests–conntrack_icmp_related–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--conntrack_icmp_related*.log
Column    File
--------  --------------------------------------------------------------------
Status0   kselftests--conntrack_icmp_related--ciqlts8_6--run1.log
Status1   kselftests--conntrack_icmp_related--ciqlts8_6--run2.log
Status2   kselftests--conntrack_icmp_related--ciqlts8_6--run3.log
Status3   kselftests--conntrack_icmp_related--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--conntrack_icmp_related--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--conntrack_icmp_related--ciqlts8_6-CVE-batch-46--run3.log

TestCase                             Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:conntrack_icmp_related.sh  pass     pass     pass     pass     pass     pass     same

netfilter:conntrack_tcp_unreplied.sh

kselftests–conntrack_tcp_unreplied–ciqlts8_6–run1.log
kselftests–conntrack_tcp_unreplied–ciqlts8_6–run2.log
kselftests–conntrack_tcp_unreplied–ciqlts8_6–run3.log
kselftests–conntrack_tcp_unreplied–ciqlts8_6-CVE-batch-46–run1.log
kselftests–conntrack_tcp_unreplied–ciqlts8_6-CVE-batch-46–run2.log
kselftests–conntrack_tcp_unreplied–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--conntrack_tcp_unreplied*.log
Column    File
--------  ---------------------------------------------------------------------
Status0   kselftests--conntrack_tcp_unreplied--ciqlts8_6--run1.log
Status1   kselftests--conntrack_tcp_unreplied--ciqlts8_6--run2.log
Status2   kselftests--conntrack_tcp_unreplied--ciqlts8_6--run3.log
Status3   kselftests--conntrack_tcp_unreplied--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--conntrack_tcp_unreplied--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--conntrack_tcp_unreplied--ciqlts8_6-CVE-batch-46--run3.log

TestCase                              Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:conntrack_tcp_unreplied.sh  fail     fail     fail     fail     fail     fail     same

netfilter:ipvs.sh

kselftests–ipvs–ciqlts8_6–run1.log
kselftests–ipvs–ciqlts8_6–run2.log
kselftests–ipvs–ciqlts8_6–run3.log
kselftests–ipvs–ciqlts8_6-CVE-batch-46–run1.log
kselftests–ipvs–ciqlts8_6-CVE-batch-46–run2.log
kselftests–ipvs–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--ipvs*.log
Column    File
--------  --------------------------------------------------
Status0   kselftests--ipvs--ciqlts8_6--run1.log
Status1   kselftests--ipvs--ciqlts8_6--run2.log
Status2   kselftests--ipvs--ciqlts8_6--run3.log
Status3   kselftests--ipvs--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--ipvs--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--ipvs--ciqlts8_6-CVE-batch-46--run3.log

TestCase           Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:ipvs.sh  skip     skip     skip     skip     skip     skip     same

netfilter:nft_flowtable.sh

kselftests–nft_flowtable–ciqlts8_6–run1.log
kselftests–nft_flowtable–ciqlts8_6–run2.log
kselftests–nft_flowtable–ciqlts8_6–run3.log
kselftests–nft_flowtable–ciqlts8_6-CVE-batch-46–run1.log
kselftests–nft_flowtable–ciqlts8_6-CVE-batch-46–run2.log
kselftests–nft_flowtable–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--nft_flowtable*.log
Column    File
--------  -----------------------------------------------------------
Status0   kselftests--nft_flowtable--ciqlts8_6--run1.log
Status1   kselftests--nft_flowtable--ciqlts8_6--run2.log
Status2   kselftests--nft_flowtable--ciqlts8_6--run3.log
Status3   kselftests--nft_flowtable--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--nft_flowtable--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--nft_flowtable--ciqlts8_6-CVE-batch-46--run3.log

TestCase                    Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:nft_flowtable.sh  fail     fail     fail     fail     fail     fail     same

netfilter:nft_meta.sh

kselftests–nft_meta–ciqlts8_6–run1.log
kselftests–nft_meta–ciqlts8_6–run2.log
kselftests–nft_meta–ciqlts8_6–run3.log
kselftests–nft_meta–ciqlts8_6-CVE-batch-46–run1.log
kselftests–nft_meta–ciqlts8_6-CVE-batch-46–run2.log
kselftests–nft_meta–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--nft_meta*.log
Column    File
--------  ------------------------------------------------------
Status0   kselftests--nft_meta--ciqlts8_6--run1.log
Status1   kselftests--nft_meta--ciqlts8_6--run2.log
Status2   kselftests--nft_meta--ciqlts8_6--run3.log
Status3   kselftests--nft_meta--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--nft_meta--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--nft_meta--ciqlts8_6-CVE-batch-46--run3.log

TestCase               Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:nft_meta.sh  pass     pass     pass     pass     pass     pass     same

netfilter:nft_nat.sh

kselftests–nft_nat–ciqlts8_6–run1.log
kselftests–nft_nat–ciqlts8_6–run2.log
kselftests–nft_nat–ciqlts8_6–run3.log
kselftests–nft_nat–ciqlts8_6-CVE-batch-46–run1.log
kselftests–nft_nat–ciqlts8_6-CVE-batch-46–run2.log
kselftests–nft_nat–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--nft_nat*.log
Column    File
--------  -----------------------------------------------------
Status0   kselftests--nft_nat--ciqlts8_6--run1.log
Status1   kselftests--nft_nat--ciqlts8_6--run2.log
Status2   kselftests--nft_nat--ciqlts8_6--run3.log
Status3   kselftests--nft_nat--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--nft_nat--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--nft_nat--ciqlts8_6-CVE-batch-46--run3.log

TestCase              Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:nft_nat.sh  pass     pass     pass     pass     pass     pass     same

netfilter:nft_queue.sh

kselftests–nft_queue–ciqlts8_6–run1.log
kselftests–nft_queue–ciqlts8_6–run2.log
kselftests–nft_queue–ciqlts8_6–run3.log
kselftests–nft_queue–ciqlts8_6-CVE-batch-46–run1.log
kselftests–nft_queue–ciqlts8_6-CVE-batch-46–run2.log
kselftests–nft_queue–ciqlts8_6-CVE-batch-46–run3.log

$ ./ktests.xsh diff kselftests--nft_queue*.log
Column    File
--------  -------------------------------------------------------
Status0   kselftests--nft_queue--ciqlts8_6--run1.log
Status1   kselftests--nft_queue--ciqlts8_6--run2.log
Status2   kselftests--nft_queue--ciqlts8_6--run3.log
Status3   kselftests--nft_queue--ciqlts8_6-CVE-batch-46--run1.log
Status4   kselftests--nft_queue--ciqlts8_6-CVE-batch-46--run2.log
Status5   kselftests--nft_queue--ciqlts8_6-CVE-batch-46--run3.log

TestCase                Status0  Status1  Status2  Status3  Status4  Status5  Summary
netfilter:nft_queue.sh  pass     pass     pass     pass     pass     pass     same

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d9246a5

Update nft_flowtable_parse_hook() to take the flowtable hook list as
parameter. This allows to reuse this function to update the hooks.

	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 48af0290d8c2269ad2b4484236ca7cb5d9d93ce5)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
…et_hooks()

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit f938266

This patch prepares for incremental flowtable hook updates.

	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 0ee7a3ed38aedbf30b214b67e08bc198d68b32fc)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit 389a2cb

This patch adds a helper function destroy the flowtable hooks.

	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 5af8e7f44f027717c8ce86b63b056332b2155812)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit 04b7db4

This patch adds a helper function to add the chain to the hashtable and
the chain list.

	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit c62ae2d56890e450e4c784ebea4d73e7970c4381)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit cdc3254
upstream-diff Ignored changes in functions `nft_commit_release',
  `nf_tables_abort_release', because the function being renamed is not
  used there (missing abadb2f and
  78d9f48).

Rename nft_flowtable_hooks_destroy() by nft_hooks_destroy() to prepare
for netdev chain device updates.

	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 465373ff4e04b41b193adaecb1a79bc35965e890)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
…able

jira VULN-177693
cve-pre CVE-2026-23231
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d472e98
upstream-diff |
  1. Needed to add forward declaration of `nft_chain_del()' so that it
     could have been used in the earlier-defined
     `nf_tables_addchain()'. In the upstream the ordering of definitions
     doesn't play a role because `nft_chain_del()' was made public in
     d0e2c7d (`netfilter: nf_tables: add
     NFT_CHAIN_BINDING') and declared in the nf_tables.h header.
  2. In `nf_tables_addchain()' preserved the intent of the change (moving
     `nf_tables_register_hook()' call to the end of the function), while
     taking into account the deviations from the upstream.
     1. Re-arranged the cleanup ladder and goto targets to ensure every
        allocator is paired with the corresponding deallocator.
         Alloc                      Dealloc
        --------------------------------------------------------
         ctx->chain contruction     nf_tables_chain_destroy()
         nf_tables_register_hook()  nf_tables_unregister_hook()
         nft_chain_add()            nft_chain_del()
         nft_trans_chain_add()      nft_trans_destroy()
        Took into account the missing
        1689f25 (`netfilter: nf_tables:
        report use refcount overflow') which otherwise would require
        `nft_use_dec_restore()' to include.
     2. Introduced the new label names. This is a combined relabeling done
        by upstream commits 002f217
        (`netfilter: nf_tables: add userdata attributes to nft_chain') and
        this one, d472e98. Commit 002f217 wasn't backported separately
        because the relabeling was coupled with other, functional
        changes. The label names were chosen to correspond, on the
        upstream, to the ones marking the same deallocator
        (`nft_use_dec_restore()' is missing in LTS 8.6 so no `err_trans'
        label).

Register hooks last when adding chain/flowtable to ensure that packets do
not walk over datastructure that is being released in the error path
without waiting for the rcu grace period.

Fixes: 91c7b38 ("netfilter: nf_tables: use new transaction infrastructure to handle chain")
Fixes: 3b49e2e ("netfilter: nf_tables: add flow table netlink frontend")
	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
(cherry picked from commit 9c03d4d752584aeec7a87965780e409ce2c93908)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-177693
cve CVE-2026-23231
commit-author Inseo An <y0un9sa@gmail.com>
commit 71e99ee

nf_tables_addchain() publishes the chain to table->chains via
list_add_tail_rcu() (in nft_chain_add()) before registering hooks.
If nf_tables_register_hook() then fails, the error path calls
nft_chain_del() (list_del_rcu()) followed by nf_tables_chain_destroy()
with no RCU grace period in between.

This creates two use-after-free conditions:

 1) Control-plane: nf_tables_dump_chains() traverses table->chains
    under rcu_read_lock(). A concurrent dump can still be walking
    the chain when the error path frees it.

 2) Packet path: for NFPROTO_INET, nf_register_net_hook() briefly
    installs the IPv4 hook before IPv6 registration fails.  Packets
    entering nft_do_chain() via the transient IPv4 hook can still be
    dereferencing chain->blob_gen_X when the error path frees the
    chain.

Add synchronize_rcu() between nft_chain_del() and the chain destroy
so that all RCU readers -- both dump threads and in-flight packet
evaluation -- have finished before the chain is freed.

Fixes: 91c7b38 ("netfilter: nf_tables: use new transaction infrastructure to handle chain")
	Signed-off-by: Inseo An <y0un9sa@gmail.com>
	Signed-off-by: Florian Westphal <fw@strlen.de>
(cherry picked from commit 025f243c144afd6177d8babd7c3f1f7e95775863)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-179371
cve CVE-2026-23392
commit-author Pablo Neira Ayuso <pablo@netfilter.org>
commit d73f4b5

Call synchronize_rcu() after unregistering the hooks from error path,
since a hook that already refers to this flowtable can be already
registered, exposing this flowtable to packet path and nfnetlink_hook
control plane.

This error path is rare, it should only happen by reaching the maximum
number hooks or by failing to set up to hardware offload, just call
synchronize_rcu().

There is a check for already used device hooks by different flowtable
that could result in EEXIST at this late stage. The hook parser can be
updated to perform this check earlier to this error path really becomes
rarely exercised.

Uncovered by KASAN reported as use-after-free from nfnetlink_hook path
when dumping hooks.

Fixes: 3b49e2e ("netfilter: nf_tables: add flow table netlink frontend")
	Reported-by: Yiming Qian <yimingqian591@gmail.com>
	Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
	Signed-off-by: Florian Westphal <fw@strlen.de>
(cherry picked from commit 54a8908f0d227c81a0c8b36d0a522911918a9677)
	Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
@pvts-mat
pvts-mat force-pushed the CVE-batch-46_ciqlts8_6 branch from 4bd41c0 to d2e8aef Compare September 22, 2026 17:07
@PlaidCat
PlaidCat requested a review from a team September 22, 2026 18:23
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