CVE-2026-46038
Description
In the Linux kernel, the following vulnerability has been resolved:
net: qrtr: ns: Free the node during ctrl_cmd_bye()
A node sends the BYE packet when it is about to go down. So the nameserver should advertise the removal of the node to all remote and local observers and free the node finally. But currently, the nameserver doesn't free the node memory even after processing the BYE packet. This causes the node memory to leak.
Hence, remove the node from Xarray list and free the node memory during both success and failure case of ctrl_cmd_bye().
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Memory leak in Linux kernel QRTR nameserver node structure not freed on BYE packet, leading to resource exhaustion.
Vulnerability
In the Linux kernel's QRTR (Qualcomm IPC Router) nameserver (net/qrtr/ns.c), the ctrl_cmd_bye() function processes a BYE packet indicating that a node is going down. The node should be removed from the Xarray list and its memory freed. However, the kernel fails to free the node memory after processing the BYE packet, causing a memory leak. Affected versions are those before the fix commit [1].
Exploitation
An attacker with the ability to send a crafted BYE packet to the QRTR nameserver (likely requiring local access or the ability to spoof a peer node) can trigger the memory leak. The attacker sends a BYE packet to the nameserver, which processes it but does not free the corresponding node structure. Repeatedly sending such packets can exhaust kernel memory.
Impact
Successfully triggering this memory leak repeatedly can lead to kernel memory exhaustion, potentially causing denial of service (DoS) conditions, such as system instability or crashes. There is no information disclosure or privilege escalation.
Mitigation
The fix is committed in the Linux kernel stable repository [1]. The commit 154fc7fe3f62c46891c3c4302f4b5b5391c932e6 ensures that the node is removed from the Xarray list and freed during both success and failure cases of ctrl_cmd_bye(). Users should update to a kernel version containing this patch. No workaround is available.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
10154fc7fe3f62net: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e5b3dac7836b1b..2018a71a5c4c1c 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -342,7 +342,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -357,8 +357,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -375,10 +377,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
076e4b162d6cnet: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3d1172c1b9c5ff..f189e1092d44cb 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -360,7 +360,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -375,8 +375,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -393,10 +395,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
68efba36446anet: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 5b08d4d4840a3c..1b9a90240a6802 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -359,7 +359,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -374,8 +374,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -392,10 +394,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
ff78ed177a66net: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e6788b45000ab7..01a4cbd651e464 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -339,7 +339,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -354,8 +354,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -372,10 +374,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
65932f5102bbnet: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e5b3dac7836b1b..2018a71a5c4c1c 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -342,7 +342,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -357,8 +357,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -375,10 +377,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
076e4b162d6cnet: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3d1172c1b9c5ff..f189e1092d44cb 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -360,7 +360,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -375,8 +375,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -393,10 +395,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
68efba36446anet: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 5b08d4d4840a3c..1b9a90240a6802 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -359,7 +359,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -374,8 +374,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -392,10 +394,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
ff78ed177a66net: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e6788b45000ab7..01a4cbd651e464 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -339,7 +339,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -354,8 +354,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -372,10 +374,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
65932f5102bbnet: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e5b3dac7836b1b..2018a71a5c4c1c 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -342,7 +342,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -357,8 +357,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -375,10 +377,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
154fc7fe3f62net: qrtr: ns: Free the node during ctrl_cmd_bye()
1 file changed · +15 −6
net/qrtr/ns.c+15 −6 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index e5b3dac7836b1b..2018a71a5c4c1c 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -342,7 +342,7 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) struct qrtr_node *node; unsigned long index; struct kvec iv; - int ret; + int ret = 0; iv.iov_base = &pkt; iv.iov_len = sizeof(pkt); @@ -357,8 +357,10 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) /* Advertise the removal of this client to all local servers */ local_node = node_get(qrtr_ns.local_node); - if (!local_node) - return 0; + if (!local_node) { + ret = 0; + goto delete_node; + } memset(&pkt, 0, sizeof(pkt)); pkt.cmd = cpu_to_le32(QRTR_TYPE_BYE); @@ -375,10 +377,18 @@ static int ctrl_cmd_bye(struct sockaddr_qrtr *from) ret = kernel_sendmsg(qrtr_ns.sock, &msg, &iv, 1, sizeof(pkt)); if (ret < 0 && ret != -ENODEV) { pr_err("failed to send bye cmd\n"); - return ret; + goto delete_node; } } - return 0; + + /* Ignore -ENODEV */ + ret = 0; + +delete_node: + xa_erase(&nodes, from->sq_node); + kfree(node); + + return ret; } static int ctrl_cmd_del_client(struct sockaddr_qrtr *from, -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing node cleanup in ctrl_cmd_bye() — the function never frees the qrtr_node memory or removes it from the Xarray after processing a BYE packet, causing a memory leak."
Attack vector
A remote QRTR node sends a BYE control packet (QRTR_TYPE_BYE) to the kernel nameserver when it is about to go down. The nameserver's ctrl_cmd_bye() function in net/qrtr/ns.c processes the packet and advertises the node's removal to local servers, but then returns without freeing the node structure or erasing it from the Xarray. An attacker who can cause a node to send repeated BYE packets (or who controls a node that goes down and comes back) can trigger a kernel memory leak over time, potentially exhausting system memory.
Affected code
The vulnerability is in the function ctrl_cmd_bye() in net/qrtr/ns.c. All patch variants show the same logical change to this single function.
What the fix does
The patch restructures ctrl_cmd_bye() so that both success and error paths converge at a new "delete_node" label. At that label, the node is removed from the Xarray via xa_erase(&nodes, from->sq_node) and freed via kfree(node). Previously, early returns (e.g. when local_node was NULL or when kernel_sendmsg failed) skipped cleanup entirely. The patch also initializes ret = 0 and ignores -ENODEV errors from sendmsg, ensuring the function always cleans up before returning.
Preconditions
- networkThe attacker must be able to send a QRTR BYE control packet to the kernel nameserver.
- configThe kernel QRTR nameserver (CONFIG_QRTR=m or built-in) must be active.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/076e4b162d6caba12c229e7f262df5b6881162b0nvd
- git.kernel.org/stable/c/154fc7fe3f62c46891c3c4302f4b5b5391c932e6nvd
- git.kernel.org/stable/c/65932f5102bb5377db36c8a4f0c28179a1967a9anvd
- git.kernel.org/stable/c/68efba36446a7774ea5b971257ade049272a07acnvd
- git.kernel.org/stable/c/ff78ed177a66763085e3214d6fbe13ca8f0b3f11nvd
News mentions
0No linked articles in our index yet.