CVE-2026-46047
Description
In the Linux kernel, the following vulnerability has been resolved:
net: qrtr: ns: Fix use-after-free in driver remove()
In the remove callback, if a packet arrives after destroy_workqueue() is called, but before sock_release(), the qrtr_ns_data_ready() callback will try to queue the work, causing use-after-free issue.
Fix this issue by saving the default 'sk_data_ready' callback during qrtr_ns_init() and use it to replace the qrtr_ns_data_ready() callback at the start of remove(). This ensures that even if a packet arrives after destroy_workqueue(), the work struct will not be dereferenced.
Note that it is also required to ensure that the RX threads are completed before destroying the workqueue, because the threads could be using the qrtr_ns_data_ready() callback.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Use-after-free in Linux kernel's QRTR namespace service (qrtr_ns) during driver removal due to late packet arrival after workqueue destruction.
Vulnerability
A use-after-free vulnerability exists in the Linux kernel's QRTR namespace service (qrtr_ns). During the remove callback of the driver, a race window allows a packet to arrive after destroy_workqueue() is called but before sock_release(). The qrtr_ns_data_ready() callback then attempts to queue work on the destroyed workqueue, leading to a use-after-free condition. The affected code is in net/qrtr/ns.c. The issue is present in kernel versions prior to the fix commit [1].
Exploitation
An attacker would need to be able to send crafted packets to the QRTR socket, and have the driver removal occur while packets are in flight. The attack requires precise timing to trigger the race condition where a packet arrives after destroy_workqueue() but before sock_release(). No special privileges beyond network access to QRTR are required if the socket is exposed. The race window is dependent on system load and scheduling.
Impact
Successful exploitation results in a use-after-free, which can lead to arbitrary code execution or system crash. The attacker gains control over freed kernel memory, potentially escalating privileges or causing denial of service. As this is a use-after-free in kernel space, the impact is a compromise of system integrity, confidentiality, and availability.
Mitigation
The fix was applied in the Linux kernel stable tree via commit db3c60ec772de30acae92d560dfcc5258e58dbe8 [1]. The patch saves the default sk_data_ready callback during qrtr_ns_init() and restores it at the start of qrtr_ns_remove() before destroying the workqueue. It also ensures RX threads are completed before workqueue destruction. Users should update to a kernel version containing this fix. No workaround is provided in the reference.
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
1Patches
107809fea20c94net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index c0418764470bed..b3f9bbcf9ab9ba 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -25,6 +25,7 @@ static struct { u32 lookup_count; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -757,6 +758,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -797,6 +799,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -806,7 +812,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
0f313eb6a8f6net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 654a3cc0d3479e..e6788b45000ab7 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -706,6 +707,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -746,6 +748,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -755,7 +761,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
db3c60ec772dnet: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3de9350cbf3075..e5b3dac7836b1b 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -709,6 +710,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -749,6 +751,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -758,7 +764,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
f96779e91657net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3203b222086034..354cea22d088f4 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -709,6 +710,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -749,6 +751,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -758,7 +764,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
2e127ceb1c41net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3de9350cbf3075..e5b3dac7836b1b 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -709,6 +710,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -749,6 +751,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -758,7 +764,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
f96779e91657net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3203b222086034..354cea22d088f4 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -709,6 +710,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -749,6 +751,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -758,7 +764,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
0f313eb6a8f6net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 654a3cc0d3479e..e6788b45000ab7 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -706,6 +707,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -746,6 +748,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -755,7 +761,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
2e127ceb1c41net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3de9350cbf3075..e5b3dac7836b1b 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -709,6 +710,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -749,6 +751,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -758,7 +764,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
7809fea20c94net: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index c0418764470bed..b3f9bbcf9ab9ba 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -25,6 +25,7 @@ static struct { u32 lookup_count; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -757,6 +758,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -797,6 +799,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -806,7 +812,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
db3c60ec772dnet: qrtr: ns: Fix use-after-free in driver remove()
1 file changed · +11 −1
net/qrtr/ns.c+11 −1 modifieddiff --git a/net/qrtr/ns.c b/net/qrtr/ns.c index 3de9350cbf3075..e5b3dac7836b1b 100644 --- a/net/qrtr/ns.c +++ b/net/qrtr/ns.c @@ -24,6 +24,7 @@ static struct { struct list_head lookups; struct workqueue_struct *workqueue; struct work_struct work; + void (*saved_data_ready)(struct sock *sk); int local_node; } qrtr_ns; @@ -709,6 +710,7 @@ int qrtr_ns_init(void) goto err_sock; } + qrtr_ns.saved_data_ready = qrtr_ns.sock->sk->sk_data_ready; qrtr_ns.sock->sk->sk_data_ready = qrtr_ns_data_ready; sq.sq_port = QRTR_PORT_CTRL; @@ -749,6 +751,10 @@ int qrtr_ns_init(void) return 0; err_wq: + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + destroy_workqueue(qrtr_ns.workqueue); err_sock: sock_release(qrtr_ns.sock); @@ -758,7 +764,12 @@ EXPORT_SYMBOL_GPL(qrtr_ns_init); void qrtr_ns_remove(void) { + write_lock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + qrtr_ns.sock->sk->sk_data_ready = qrtr_ns.saved_data_ready; + write_unlock_bh(&qrtr_ns.sock->sk->sk_callback_lock); + cancel_work_sync(&qrtr_ns.work); + synchronize_net(); destroy_workqueue(qrtr_ns.workqueue); /* sock_release() expects the two references that were put during -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing teardown ordering in qrtr_ns_remove() allows the qrtr_ns_data_ready() callback to queue work on a destroyed workqueue, causing use-after-free."
Attack vector
During driver removal, qrtr_ns_remove() calls destroy_workqueue() before restoring the original sk_data_ready callback. If a network packet arrives in this window, the qrtr_ns_data_ready() callback (still installed on the socket) attempts to queue work_struct work onto the already-destroyed workqueue, causing a use-after-free. An attacker on the same QRTR bus can trigger this by sending a packet to the nameservice socket while the driver is being removed [patch_id=2660159].
Affected code
The vulnerability is in net/qrtr/ns.c, in the qrtr_ns_remove() function and the error path of qrtr_ns_init() [patch_id=2660159]. The static qrtr_ns structure holds the workqueue and work_struct that are freed before the sk_data_ready callback is restored.
What the fix does
The patch saves the original sk_data_ready pointer in qrtr_ns.saved_data_ready during qrtr_ns_init() [patch_id=2660159]. In qrtr_ns_remove(), the custom qrtr_ns_data_ready callback is replaced with the saved default before cancel_work_sync() and destroy_workqueue() are called. A synchronize_net() barrier is also added after cancel_work_sync() to ensure any in-flight RX threads using the old callback have completed before the workqueue is destroyed. The same restoration is applied in the error path of qrtr_ns_init() for consistency.
Preconditions
- configThe QRTR nameservice driver must be in the process of being removed (e.g., module unload or device removal).
- networkAn attacker must be able to send a QRTR packet to the nameservice control port (QRTR_PORT_CTRL) during the removal window.
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/0f313eb6a8f6dffa491373cf3afab979fa1c02f4nvd
- git.kernel.org/stable/c/2e127ceb1c415e246076d8e09e23e443a7a2038fnvd
- git.kernel.org/stable/c/7809fea20c9404bfcfa6112ec08d1fe1d3520bebnvd
- git.kernel.org/stable/c/db3c60ec772de30acae92d560dfcc5258e58dbe8nvd
- git.kernel.org/stable/c/f96779e916576e81430ebb326baff6e433fef8aenvd
News mentions
0No linked articles in our index yet.