Skip to content

[LTS 8.6 RT] CVE-2023-4206, CVE-2023-4207, CVE-2023-4208 #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

pvts-mat
Copy link
Contributor

@pvts-mat pvts-mat commented Mar 8, 2025

[LTS 8.6 RT]
CVE-2023-4206 VULN-6645
CVE-2023-4207 VULN-6652
CVE-2023-4208 VULN-6659

Problem

The PR addresses a series of related CVEs, which were once listed under a single CVE-2023-4128. From https://lore.kernel.org/netdev/[email protected]/:

Three classifiers (cls_fw, cls_u32 and cls_route) always copy
tcf_result struct into the new instance of the filter on update.

This causes a problem when updating a filter bound to a class,
as tcf_unbind_filter() is always called on the old instance in the
success path, decreasing filter_cnt of the still referenced class
and allowing it to be deleted, leading to a use-after-free.

This patch set fixes this issue in all affected classifiers by no longer
copying the tcf_result struct from the old filter.

Each CVE is related to a different classifier:

CVE File Option
CVE-2023-4206 net/sched/cls_route.c CONFIG_NET_CLS_ROUTE4
CVE-2023-4207 net/sched/cls_fw.c CONFIG_NET_CLS_FW
CVE-2023-4208 net/sched/cls_u32.c CONFIG_NET_CLS_U32

Analysis and solution

Official fixes

The official fixes for each of the vulnerabilities are as follows:

CVE Mainline fix Backport to 4.19 (closest to 4.18 of Rocky LTS 8.6 RT) Relation to mainline fix Applicable to LTS 8.6 RT
CVE-2023-4206 b80b829 ad8f36f96696a7f1d191da66637c415959bab6d8 Same Yes
CVE-2023-4207 76e42ae 4f38dc8496d1991e2c055a0068dd98fb48affcc6 Same Yes
CVE-2023-4208 3044b16 4aae24015ecd70d824a953e2dc5b0ca2c4769243 Same Yes

Applicability

Each change is applicable to the LTS 8.6 RT from the configuration standpoint.

grep -e '\(CONFIG_NET_SCHED\|CONFIG_NET_CLS\|CONFIG_NET_CLS_ROUTE4\|CONFIG_NET_CLS_FW\|CONFIG_NET_CLS_U32\)\b' configs/kernel-rt-4.18.0-x86_64.config

CONFIG_NET_SCHED=y
CONFIG_NET_CLS=y
CONFIG_NET_CLS_ROUTE4=m
CONFIG_NET_CLS_FW=m
CONFIG_NET_CLS_U32=m

Analysis

The official fix is very straightforward yet it begs the question: isn't the tcf_result -type struct needed in the new instance of the appropriate filter object? Doesn't the erasure of res field break some expectation some code using the change function may have, if the changing of res wasn't requested?

While the attempts to answer this question based on the encoded logic alone were inconclusive (no counter examples found but also too little expertise in this codebase to give definite affirmative answer), some support for this solution can be provided by comparing the modified classifiers to others.

The kernel provides 11 classifiers in total, based on modules implementing tcf_proto_ops:

Kind File .change function Filter struct Contains tcf_result? Patch?
basic cls_basic.c basic_change basic_filter Yes  
bpf cls_bpf.c cls_bpf_change cls_bpf_prog Yes  
cgroup cls_cgroup.c cls_cgroup_change cls_cgroup_head No  
flow cls_flow.c flow_change flow_filter No  
flower cls_flower.c fl_change cls_fl_filter Yes  
fw cls_fw.c fw_change fw_filter Yes x
matchall cls_matchall.c mall_change cls_mall_head Yes  
route cls_route.c route4_change route4_filter Yes x
rsvp cls_rsvp.h rsvp_change rsvp_filter Yes  
tcindex cls_tcindex.c tcindex_change tcindex_filter_result Yes  
u32 cls_u32.c u32_change tc_u_knode Yes x

Each classifier has an associated filter struct. Two of these filter structs (cgroup, flow) don't contain tcf_result -type field and thus cannot be compared against. Three classifiers are objects of the evaluation - fw, route, u32. This leaves 6 classifiers to check for how they operate on the tcf_result fields in their respective change function: basic, bpf, flower, matchall, rsvp, tcindex.

