Skip to content

Conversation

pvts-mat
Copy link
Contributor

[LTS 8.6]
CVE-2025-37890
VULN-68290

Problem

https://access.redhat.com/security/cve/CVE-2025-37890

A use-after-free vulnerability has been identified in the Linux kernel's HFSC (Hierarchical Fair Service Curve) queuing discipline when it is configured with NETEM (Network Emulation) as a child. This flaw can lead to a kernel panic or crash due to incorrect assumptions about the queue state. Exploitation of this vulnerability requires local access with CAP_NET_ADMIN privileges and control over the qdisc (queueing discipline) setup. A local attacker could leverage this flaw to achieve denial of service or escalate privileges. Given that it affects kernel memory structures, successful exploitation could result in memory corruption, data leaks, or arbitrary write capabilities, leading to a full kernel crash.

Applicability: yes

The patch relates to the sch_hfsc module, enabled with the NET_SCH_HFSC option. It's set to m in all configs of LTS 8.6:

$ grep 'NET_SCH_HFSC\b' configs/*.config

configs/kernel-aarch64-debug.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-aarch64.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-ppc64le-debug.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-ppc64le.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-s390x-debug.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-s390x-zfcpdump.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-s390x.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-x86_64-debug.config:CONFIG_NET_SCH_HFSC=m
configs/kernel-x86_64.config:CONFIG_NET_SCH_HFSC=m

The commit 37d9cf1 marked as introducing the bug was backported to LTS 8.6 in f3e1778. The mainline fix 141d343 wasn't backported. For the full picture please refer to the Appendix: Bug timeline

Solution

The 141d343 fix is straightforward and applies without any changes. There is, however, an additional commit ac9fe7d which should be taken into consideration. It was motivated by the same authors who reported the CVE-2025-37890 bug, pointing out the insufficiency of the 141d343 patch this time:

net_sched: hfsc: Address reentrant enqueue adding class to eltree twice

Savino says:
    "We are writing to report that this recent patch
    (141d34391abbb315d68556b7c67ad97885407547) [1]
    can be bypassed, and a UAF can still occur when HFSC is utilized with
    NETEM.

    The patch only checks the cl->cl_nactive field to determine whether
    it is the first insertion or not [2], but this field is only
    incremented by init_vf [3].

    By using HFSC_RSC (which uses init_ed) [4], it is possible to bypass the
    check and insert the class twice in the eltree.
    Under normal conditions, this would lead to an infinite loop in
    hfsc_dequeue for the reasons we already explained in this report [5].

    However, if TBF is added as root qdisc and it is configured with a
    very low rate,
    it can be utilized to prevent packets from being dequeued.
    This behavior can be exploited to perform subsequent insertions in the
    HFSC eltree and cause a UAF."

To fix both the UAF and the infinite loop, with netem as an hfsc child,
check explicitly in hfsc_enqueue whether the class is already in the eltree
whenever the HFSC_RSC flag is set.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=141d34391abbb315d68556b7c67ad97885407547
[2] https://elixir.bootlin.com/linux/v6.15-rc5/source/net/sched/sch_hfsc.c#L1572
[3] https://elixir.bootlin.com/linux/v6.15-rc5/source/net/sched/sch_hfsc.c#L677
[4] https://elixir.bootlin.com/linux/v6.15-rc5/source/net/sched/sch_hfsc.c#L1574
[5] https://lore.kernel.org/netdev/8DuRWwfqjoRDLDmBMlIfbrsZg9Gx50DHJc1ilxsEBNe2D6NMoigR_eIRIG0LOjMc3r10nUUZtArXx4oZBIdUfZQrwjcQhdinnMis_0G7VEk=@willsroot.io/T/#u

This commit was backported as well as part of the CVE-2025-37890 patch.

kABI check: passed

DESCR_TARGET=1 DEBUG=1 RELAXED_DEPS=1 CVE=CVE-2025-37890 ./ninja.sh -d explain _kabi_checked__x86_64--test--ciqlts8_6-CVE-2025-37890

[0/1] 	Check ABI of kernel [ciqlts8_6-CVE-2025-37890]	_kabi_checked__x86_64--test--ciqlts8_6-CVE-2025-37890
++ uname -m
+ python3 /data/src/ctrliq-github/kernel-dist-git-el-8.6/SOURCES/check-kabi -k /data/src/ctrliq-github/kernel-dist-git-el-8.6/SOURCES/Module.kabi_x86_64 -s vms/x86_64--build--ciqlts8_6/build_files/kernel-src-tree-ciqlts8_6-CVE-2025-37890/Module.symvers
kABI check passed
+ touch state/kernels/ciqlts8_6-CVE-2025-37890/x86_64/kabi_checked

Boot test: passed

boot-test.log

Kselftests: passed relative

Coverage

Only the net-related tests were run. Arguably the most important are the tests in net/forwarding collection.

net/forwarding (except sch_tbf_ets.sh, sch_tbf_root.sh, sch_tbf_prio.sh, sch_ets.sh, tc_actions.sh), net/mptcp (except simult_flows.sh, mptcp_join.sh), net, netfilter (except nft_trans_stress.sh)

Reference

The first and second batch are disjoint.

First batch:
kselftests–ciqlts8_6–run1.log
kselftests–ciqlts8_6–run2.log
Second batch:
kselftests–ciqlts8_6–run3.log

Patch

First batch:
kselftests–ciqlts8_6-CVE-2025-37890–run1.log
kselftests–ciqlts8_6-CVE-2025-37890–run2.log
kselftests–ciqlts8_6-CVE-2025-37890–run3.log
Second batch:
kselftests–ciqlts8_6-CVE-2025-37890–run4.log

Comparison

There are some differences in the results but they're all for the tests marked before as unstable (see https://gitlab.conclusive.pl/devices/rocky-patching/-/blob/master/rocky.yml?ref_type=heads). They were just run by a mistake.

$ ktests.xsh diff -d kselftests*.log

Column    File
--------  ----------------------------------------------
Status0   kselftests--ciqlts8_6--run1.log
Status1   kselftests--ciqlts8_6--run2.log
Status2   kselftests--ciqlts8_6--run3.log
Status3   kselftests--ciqlts8_6-CVE-2025-37890--run1.log
Status4   kselftests--ciqlts8_6-CVE-2025-37890--run2.log
Status5   kselftests--ciqlts8_6-CVE-2025-37890--run3.log
Status6   kselftests--ciqlts8_6-CVE-2025-37890--run4.log

TestCase             Status0  Status1  Status2  Status3  Status4  Status5  Status6  Summary
net:ip_defrag.sh     pass     pass              pass     fail     pass              diff
net:udpgso_bench.sh  skip     skip              skip     skip     fail              diff
net:xfrm_policy.sh   pass     pass              fail     fail     pass              diff

Specific tests: skipped

Appendix: Bug timeline

The following table summarizes the timeline of the net/sched/sch_hfsc.c file on 6 branches:

  • Rocky: ciqlts9_4, ciqlts9_2, ciqlts8_6,
  • upstream: kernel-mainline, linux-5.15.y, linux-4.19.y.
0 Commit introducing the bug CVE-2025-37890
1 Official fix of CVE-2025-37890
2 The fix of the fix of CVE-2025-37890
   kernel-mainline                                                                                     linux-5.15.y            ciqlts9_4               ciqlts9_2               linux-4.19.y            ciqlts8_6
   --------------------------------------------------------------------------------------------------  ----------------------  ----------------------  ----------------------  ----------------------  ----------------------
   dd831ac82 2025-07-10 net/sched: sch_qfq: Fix null-deref in agg_dequeue
2> ac9fe7dd8 2025-05-28 net_sched: hfsc: Address reentrant enqueue adding class to eltree twice        ~ 2c928b3a0 2025-06-04
   3f9811381 2025-05-22 sch_hfsc: Fix qlen accounting bug when using peek in hfsc_enqueue()            ~ 89c301e92 2025-06-04
1> 141d34391 2025-04-28 net_sched: hfsc: Fix a UAF vulnerability in class with netem as child qdisc    ~ e3e949a39 2025-05-09
   6ccbda44e 2025-04-23 net_sched: hfsc: Fix a potential UAF in hfsc_dequeue() too                     ~ da7936518 2025-05-02
   3df275ef0 2025-04-23 net_sched: hfsc: Fix a UAF vulnerability in class handling                     ~ fcc8ede66 2025-05-02
   51eb3b655 2025-04-08 sch_hfsc: make hfsc_qlen_notify() idempotent
   49e8ae537 2024-04-19 net_sched: sch_hfsc: implement lockless accesses to q->defcls
   241a94abc 2024-02-02 net/sched: Add module aliases for cls_,sch_,act_ modules
   f96118c5d 2023-11-01 net: sched: Fill in missing MODULE_DESCRIPTION for qdiscs
   a13b67c9a 2023-10-18 net/sched: sch_hfsc: upgrade 'rt' to 'sc' when it becomes a inner curve        ~ b33179dbf 2023-10-25  ~ 9950d4d93 2023-10-26  ~ 963fd188b 2025-02-21  ~ a39a303c0 2023-10-25  ~ 7a44a1754 2025-02-21
   b3d26c570 2023-08-25 net/sched: sch_hfsc: Ensure inner classes have fsc curve                       ~ 4cf994d3f 2023-09-19  ~ ff4452fb0 2023-10-26  ~ 8bb8fbc62 2025-02-21  ~ 7c62e0c3c 2023-09-23  ~ 1b3b94f37 2024-09-13
   8e4553ef3 2023-08-01 net/sched: sch_hfsc: warn about class in use while deleting                                            ~ 24c91e176 2023-12-11
   8798481b6 2023-08-01 net/sched: wrap open coded Qdics class filter counter
   e046fa895 2022-09-22 net/sched: use tc_qdisc_stats_dump() in qdisc                                                          ~ c25ea5b55 2023-05-10
   a102c8973 2022-09-01 net: sched: remove redundant NULL check in change hook function                                        ~ c26048684 2023-05-10
   c19d893fb 2022-08-25 net: sched: delete duplicate cleanup of backlog and qlen                       ~ 34f2a4eed 2022-10-29  ~ 3b0715d0c 2023-05-10
   29cbcd858 2021-10-18 net: sched: Remove Qdisc::running sequence counter                                                     ~ 43c09223c 2022-06-06  ~ 43c09223c 2022-06-06
   50dc9a857 2021-10-18 net: sched: Merge Qdisc::bstats and Qdisc::cpu_bstats data types                                       ~ 07089a02e 2022-06-06  ~ 07089a02e 2022-06-06
   67c9e6270 2021-10-18 net: sched: Protect Qdisc::bstats with u64_stats                                                       ~ 899676622 2022-06-06  ~ 899676622 2022-06-06
   3aa260559 2021-07-29 net/sched: store the last executed chain also for clsact egress                = 3aa260559 2021-07-29  ~ bee2c235e 2021-12-09  ~ bee2c235e 2021-12-09                          ~ ffb881ce7 2024-09-11
   4dd78a737 2021-01-22 net: sched: Add extack to Qdisc_class_ops.delete                               = 4dd78a737 2021-01-22  = 4dd78a737 2021-01-22  = 4dd78a737 2021-01-22                          ~ d73d5e5ed 2024-09-11
   ac5c66f26 2020-07-16 Revert "net: sched: Pass root lock to Qdisc_ops.enqueue"                       = ac5c66f26 2020-07-16  = ac5c66f26 2020-07-16  = ac5c66f26 2020-07-16
   3f649ab72 2020-07-16 treewide: Remove uninitialized_var() usage                                     = 3f649ab72 2020-07-16  = 3f649ab72 2020-07-16  = 3f649ab72 2020-07-16
   964201de6 2020-07-07 net/sched: Use fallthrough pseudo-keyword                                      = 964201de6 2020-07-07  = 964201de6 2020-07-07  = 964201de6 2020-07-07                          ~ 1dada1b74 2024-09-11
   aebe4426c 2020-06-29 net: sched: Pass root lock to Qdisc_ops.enqueue                                = aebe4426c 2020-06-29  = aebe4426c 2020-06-29  = aebe4426c 2020-06-29
   8cb081746 2019-04-27 netlink: make validation more configurable for future strictness               = 8cb081746 2019-04-27  = 8cb081746 2019-04-27  = 8cb081746 2019-04-27                          # 9c3767b38 2024-09-11
   ae0be8de9 2019-04-27 netlink: make nla_nest_start() add NLA_F_NESTED flag                           = ae0be8de9 2019-04-27  = ae0be8de9 2019-04-27  = ae0be8de9 2019-04-27                          # 9c3767b38 2024-09-11
   e5f0e8f8e 2019-04-01 net: sched: introduce and use qdisc tree flush/purge helpers                   = e5f0e8f8e 2019-04-01  = e5f0e8f8e 2019-04-01  = e5f0e8f8e 2019-04-01                          ~ 8eebb4e4c 2024-09-11
   5dd431b6b 2019-04-01 net: sched: introduce and use qstats read helpers                              = 5dd431b6b 2019-04-01  = 5dd431b6b 2019-04-01  = 5dd431b6b 2019-04-01                          ~ ce56c8fb0 2024-09-11
0> 37d9cf1a3 2019-01-15 sched: Fix detection of empty queues in child qdiscs                           = 37d9cf1a3 2019-01-15  = 37d9cf1a3 2019-01-15  = 37d9cf1a3 2019-01-15                          ~ f3e1778ab 2024-09-11
   f6bab1993 2019-01-15 sched: Avoid dereferencing skb pointer after child enqueue                     = f6bab1993 2019-01-15  = f6bab1993 2019-01-15  = f6bab1993 2019-01-15                          ~ aded77caa 2024-09-11
   …

… qdisc

jira VULN-68290
cve CVE-2025-37890
commit-author Victor Nogueira <[email protected]>
commit 141d343

As described in Gerrard's report [1], we have a UAF case when an hfsc class
has a netem child qdisc. The crux of the issue is that hfsc is assuming
that checking for cl->qdisc->q.qlen == 0 guarantees that it hasn't inserted
the class in the vttree or eltree (which is not true for the netem
duplicate case).

This patch checks the n_active class variable to make sure that the code
won't insert the class in the vttree or eltree twice, catering for the
reentrant case.

[1] https://lore.kernel.org/netdev/CAHcdcOm+03OD2j6R0=YHKqmy=VgJ8xEOKuP6c7mSgnp-TEJJbw@mail.gmail.com/

Fixes: 37d9cf1 ("sched: Fix detection of empty queues in child qdiscs")
	Reported-by: Gerrard Tai <[email protected]>
	Acked-by: Jamal Hadi Salim <[email protected]>
	Signed-off-by: Victor Nogueira <[email protected]>
Link: https://patch.msgid.link/[email protected]
	Signed-off-by: Jakub Kicinski <[email protected]>
(cherry picked from commit 141d343)
	Signed-off-by: Marcin Wcisło <[email protected]>
jira VULN-68290
cve-bf CVE-2025-37890
commit-author Pedro Tammela <[email protected]>
commit ac9fe7d

Savino says:
    "We are writing to report that this recent patch
    (141d343) [1]
    can be bypassed, and a UAF can still occur when HFSC is utilized with
    NETEM.

    The patch only checks the cl->cl_nactive field to determine whether
    it is the first insertion or not [2], but this field is only
    incremented by init_vf [3].

    By using HFSC_RSC (which uses init_ed) [4], it is possible to bypass the
    check and insert the class twice in the eltree.
    Under normal conditions, this would lead to an infinite loop in
    hfsc_dequeue for the reasons we already explained in this report [5].

    However, if TBF is added as root qdisc and it is configured with a
    very low rate,
    it can be utilized to prevent packets from being dequeued.
    This behavior can be exploited to perform subsequent insertions in the
    HFSC eltree and cause a UAF."

To fix both the UAF and the infinite loop, with netem as an hfsc child,
check explicitly in hfsc_enqueue whether the class is already in the eltree
whenever the HFSC_RSC flag is set.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=141d34391abbb315d68556b7c67ad97885407547
[2] https://elixir.bootlin.com/linux/v6.15-rc5/source/net/sched/sch_hfsc.c#L1572
[3] https://elixir.bootlin.com/linux/v6.15-rc5/source/net/sched/sch_hfsc.c#L677
[4] https://elixir.bootlin.com/linux/v6.15-rc5/source/net/sched/sch_hfsc.c#L1574
[5] https://lore.kernel.org/netdev/8DuRWwfqjoRDLDmBMlIfbrsZg9Gx50DHJc1ilxsEBNe2D6NMoigR_eIRIG0LOjMc3r10nUUZtArXx4oZBIdUfZQrwjcQhdinnMis_0G7VEk=@willsroot.io/T/#u

Fixes: 37d9cf1 ("sched: Fix detection of empty queues in child qdiscs")
	Reported-by: Savino Dicanosa <[email protected]>
	Reported-by: William Liu <[email protected]>
	Acked-by: Jamal Hadi Salim <[email protected]>
	Tested-by: Victor Nogueira <[email protected]>
	Signed-off-by: Pedro Tammela <[email protected]>
Link: https://patch.msgid.link/[email protected]
	Signed-off-by: Paolo Abeni <[email protected]>

(cherry picked from commit ac9fe7d)
	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 2195729 into ctrliq:ciqlts8_6 Aug 15, 2025
2 of 3 checks passed
@PlaidCat
Copy link
Collaborator

We merged this because an informative GHA is broken for Forked Repos

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