[LTS 8.6] CVE-2026-31669, CVE-2026-46135, CVE-2026-43198 - #1561
Open
pvts-mat wants to merge 3 commits into
Open
Conversation
jira VULN-184569 cve CVE-2026-43198 commit-author Eric Dumazet <edumazet@google.com> commit 858d2a4 upstream-diff | 1. Omitted changes to the `smc_tcp_syn_recv_sock()' function as it's not present in LTS 8.6 codebase (introduced in 8270d9c ("net/smc: Limit backlog connections")). 2. The `tcp_v6_mapped_child_init()' function differs from the upstream to the extent that `tcp_v6_syn_recv_sock()' from which it was extracted differ, specifically its `skb->protocol == htons(ETH_P_IP)' branch. Code in tcp_v6_syn_recv_sock() after the call to tcp_v4_syn_recv_sock() is done too late. After tcp_v4_syn_recv_sock(), the child socket is already visible from TCP ehash table and other cpus might use it. Since newinet->pinet6 is still pointing to the listener ipv6_pinfo bad things can happen as syzbot found. Move the problematic code in tcp_v6_mapped_child_init() and call this new helper from tcp_v4_syn_recv_sock() before the ehash insertion. This allows the removal of one tcp_sync_mss(), since tcp_v4_syn_recv_sock() will call it with the correct context. Fixes: 1da177e ("Linux-2.6.12-rc2") Reported-by: syzbot+937b5bbb6a815b3e5d0b@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/69949275.050a0220.2eeac1.0145.GAE@google.com/ Signed-off-by: Eric Dumazet <edumazet@google.com> Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com> Link: https://patch.msgid.link/20260217161205.2079883-1-edumazet@google.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> (cherry picked from commit d707e4a97b50ae768d18426355045159f0f2394e) Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-187106 cve CVE-2026-46135 commit-author Chaitanya Kulkarni <kch@nvidia.com> commit 5293a88 upstream-diff In the upstream the ICResp send failure path contained `queue->state = NVMET_TCP_Q_FAILED' assignment before return and it was preserved by the fix (it was introduced in 675b453 ("nvmet-tcp: enable TLS handshake upcall"), conflict-merged in 90d624a, missing from LTS 8.6). Similarly the LTS 8.6 version did _not_ contain this assignment and that was preserved in the backport as well. Provided the CVE-2026-46135 bug condition didn't occur and `nvmet_tcp_handle_icreq()' didn't bail out early with `-ESHUTDOWN', not setting `queue->state' leaves it with the `NVMET_TCP_Q_CONNECTING' value which it had to contain for the `nvmet_tcp_handle_icreq()' to be called in the first place. Since it exits with a non-zero value the `nvmet_tcp_socket_error()' shuts down the socket, which triggers `nvmet_tcp_state_change()' callback carrying out the queue teardown in `nvmet_tcp_schedule_release_queue()'. The `queue->state != NVMET_TCP_Q_DISCONNECTING' condition is passed and the queue is released properly. nvmet_tcp_handle_icreq() updates queue->state after sending an Initialization Connection Response (ICResp), but it does so without serializing against target-side queue teardown. If an NVMe/TCP host sends an Initialization Connection Request (ICReq) and immediately closes the connection, target-side teardown may start in softirq context before io_work drains the already buffered ICReq. In that case, nvmet_tcp_schedule_release_queue() sets queue->state to NVMET_TCP_Q_DISCONNECTING and drops the queue reference under state_lock. If io_work later processes that ICReq, nvmet_tcp_handle_icreq() can still overwrite the state back to NVMET_TCP_Q_LIVE. That defeats the DISCONNECTING-state guard in nvmet_tcp_schedule_release_queue() and allows a later socket state change to re-enter teardown and issue a second kref_put() on an already released queue. The ICResp send failure path has the same problem. If teardown has already moved the queue to DISCONNECTING, a send error can still overwrite the state with NVMET_TCP_Q_FAILED, again reopening the window for a second teardown path to drop the queue reference. Fix this by serializing both post-send state transitions with state_lock and bailing out if teardown has already started. Use -ESHUTDOWN as an internal sentinel for that bail-out path rather than propagating it as a transport error like -ECONNRESET. Keep nvmet_tcp_socket_error() setting rcv_state to NVMET_TCP_RECV_ERR before honoring that sentinel so receive-side parsing stays quiesced until the existing release path completes. Fixes: c46a6465bac2 ("nvmet-tcp: add NVMe over TCP target driver") Cc: stable@vger.kernel.org Reported-by: Shivam Kumar <skumar47@syr.edu> Tested-by: Shivam Kumar <kumar.shivam43666@gmail.com> Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com> Signed-off-by: Keith Busch <kbusch@kernel.org> (cherry picked from commit 7f39b4e844e63db6a6b4c4bba59347c21aa9f808) Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
jira VULN-182698 cve CVE-2026-31669 commit-author Jiayuan Chen <jiayuan.chen@linux.dev> commit 9b55b25 upstream-diff Conflicts due to multiple updates to the `mptcp_subflow_init' function. Preserved the split of `mptcp_subflow_init' along the `IS_ENABLED(CONFIG_MPTCP_IPV6)' line. The ehash table lookups are lockless and rely on SLAB_TYPESAFE_BY_RCU to guarantee socket memory stability during RCU read-side critical sections. Both tcp_prot and tcpv6_prot have their slab caches created with this flag via proto_register(). However, MPTCP's mptcp_subflow_init() copies tcpv6_prot into tcpv6_prot_override during inet_init() (fs_initcall, level 5), before inet6_init() (module_init/device_initcall, level 6) has called proto_register(&tcpv6_prot). At that point, tcpv6_prot.slab is still NULL, so tcpv6_prot_override.slab remains NULL permanently. This causes MPTCP v6 subflow child sockets to be allocated via kmalloc (falling into kmalloc-4k) instead of the TCPv6 slab cache. The kmalloc-4k cache lacks SLAB_TYPESAFE_BY_RCU, so when these sockets are freed without SOCK_RCU_FREE (which is cleared for child sockets by design), the memory can be immediately reused. Concurrent ehash lookups under rcu_read_lock can then access freed memory, triggering a slab-use-after-free in __inet_lookup_established. Fix this by splitting the IPv6-specific initialization out of mptcp_subflow_init() into a new mptcp_subflow_v6_init(), called from mptcp_proto_v6_init() before protocol registration. This ensures tcpv6_prot_override.slab correctly inherits the SLAB_TYPESAFE_BY_RCU slab cache. Fixes: b19bc29 ("mptcp: implement delegated actions") Cc: stable@vger.kernel.org Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev> Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org> Link: https://patch.msgid.link/20260406031512.189159-1-jiayuan.chen@linux.dev Signed-off-by: Jakub Kicinski <kuba@kernel.org> (cherry picked from commit 6b644e703c07d879025067d7e2a6fac8db3b2ac1) Signed-off-by: Marcin Wcisło <marcin.wcislo@conclusive.pl>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[LTS 8.6]
Commits
CVE-2026-31669
CVE-2026-46135
The problem with the backport of the upstream fix is that, in the upstream, the modification in the send-failure code branch assumes that
queue->state = NVMET_TCP_Q_FAILEDis in placekernel-src-tree/drivers/nvme/target/tcp.c
Line 911 in bad44c9
while the LTS 8.6 version does not contain such state setting. Moreover, this exact state isn't even defined in that version
kernel-src-tree/drivers/nvme/target/tcp.c
Lines 98 to 102 in ea7073f
While the immediate intuition may be to leave this branch without
queue->statemodification even in the presence of introduced-ESHUTDOWNbail-out, the solutions in other versions with similar situation are contradictory. Stablelinux-5.15.ybackport 9c63cf80895a70eb4fcfcaa725bb1ac9ae76f02b, for example, doesn't set thequeue->state, whilerocky8_10backport (embedded in 4ecf105) does, although to a different state from the upstream:kernel-src-tree/drivers/nvme/target/tcp.c
Line 912 in f92f5b2
Given those contradictory precedents and the subtle nature of the bug involving race condition between protocol state transitions and queue teardowns a deeper analysis was warranted.
The upstream bug scenario
Two communication sides: host and target. The bug occurs on the target side.
Host initiates connection, a socket is opened, and sends ICReq to target. The ICReq reaches target, where it will be handled by
nvmet_tcp_handle_icreq(), in a process thread. Immediately after sending ICReq the host closes the socket. This triggers a software interrupt on the target side andnvmet_tcp_state_change()is calledkernel-src-tree/drivers/nvme/target/tcp.c
Lines 1643 to 1668 in bad44c9
which falls to
nvmet_tcp_schedule_release_queue():kernel-src-tree/drivers/nvme/target/tcp.c
Lines 1376 to 1388 in bad44c9
The code branches off to
queuerelease (upon receiving ICReq thequeue->stateisNVMET_TCP_Q_CONNECTING):kernel-src-tree/drivers/nvme/target/tcp.c
Lines 1384 to 1385 in bad44c9
The
queue->stateis set toNVMET_TCP_Q_DISCONNECTINGand the queue is released withkref_put().All of this may happen before the ICReq is dequeued and fully handled. This work is done in the process context, in the
io_workkernel-src-tree/drivers/nvme/target/tcp.c
Line 1895 in bad44c9
function
nvmet_tcp_io_workkernel-src-tree/drivers/nvme/target/tcp.c
Line 1407 in bad44c9
The chain of calls leading to
nvmet_tcp_handle_icreq()handling the ICReq is following:kernel-src-tree/drivers/nvme/target/tcp.c
Line 1417 in bad44c9
kernel-src-tree/drivers/nvme/target/tcp.c
Line 1354 in bad44c9
kernel-src-tree/drivers/nvme/target/tcp.c
Line 1322 in bad44c9
kernel-src-tree/drivers/nvme/target/tcp.c
Line 1217 in bad44c9
kernel-src-tree/drivers/nvme/target/tcp.c
Line 1030 in bad44c9
Upon calling
nvmet_tcp_handle_icreq()thequeue->stateisNVMET_TCP_Q_CONNECTINGkernel-src-tree/drivers/nvme/target/tcp.c
Line 1024 in bad44c9
The ICResp is constructed
kernel-src-tree/drivers/nvme/target/tcp.c
Lines 894 to 908 in bad44c9
and sent back to the host
kernel-src-tree/drivers/nvme/target/tcp.c
Line 909 in bad44c9
Then, after
nvmet_tcp_schedule_release_queue()done its work in softirq in reaction to the closed socket and setqueue->state = NVMET_TCP_Q_DISCONNECTING, thenvmet_tcp_handle_icreq()function setsqueue->statetoNVMET_TCP_Q_LIVE, reopening the connection again,kernel-src-tree/drivers/nvme/target/tcp.c
Line 915 in bad44c9
or to
NVMET_TCP_Q_FAILEDkernel-src-tree/drivers/nvme/target/tcp.c
Line 911 in bad44c9
Now, a change of the socket state can trigger
nvmet_tcp_schedule_release_queue()again, and becausequeue->stateis no longerNVMET_TCP_Q_DISCONNECTINGthe queue teardown code path is hit again, on an already releasedqueuekernel-src-tree/drivers/nvme/target/tcp.c
Lines 1384 to 1385 in bad44c9
The upstream fix
Right before the queue state change in
nvmet_tcp_handle_icreq(), whether it's toNVMET_TCP_Q_FAILEDorNVMET_TCP_Q_LIVE, a condition described above is detected by checking ifqueue->statedidn't becomeNVMET_TCP_Q_DISCONNECTINGsince the last check atkernel-src-tree/drivers/nvme/target/tcp.c
Line 1024 in bad44c9
If that's the case the request handling is interrupted with
-ESHUTDOWN. If not, the code proceeds to alter thequeue->stateand continues protocol processing. In either case the handling ofqueue->stateis guarded byqueue->state_lockspinlock to serialize it with the possible concurrent access in the softirqnvmet_tcp_schedule_release_queue()callback.If the request handling is interrupted the
-ESHUTDOWNreturn code is eventually passed tonvmet_tcp_socket_error()asstatusargument, atkernel-src-tree/drivers/nvme/target/tcp.c
Line 1356 in bad44c9
The setting
queue->rcv_state = NVMET_TCP_RECV_ERRremains unchanged, including now also the newly introduced casestatus == -ESHUTDOWN. This prevents attempts of processing the ICReq again. From the commit's message:The "staying quiescent" refers to this fragment
kernel-src-tree/drivers/nvme/target/tcp.c
Lines 1318 to 1319 in bad44c9
Once
queue->state_recvis set toNVMET_TCP_RECV_ERRthe processing of any messages from the host is effectively suppressed.Verdict for LTS 8.6
For the LTS 8.6 version it is correct for the ICResp send failure path in
nvmet_tcp_handle_icreq()to remain without setting thequeue->state.Because of
kernel-src-tree/drivers/nvme/target/tcp.c
Line 977 in ea7073f
not setting
queue->stateto anything in the ICResp send failure path equals to setting it toNVMET_TCP_Q_CONNECTING. From the fixing commit's message:The "reopening of the window" refers to the previously mentioned "defeat of the DISCONNECTING-state guard":
kernel-src-tree/drivers/nvme/target/tcp.c
Line 1261 in ea7073f
In the upstream both
NVMET_TCP_Q_LIVEandNVMET_TCP_Q_FAILEDstates would pass the test and proceed to the second teardown (if not for the patch). This situation doesn't differ from LTS 8.6'sNVMET_TCP_Q_LIVEandNVMET_TCP_Q_CONNECTING, both also different fromNVMET_TCP_Q_DISCONNECTING, upholding the patch's logic.In particular the
queue->stateshould not be set toNVMET_TCP_Q_DISCONNECTINGin the ICResp send failure path, as it's done inrocky8_10, because it's not accompanied by the queue teardown, which happens only in thenvmet_tcp_schedule_release_queue()function, and when it's eventually called from thenvmet_tcp_state_change()callback as the result of closing the socket (kernel_sock_shutdown(...)) in thenvmet_tcp_socket_error()status-handling function, the teardown will be skipped, resulting in leaks.CVE-2026-43198
In the upstream the
tcp_v6_mapped_child_init()function was created fromtcp_v6_syn_recv_sock()- the code was extracted to be executed at different point in the sequence. The LTS 8.6 backport maintains this logic. Since the upstream version oftcp_v6_syn_recv_sock()differs from what can be found in LTS 8.6 thetcp_v6_mapped_child_init()also differs in the same way.What may be confusing is that the Rocky 8.10 backport of that same fix (theoretically 0010a14, technically embedded in the buildable 0010a14) contains
tcp_v6_mapped_child_init()which is different from what is proposed in this PR, despitetcp_v6_syn_recv_sock()from before the fix being the same in both versions (at least up to the modifiedskb->protocol == htons(ETH_P_IP)branch).This discrepancy suggested the following scenarios (at least):
To eliminate the possibility of (1) or (4) the solution differences were analyzed.
Extracted fragment, identical in both versions:
kernel-src-tree/net/ipv6/tcp_ipv6.c
Lines 1127 to 1181 in ea7073f
Function
tcp_v6_mapped_child_init()in Rocky 8.10 after fix:kernel-src-tree/net/ipv6/tcp_ipv6.c
Lines 1097 to 1131 in 0010a14
Function
tcp_v6_mapped_child_init()in the proposed solution for CVE-2026-43198 fix on LTS 8.6:kernel-src-tree/net/ipv6/tcp_ipv6.c
Lines 1123 to 1158 in da0c06b
After the eliminating the syntactically equivalent differences what remains is the value of
newnp. Rocky 8.10:LTS 8.6:
The LTS 8.6 version reflects directly what was in
tcp_v6_syn_recv_sock()before the fix, the Rocky 8.10 version does not. These values are not strictly equivalent unless some additional conditions hold true.The socket structs can be arranged in "type inheritance" hierarchy, where the "base class" is the first field:
sockinet_sockipv6_pinfo pinet6inet_connection_socktcp_socktcp6_sockipv6_pinfo inet6With
newskbeing of typetcp6_sock(at least), the "newtcp6sk" access method recovers thetcp6_sock::inet6field. whileinet6_sk(newsk)returnsinet_sock::pinet6. Seekernel-src-tree/include/linux/ipv6.h
Lines 342 to 345 in ea7073f
These fields can nevertheless be, for all intents and purposes of
tcp_v6_syn_recv_sock()function, the same. This is suggested by the commit 93a77c1, not backported to neither Rocky 8.10 nor LTS 8.6. It introduces functiontcp_inet6_sk()abstracting away the "newtcp6sk"inet6access method, but it also converts theinet6_sk(X)instances totcp_inet6_sk(X)(see 93a77c1#diff-8b341e52e57c996bc4f294087ab526ac0b1c3c47e045557628cc24277cbfda0dL1088-L1098).This suggests option (4): both solutions are correct, but Rocky 8.10 incorporates also the change from commit 93a77c1. For the LTS 8.6 solution a more straightforward implementation of
tcp_v6_mapped_child_init()was chosen, reflecting the code extracted fromtcp_v6_syn_recv_sock()directly.kABI check: passed
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-44–run1.log
kselftests–ciqlts8_6-CVE-batch-44–run2.log
Comparison
The tests results for the reference and the patch are the same.
full-test-results-comparison.log