Analysis of the filter functions of these classifiers shows that no tcf_result is copied on the creation of new filter struct instance. The single exception is tcindex: copying of tcf_result struct to the newly created filter struct does seem to be taking place in a rather convoluted way. (The **arg in tcindex_change is passed as r to tcindex_set_parms. There the copy of r->res is assigned to the local variable cr in case r wasn't null. Then it's assigned back to r->res, except this time r may not be the same as provided in the argument, but also &new_filter_result. In that case a new filter struct f is created and receives the copy of tcf_result.) The error condition from CVEs doesn't seem to occur in this case though, as there is no tcf_unbind_filter call messing with the reference counters. This supports the thesis that copying tcf_result into the newly created filter instance is preferable but perhaps not required and can be foregone when it's causing problems.

Unrelated to the tcf_result issue, it may be worth considering the retirement of the tcindex filter in LTS 8.6 RT, as it was done in the mainline kernel for security reasons on 2023-02-16:

commit 8c710f75256bb3cf05ac7b1672c82b92c43f3d28
Author:     Jamal Hadi Salim <[email protected]>
AuthorDate: Tue Feb 14 08:49:14 2023 -0500
Commit:     Paolo Abeni <[email protected]>
CommitDate: Thu Feb 16 09:27:07 2023 +0100

    net/sched: Retire tcindex classifier
    
    The tcindex classifier has served us well for about a quarter of a century
    but has not been getting much TLC due to lack of known users. Most recently
    it has become easy prey to syzkaller. For this reason, we are retiring it.
    
    Signed-off-by: Jamal Hadi Salim <[email protected]>
    Acked-by: Jiri Pirko <[email protected]>
    Signed-off-by: Paolo Abeni <[email protected]>

(Syzkaller = Google's fuzzing framework)

Retiring tcindex from mainline kernel is unfortunate, because it leaves LTS 8.6 RT not only with rich source of vulnerabilities, as the commit's message suggests, but a silent source, without any CVEs nor patches made for them by kernel.org in the future.

kABI check: omitted (unstable ABI of RT kernels)

Boot test: passed

boot-test.log

Kselftests (general): passed relative

Methodology

Source-compiled (e1a9851e6068a6ef800ec6b2b48a7c243882ed06) kselftests suite was used.

Coverage (including tests skipped during execution)

android, breakpoints, capabilities, core, cpu-hotplug, cpufreq, efivarfs, exec, filesystems, firmware, fpu, ftrace, futex, gpio, intel_pstate, ipc, kcmp, kvm, lib, livepatch, membarrier, memfd, memory-hotplug, mount, net, net/forwarding, net/mptcp, netfilter, nsfs, proc, pstore, ptrace, rseq, rtc, sgx, sigaltstack, size, splice, static_keys, sync, sysctl, tc-testing, timens, timers, tpm2, user, vm, x86, zram

Reference

Three test runs were conducted on the reference kernel.
kselftests–src–ciqlts8_6-rt–run1.log
kselftests–src–ciqlts8_6-rt–run2.log
kselftests–src–ciqlts8_6-rt–run3.log

Patch

Two test runs were conducted on the patched kernel.
kselftests–src–ciqlts8_6-rt-CVE-2023-4206.4207.4208–run1.log
kselftests–src–ciqlts8_6-rt-CVE-2023-4206.4207.4208–run2.log

Comparison

ktests.xsh table --where "Summary = 'diff'" kselftests*.log

Column    File
--------  ---------------------------------------------------------------
Status0   kselftests--src--ciqlts8_6-rt--run1.log
Status1   kselftests--src--ciqlts8_6-rt--run2.log
Status2   kselftests--src--ciqlts8_6-rt--run3.log
Status3   kselftests--src--ciqlts8_6-rt-CVE-2023-4206.4207.4208--run1.log
Status4   kselftests--src--ciqlts8_6-rt-CVE-2023-4206.4207.4208--run2.log

TestCase                   Status0  Status1  Status2  Status3  Status4  Summary
kvm:hardware_disable_test  pass     pass     pass     pass     fail     diff
net/mptcp:mptcp_join.sh    fail     fail     fail     fail     pass     diff
net:gro.sh                 pass     fail     pass     pass     pass     diff

All the differing results are for tests known before to give inconsistent results.

Kselftests (networking): passed relative

Methodology

In general kselftests all the net/forwarding tests fail (really should be skipped) because of the missing tool dependencies

# selftests: net/forwarding: bridge_igmp.sh
# SKIP: jq not installed
not ok 1 selftests: net/forwarding: bridge_igmp.sh # exit=1

Because the patch deals with networking specifically, an additional batch of tests was carried out after solving the test requirements issues.

sudo make ARCH=$(uname -m) -C tools/testing/selftests TARGETS="net/forwarding" run_tests

The tools/testing/selftests/net/forwarding/forwarding.config file used was created directly from the tools/testing/selftests/net/forwarding/forwarding.config.sample.

Reference

Three test runs were conducted on the reference kernel.
kselftests-net-forwarding–src–ciqlts8_6-rt–run1.log
kselftests-net-forwarding–src–ciqlts8_6-rt–run2.log
kselftests-net-forwarding–src–ciqlts8_6-rt–run3.log

Patch

A single test run was conducted on the patched kernel.
kselftests-net-forwarding–src–ciqlts8_6-rt-CVE-2023-4206.4207.4208–run1.log

Comparison and discussion

Results for the reference and patched kernel are the same.

ktests.xsh table kselftests-net-forwarding*.log

Column    File
--------  ------------------------------------------------------------------------------
Status0   kselftests-net-forwarding--src--ciqlts8_6-rt--run1.log
Status1   kselftests-net-forwarding--src--ciqlts8_6-rt--run2.log
Status2   kselftests-net-forwarding--src--ciqlts8_6-rt--run3.log
Status3   kselftests-net-forwarding--src--ciqlts8_6-rt-CVE-2023-4206.4207.4208--run1.log

TestCase                                     Status0  Status1  Status2  Status3  Summary
net/forwarding:bridge_igmp.sh                fail     fail     fail     fail     same
net/forwarding:bridge_port_isolation.sh      pass     pass     pass     pass     same
net/forwarding:bridge_sticky_fdb.sh          pass     pass     pass     pass     same
net/forwarding:bridge_vlan_aware.sh          fail     fail     fail     fail     same
net/forwarding:bridge_vlan_unaware.sh        pass     pass     pass     pass     same
net/forwarding:ethtool.sh                    fail     fail     fail     fail     same
net/forwarding:gre_multipath.sh              fail     fail     fail     fail     same
net/forwarding:ip6_forward_instats_vrf.sh    fail     fail     fail     fail     same
net/forwarding:ipip_flat_gre.sh              pass     pass     pass     pass     same
net/forwarding:ipip_flat_gre_key.sh          pass     pass     pass     pass     same
net/forwarding:ipip_flat_gre_keys.sh         pass     pass     pass     pass     same
net/forwarding:ipip_hier_gre.sh              pass     pass     pass     pass     same
net/forwarding:ipip_hier_gre_key.sh          pass     pass     pass     pass     same
net/forwarding:ipip_hier_gre_keys.sh         pass     pass     pass     pass     same
net/forwarding:loopback.sh                   skip     skip     skip     skip     same
net/forwarding:mirror_gre.sh                 fail     fail     fail     fail     same
net/forwarding:mirror_gre_bound.sh           pass     pass     pass     pass     same
net/forwarding:mirror_gre_bridge_1d.sh       pass     pass     pass     pass     same
net/forwarding:mirror_gre_bridge_1d_vlan.sh  fail     fail     fail     fail     same
net/forwarding:mirror_gre_bridge_1q.sh       fail     fail     fail     fail     same
net/forwarding:mirror_gre_bridge_1q_lag.sh   fail     fail     fail     fail     same
net/forwarding:mirror_gre_changes.sh         fail     fail     fail     fail     same
net/forwarding:mirror_gre_flower.sh          fail     fail     fail     fail     same
net/forwarding:mirror_gre_lag_lacp.sh        pass     pass     pass     pass     same
net/forwarding:mirror_gre_neigh.sh           pass     pass     pass     pass     same
net/forwarding:mirror_gre_nh.sh              pass     pass     pass     pass     same
net/forwarding:mirror_gre_vlan.sh            pass     pass     pass     pass     same
net/forwarding:mirror_gre_vlan_bridge_1q.sh  fail     fail     fail     fail     same
net/forwarding:mirror_vlan.sh                pass     pass     pass     pass     same
net/forwarding:router.sh                     fail     fail     fail     fail     same
net/forwarding:router_bridge.sh              pass     pass     pass     pass     same
net/forwarding:router_bridge_vlan.sh         pass     pass     pass     pass     same
net/forwarding:router_broadcast.sh           fail     fail     fail     fail     same
net/forwarding:router_multicast.sh           fail     fail     fail     fail     same
net/forwarding:router_multipath.sh           fail     fail     fail     fail     same
net/forwarding:router_vid_1.sh               pass     pass     pass     pass     same

The list of net/forwarding tests performed is not exhaustive (36 / 53). The net/forwarding:sch_ets.sh test executed right after net/forwarding:router_vid_1.sh causes the machine to hang for more than 10 minutes and the used testing framework interrupts the test suite.

...
ok 36 selftests: net/forwarding: router_vid_1.sh
# selftests: net/forwarding: sch_ets.sh
# TEST: ping vlan 10                                                  [ OK ]
# TEST: ping vlan 11                                                  [ OK ]
# TEST: ping vlan 12                                                  [ OK ]
# Running in priomap mode
# Testing ets bands 3 strict 3, streams 0 1
# TEST: band 0                                                        [ OK ]
# INFO: Expected ratio >95% Measured ratio 100.00
# TEST: band 1                                                        [ OK ]
# INFO: Expected ratio <5% Measured ratio 0
# Testing ets bands 3 strict 3, streams 1 2
ERROR:root:Subprocess exceeded the maximum freeze time 600 s. Terminating
INFO:root:Finished tests. Cleaning up the machine
...

The fix for the problem was deferred to another CVE for the sake of patching efficiency.

pvts-mat added 3 commits March 5, 2025 15:35
…e-after-free

jira VULN-6645
cve CVE-2023-4206
commit-author valis <[email protected]>
commit b80b829

When route4_change() is called on an existing filter, the whole
tcf_result struct is always copied into the new instance of the filter.

This causes a problem when updating a filter bound to a class,
as tcf_unbind_filter() is always called on the old instance in the
success path, decreasing filter_cnt of the still referenced class
and allowing it to be deleted, leading to a use-after-free.

Fix this by no longer copying the tcf_result struct from the old filter.

Fixes: 1109c00 ("net: sched: RCU cls_route")
	Reported-by: valis <[email protected]>
	Reported-by: Bing-Jhong Billy Jheng <[email protected]>
	Signed-off-by: valis <[email protected]>
	Signed-off-by: Jamal Hadi Salim <[email protected]>
	Reviewed-by: Victor Nogueira <[email protected]>
	Reviewed-by: Pedro Tammela <[email protected]>
	Reviewed-by: M A Ramdhan <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
	Signed-off-by: Jakub Kicinski <[email protected]>
(cherry picked from commit b80b829)
	Signed-off-by: Marcin Wcisło <[email protected]>
…fter-free

jira VULN-6652
cve CVE-2023-4207
commit-author valis <[email protected]>
commit 76e42ae

When fw_change() is called on an existing filter, the whole
tcf_result struct is always copied into the new instance of the filter.

This causes a problem when updating a filter bound to a class,
as tcf_unbind_filter() is always called on the old instance in the
success path, decreasing filter_cnt of the still referenced class
and allowing it to be deleted, leading to a use-after-free.

Fix this by no longer copying the tcf_result struct from the old filter.

Fixes: e35a8ee ("net: sched: fw use RCU")
	Reported-by: valis <[email protected]>
	Reported-by: Bing-Jhong Billy Jheng <[email protected]>
	Signed-off-by: valis <[email protected]>
	Signed-off-by: Jamal Hadi Salim <[email protected]>
	Reviewed-by: Victor Nogueira <[email protected]>
	Reviewed-by: Pedro Tammela <[email protected]>
	Reviewed-by: M A Ramdhan <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
	Signed-off-by: Jakub Kicinski <[email protected]>
(cherry picked from commit 76e42ae)
	Signed-off-by: Marcin Wcisło <[email protected]>
…after-free

jira VULN-6659
cve CVE-2023-4208
commit-author valis <[email protected]>
commit 3044b16

When u32_change() is called on an existing filter, the whole
tcf_result struct is always copied into the new instance of the filter.

This causes a problem when updating a filter bound to a class,
as tcf_unbind_filter() is always called on the old instance in the
success path, decreasing filter_cnt of the still referenced class
and allowing it to be deleted, leading to a use-after-free.

Fix this by no longer copying the tcf_result struct from the old filter.

Fixes: de5df63 ("net: sched: cls_u32 changes to knode must appear atomic to readers")
	Reported-by: valis <[email protected]>
	Reported-by: M A Ramdhan <[email protected]>
	Signed-off-by: valis <[email protected]>
	Signed-off-by: Jamal Hadi Salim <[email protected]>
	Reviewed-by: Victor Nogueira <[email protected]>
	Reviewed-by: Pedro Tammela <[email protected]>
	Reviewed-by: M A Ramdhan <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
	Signed-off-by: Jakub Kicinski <[email protected]>
(cherry picked from commit 3044b16)
	Signed-off-by: Marcin Wcisło <[email protected]>
Copy link
Collaborator

@bmastbergen bmastbergen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥌

Copy link
Collaborator

@PlaidCat PlaidCat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@PlaidCat PlaidCat merged commit 423a0c6 into ctrliq:ciqlts8_6-rt Mar 13, 2025
1 check passed
@pvts-mat pvts-mat mentioned this pull request Jul 9, 2025
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.

3 participants