* [PATCH] vhost: fix use-after-free in fdset during shutdown
@ 2026-02-04 18:03 Yehor Malikov
2026-02-04 18:48 ` [PATCH v2] " malikovyehor
0 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-04 18:03 UTC (permalink / raw)
To: dev@dpdk.org; +Cc: maxime.coquelin@redhat.com, chenbox@nvidia.com
[-- Attachment #1: Type: text/plain, Size: 4342 bytes --]
From ecc4db77657e4a1388d3799883238adf6d693c73 Mon Sep 17 00:00:00 2001
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
Date: Wed, 4 Feb 2026 11:58:15 +0100
Subject: [PATCH] vhost: fix use-after-free in fdset during shutdown
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to properly stop the dispatch thread
before freeing resources:
- Set destroy flag to signal thread exit
- Wait for thread completion via rte_thread_join()
- Close epoll fd and free memory only after thread exits
Add RTE_FINI destructor to ensure fdset cleanup runs before EAL
cleanup frees hugepages.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 33 +++++++++++++++++++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 8 ++++++++
4 files changed, 43 insertions(+)
diff --git a/.mailmap b/.mailmap
index 34a99f93a1..6fb87ca810 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1800,6 +1800,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..4c759d44a4 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,39 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+ int i;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+
+ /* Close epoll fd */
+ close(pfdset->epfd);
+
+ /* Remove from global fdsets list */
+ pthread_mutex_lock(&fdsets_mutex);
+ for (i = 0; i < MAX_FDSETS; i++) {
+ if (fdsets[i] == pfdset) {
+ fdsets[i] = NULL;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&fdsets_mutex);
+
+ /* Free the fdset */
+ rte_free(pfdset);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index 9b4f332f94..e953dd1849 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -1209,3 +1209,11 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
--
2.51.1
CONFIDENTIALITY NOTICE: This email and any files attached may contain confidential information and may be restricted from disclosure by corporate confidentiality guidelines, or applicable state and federal law. It is intended solely for the use of the person or entity to whom the email was addressed. If you are not the intended recipient of this message, be advised that any dissemination, distribution, or use of the contents of this message is strictly prohibited. Please delete this email from your system if you are not the intended recipient.
[-- Attachment #2: Type: text/html, Size: 18421 bytes --]
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v2] vhost: fix use-after-free in fdset during shutdown
2026-02-04 18:03 [PATCH] vhost: fix use-after-free in fdset during shutdown Yehor Malikov
@ 2026-02-04 18:48 ` malikovyehor
2026-02-04 18:58 ` [PATCH v3] " Yehor Malikov
0 siblings, 1 reply; 23+ messages in thread
From: malikovyehor @ 2026-02-04 18:48 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to properly stop the dispatch thread
before freeing resources:
- Set destroy flag to signal thread exit
- Wait for thread completion via rte_thread_join()
- Close epoll fd and free memory only after thread exits
Add RTE_FINI destructor to ensure fdset cleanup runs before EAL
cleanup frees hugepages.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 33 +++++++++++++++++++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 8 ++++++++
4 files changed, 43 insertions(+)
diff --git a/.mailmap b/.mailmap
index 34a99f93a1..6fb87ca810 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1800,6 +1800,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..4c759d44a4 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,39 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+ int i;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+
+ /* Close epoll fd */
+ close(pfdset->epfd);
+
+ /* Remove from global fdsets list */
+ pthread_mutex_lock(&fdsets_mutex);
+ for (i = 0; i < MAX_FDSETS; i++) {
+ if (fdsets[i] == pfdset) {
+ fdsets[i] = NULL;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&fdsets_mutex);
+
+ /* Free the fdset */
+ rte_free(pfdset);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index 9b4f332f94..e953dd1849 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -1209,3 +1209,11 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v3] vhost: fix use-after-free in fdset during shutdown
2026-02-04 18:48 ` [PATCH v2] " malikovyehor
@ 2026-02-04 18:58 ` Yehor Malikov
2026-02-04 20:34 ` Stephen Hemminger
` (2 more replies)
0 siblings, 3 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-04 18:58 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to properly stop the dispatch thread
before freeing resources:
- Set destroy flag to signal thread exit
- Wait for thread completion via rte_thread_join()
- Close epoll fd and free memory only after thread exits
Add RTE_FINI destructor to ensure fdset cleanup runs before EAL
cleanup frees hugepages.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 33 +++++++++++++++++++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 8 ++++++++
4 files changed, 43 insertions(+)
diff --git a/.mailmap b/.mailmap
index 34a99f93a1..6fb87ca810 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1800,6 +1800,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..4c759d44a4 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,39 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+ int i;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+
+ /* Close epoll fd */
+ close(pfdset->epfd);
+
+ /* Remove from global fdsets list */
+ pthread_mutex_lock(&fdsets_mutex);
+ for (i = 0; i < MAX_FDSETS; i++) {
+ if (fdsets[i] == pfdset) {
+ fdsets[i] = NULL;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&fdsets_mutex);
+
+ /* Free the fdset */
+ rte_free(pfdset);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index 9b4f332f94..e953dd1849 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -1209,3 +1209,11 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v3] vhost: fix use-after-free in fdset during shutdown
2026-02-04 18:58 ` [PATCH v3] " Yehor Malikov
@ 2026-02-04 20:34 ` Stephen Hemminger
2026-02-04 21:32 ` Yehor Malikov
2026-02-04 21:35 ` [PATCH v4] " Yehor Malikov
2 siblings, 0 replies; 23+ messages in thread
From: Stephen Hemminger @ 2026-02-04 20:34 UTC (permalink / raw)
To: Yehor Malikov; +Cc: dev, maxime.coquelin, chenbox, Yehor Malikov
On Wed, 4 Feb 2026 19:58:00 +0100
Yehor Malikov <malikovyehor@gmail.com> wrote:
> + /* Remove from global fdsets list */
> + pthread_mutex_lock(&fdsets_mutex);
> + for (i = 0; i < MAX_FDSETS; i++) {
> + if (fdsets[i] == pfdset) {
> + fdsets[i] = NULL;
> + break;
> + }
> + }
> + pthread_mutex_unlock(&fdsets_mutex);
> +
> + /* Free the fdset */
> + rte_free(pfdset);
> +}
> +
Calling mutex_lock in destructor seems it could be a problem.
Also, I would put in a destructor unless the init was called
by a constructor.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3] vhost: fix use-after-free in fdset during shutdown
2026-02-04 18:58 ` [PATCH v3] " Yehor Malikov
2026-02-04 20:34 ` Stephen Hemminger
@ 2026-02-04 21:32 ` Yehor Malikov
2026-02-04 21:35 ` [PATCH v4] " Yehor Malikov
2 siblings, 0 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-04 21:32 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to stop the dispatch thread by setting
the destroy flag and waiting for thread completion. Resource cleanup
(epoll fd, fdsets array, memory) is intentionally skipped since the
function is only called during process exit when the OS will reclaim
all resources anyway, avoiding potential deadlocks from mutex
operations in destructor context.
Use symmetric RTE_INIT/RTE_FINI constructors and destructors for both
vhost-user and VDUSE fdsets to ensure proper initialization at library
load and cleanup before EAL teardown.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 16 ++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 24 +++++++++++++++++++-----
lib/vhost/vduse.c | 23 ++++++++++++++++++-----
5 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/.mailmap b/.mailmap
index 34a99f93a1..6fb87ca810 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1800,6 +1800,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index 9b4f332f94..93d129774f 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1197,11 +1199,8 @@ rte_vhost_driver_start(const char *path)
return vduse_device_create(path, vsocket->net_compliant_ol_flags);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
- if (vhost_user.fdset == NULL) {
- VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
- return -1;
- }
+ VHOST_CONFIG_LOG(path, ERR, "Vhost-user fdset not initialized");
+ return -1;
}
if (vsocket->is_server)
@@ -1209,3 +1208,18 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_INIT(vhost_user_fdset_init)
+{
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
+ if (vhost_user.fdset == NULL)
+ VHOST_CONFIG_LOG(VHOST_USER_FDSET_NAME, ERR, "failed to init Vhost-user fdset");
+}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 9de7f04a4f..e770bd61d4 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -627,11 +628,8 @@ vduse_device_create(const char *path, bool compliant_ol_flags)
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
- if (vduse.fdset == NULL) {
- VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
- return -1;
- }
+ VHOST_CONFIG_LOG(path, ERR, "VDUSE fdset not initialized");
+ return -1;
}
control_fd = open(VDUSE_CTRL_PATH, O_RDWR);
@@ -878,3 +876,18 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+RTE_INIT(vduse_fdset_init)
+{
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
+ if (vduse.fdset == NULL)
+ VHOST_CONFIG_LOG(VDUSE_FDSET_NAME, ERR, "failed to init VDUSE fdset");
+}
+
+RTE_FINI(vduse_fdset_fini)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v4] vhost: fix use-after-free in fdset during shutdown
2026-02-04 18:58 ` [PATCH v3] " Yehor Malikov
2026-02-04 20:34 ` Stephen Hemminger
2026-02-04 21:32 ` Yehor Malikov
@ 2026-02-04 21:35 ` Yehor Malikov
2026-02-04 22:00 ` [PATCH v5] " Yehor Malikov
2 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-04 21:35 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to stop the dispatch thread by setting
the destroy flag and waiting for thread completion. Resource cleanup
(epoll fd, fdsets array, memory) is intentionally skipped since the
function is only called during process exit when the OS will reclaim
all resources anyway, avoiding potential deadlocks from mutex
operations in destructor context.
Use symmetric RTE_INIT/RTE_FINI constructors and destructors for both
vhost-user and VDUSE fdsets to ensure proper initialization at library
load and cleanup before EAL teardown.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 16 ++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 24 +++++++++++++++++++-----
lib/vhost/vduse.c | 23 ++++++++++++++++++-----
5 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/.mailmap b/.mailmap
index 34a99f93a1..6fb87ca810 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1800,6 +1800,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index 9b4f332f94..93d129774f 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1197,11 +1199,8 @@ rte_vhost_driver_start(const char *path)
return vduse_device_create(path, vsocket->net_compliant_ol_flags);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
- if (vhost_user.fdset == NULL) {
- VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
- return -1;
- }
+ VHOST_CONFIG_LOG(path, ERR, "Vhost-user fdset not initialized");
+ return -1;
}
if (vsocket->is_server)
@@ -1209,3 +1208,18 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_INIT(vhost_user_fdset_init)
+{
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
+ if (vhost_user.fdset == NULL)
+ VHOST_CONFIG_LOG(VHOST_USER_FDSET_NAME, ERR, "failed to init Vhost-user fdset");
+}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 9de7f04a4f..e770bd61d4 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -627,11 +628,8 @@ vduse_device_create(const char *path, bool compliant_ol_flags)
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
- if (vduse.fdset == NULL) {
- VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
- return -1;
- }
+ VHOST_CONFIG_LOG(path, ERR, "VDUSE fdset not initialized");
+ return -1;
}
control_fd = open(VDUSE_CTRL_PATH, O_RDWR);
@@ -878,3 +876,18 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+RTE_INIT(vduse_fdset_init)
+{
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
+ if (vduse.fdset == NULL)
+ VHOST_CONFIG_LOG(VDUSE_FDSET_NAME, ERR, "failed to init VDUSE fdset");
+}
+
+RTE_FINI(vduse_fdset_fini)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v5] vhost: fix use-after-free in fdset during shutdown
2026-02-04 21:35 ` [PATCH v4] " Yehor Malikov
@ 2026-02-04 22:00 ` Yehor Malikov
2026-02-04 23:05 ` [PATCH v6] " Yehor Malikov
0 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-04 22:00 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to stop the dispatch thread by setting
the destroy flag and waiting for thread completion. Resource cleanup
(epoll fd, fdsets array, memory) is intentionally skipped since the
function is only called during process exit when the OS will reclaim
all resources anyway, avoiding potential deadlocks from mutex
operations in destructor context.
Use symmetric RTE_INIT/RTE_FINI constructors and destructors for both
vhost-user and VDUSE fdsets to ensure proper initialization at library
load and cleanup before EAL teardown.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 16 ++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 24 +++++++++++++++++++-----
lib/vhost/vduse.c | 23 ++++++++++++++++++-----
5 files changed, 55 insertions(+), 10 deletions(-)
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..998185a66e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1839,6 +1839,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index ae95e7e6b0..b73d3e75a9 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1198,11 +1200,8 @@ rte_vhost_driver_start(const char *path)
vsocket->extbuf, vsocket->linearbuf);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
- if (vhost_user.fdset == NULL) {
- VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
- return -1;
- }
+ VHOST_CONFIG_LOG(path, ERR, "Vhost-user fdset not initialized");
+ return -1;
}
if (vsocket->is_server)
@@ -1210,3 +1209,18 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_INIT(vhost_user_fdset_init)
+{
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
+ if (vhost_user.fdset == NULL)
+ VHOST_CONFIG_LOG(VHOST_USER_FDSET_NAME, ERR, "failed to init Vhost-user fdset");
+}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 0b5d158fee..ad0588ef9f 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -685,11 +686,8 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
- if (vduse.fdset == NULL) {
- VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
- return -1;
- }
+ VHOST_CONFIG_LOG(path, ERR, "VDUSE fdset not initialized");
+ return -1;
}
control_fd = open(VDUSE_CTRL_PATH, O_RDWR);
@@ -942,3 +940,18 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+RTE_INIT(vduse_fdset_init)
+{
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
+ if (vduse.fdset == NULL)
+ VHOST_CONFIG_LOG(VDUSE_FDSET_NAME, ERR, "failed to init VDUSE fdset");
+}
+
+RTE_FINI(vduse_fdset_fini)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v6] vhost: fix use-after-free in fdset during shutdown
2026-02-04 22:00 ` [PATCH v5] " Yehor Malikov
@ 2026-02-04 23:05 ` Yehor Malikov
2026-02-05 1:17 ` Stephen Hemminger
` (2 more replies)
0 siblings, 3 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-04 23:05 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to stop the dispatch thread by setting
the destroy flag and waiting for thread completion. Resource cleanup
(epoll fd, fdsets array, memory) is intentionally skipped since the
function is only called during process exit when the OS will reclaim
all resources anyway, avoiding potential deadlocks from mutex
operations in destructor context.
Use symmetric RTE_FINI destructors for both vhost-user and VDUSE fdsets
to ensure proper cleanup before EAL teardown.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 16 ++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/socket.c | 12 +++++++++++-
lib/vhost/vduse.c | 11 ++++++++++-
5 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..998185a66e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1839,6 +1839,7 @@ Yaron Illouz <yaroni@radcom.com>
Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yelena Krivosheev <yelena@marvell.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index ae95e7e6b0..91680fbbaa 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1198,7 +1200,7 @@ rte_vhost_driver_start(const char *path)
vsocket->extbuf, vsocket->linearbuf);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
if (vhost_user.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
return -1;
@@ -1210,3 +1212,11 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+RTE_FINI(vhost_user_fdset_fini)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 0b5d158fee..8d1846b491 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -685,7 +686,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
if (vduse.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
return -1;
@@ -942,3 +943,11 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+RTE_FINI(vduse_fdset_fini)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v6] vhost: fix use-after-free in fdset during shutdown
2026-02-04 23:05 ` [PATCH v6] " Yehor Malikov
@ 2026-02-05 1:17 ` Stephen Hemminger
2026-02-05 11:16 ` [PATCH v7] " Yehor Malikov
2026-02-05 11:20 ` Yehor Malikov
2 siblings, 0 replies; 23+ messages in thread
From: Stephen Hemminger @ 2026-02-05 1:17 UTC (permalink / raw)
To: Yehor Malikov; +Cc: dev, maxime.coquelin, chenbox, Yehor Malikov
On Thu, 5 Feb 2026 00:05:02 +0100
Yehor Malikov <malikovyehor@gmail.com> wrote:
> From: Yehor Malikov <Yehor.Malikov@solidigm.com>
>
> The fdset_event_dispatch thread runs in a loop checking the destroy
> flag after each epoll_wait iteration. During process exit,
> rte_eal_cleanup() frees hugepages memory while the fdset thread is
> still running, causing use-after-free when accessing the fdset
> structure.
>
> Add fdset_deinit() function to stop the dispatch thread by setting
> the destroy flag and waiting for thread completion. Resource cleanup
> (epoll fd, fdsets array, memory) is intentionally skipped since the
> function is only called during process exit when the OS will reclaim
> all resources anyway, avoiding potential deadlocks from mutex
> operations in destructor context.
>
> Use symmetric RTE_FINI destructors for both vhost-user and VDUSE fdsets
> to ensure proper cleanup before EAL teardown.
>
> Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
>
> Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
I understand why this is important but not sure if it will work as
you expect. I asked AI to help give a better explanation (so don't trust it
to be right).
Good question. Yes, there are potential concerns:
**RTE_FINI ordering is undefined.** There's no guarantee about the order destructors run relative to each other or to EAL cleanup. The commit message says the goal is to stop the fdset thread "before EAL teardown" but `RTE_FINI` doesn't actually guarantee that - it just registers an `__attribute__((destructor))` function.
**Possible scenarios:**
1. **EAL cleanup runs first** - If `rte_eal_cleanup()` frees hugepages before the `RTE_FINI` destructor runs, we're back to the original bug. The destructor would then call `fdset_deinit()` on already-freed memory.
2. **Destructor runs during active vhost operations** - If the application is still doing vhost work when exit starts, the destructor could stop the dispatch thread while callbacks are expected to run. The `busy` flag helps with `fdset_del()` synchronization, but abrupt thread termination during shutdown could leave things inconsistent.
3. **Fork concerns** - After `fork()`, the child has the same destructor registered but the dispatch thread doesn't exist in the child (threads aren't inherited). `rte_thread_join()` on an invalid/non-existent thread ID could behave unexpectedly.
**What would be more robust:**
The proper fix would be to integrate with EAL cleanup directly - register a callback via `rte_eal_cleanup_register()` (if that exists) or have vhost explicitly clean up its fdsets in a documented shutdown sequence before `rte_eal_cleanup()` is called, rather than relying on destructor ordering.
Worth raising with the author: has this been tested with different linking scenarios (static vs dynamic) and confirmed the destructor actually runs before EAL teardown in practice?
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v7] vhost: fix use-after-free in fdset during shutdown
2026-02-04 23:05 ` [PATCH v6] " Yehor Malikov
2026-02-05 1:17 ` Stephen Hemminger
@ 2026-02-05 11:16 ` Yehor Malikov
2026-02-05 11:20 ` Yehor Malikov
2 siblings, 0 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-05 11:16 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to stop the dispatch thread by setting
the destroy flag and waiting for thread completion. Resource cleanup
(epoll fd, fdsets array, memory) is intentionally skipped since the
function is only called during process exit when the OS will reclaim
all resources anyway, avoiding potential deadlocks from mutex
operations in destructor context.
Use symmetric RTE_FINI destructors for both vhost-user and VDUSE fdsets
to ensure proper cleanup before EAL teardown.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 16 ++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/rte_vhost.h | 15 +++++++++++++++
lib/vhost/socket.c | 29 ++++++++++++++++++++++++++++-
lib/vhost/vduse.c | 17 ++++++++++++++++-
lib/vhost/vduse.h | 1 +
7 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..711a6ceff5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1840,6 +1840,7 @@ Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
Yelena Krivosheev <yelena@marvell.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
Yi Chen <chenyi221@huawei.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/rte_vhost.h b/lib/vhost/rte_vhost.h
index 2f7c4c0080..31cbbe6039 100644
--- a/lib/vhost/rte_vhost.h
+++ b/lib/vhost/rte_vhost.h
@@ -464,6 +464,21 @@ int rte_vhost_driver_register(const char *path, uint64_t flags);
/* Unregister vhost driver. This is only meaningful to vhost user. */
int rte_vhost_driver_unregister(const char *path);
+/**
+ * Cleanup vhost library resources.
+ *
+ * This function stops the fdset event dispatch threads for both vhost-user
+ * and VDUSE backends. It should be called before rte_eal_cleanup() to ensure
+ * proper shutdown without use-after-free issues.
+ *
+ * The function is safe to call multiple times or even if no vhost driver
+ * was ever started.
+ *
+ * @return
+ * 0 on success
+ */
+int rte_vhost_cleanup(void);
+
/**
* Set the vdpa device id, enforce single connection per socket
*
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index ae95e7e6b0..e378028fe1 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1198,7 +1200,7 @@ rte_vhost_driver_start(const char *path)
vsocket->extbuf, vsocket->linearbuf);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
if (vhost_user.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
return -1;
@@ -1210,3 +1212,28 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+static void
+vhost_user_fdset_cleanup(void)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
+
+RTE_EXPORT_SYMBOL(rte_vhost_cleanup)
+int
+rte_vhost_cleanup(void)
+{
+ vhost_user_fdset_cleanup();
+ vduse_fdset_cleanup();
+
+ return 0;
+}
+
+RTE_FINI(vhost_fini)
+{
+ vhost_user_fdset_cleanup();
+ vduse_fdset_cleanup();
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 0b5d158fee..2633871472 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -685,7 +686,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
if (vduse.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
return -1;
@@ -942,3 +943,17 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+void
+vduse_fdset_cleanup(void)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
+
+RTE_FINI(vduse_fdset_fini)
+{
+ vduse_fdset_cleanup();
+}
diff --git a/lib/vhost/vduse.h b/lib/vhost/vduse.h
index b2515bb9df..53f16b0238 100644
--- a/lib/vhost/vduse.h
+++ b/lib/vhost/vduse.h
@@ -11,5 +11,6 @@
int vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf);
int vduse_device_destroy(const char *path);
+void vduse_fdset_cleanup(void);
#endif /* _VDUSE_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v7] vhost: fix use-after-free in fdset during shutdown
2026-02-04 23:05 ` [PATCH v6] " Yehor Malikov
2026-02-05 1:17 ` Stephen Hemminger
2026-02-05 11:16 ` [PATCH v7] " Yehor Malikov
@ 2026-02-05 11:20 ` Yehor Malikov
2026-02-05 18:30 ` [PATCH v8] " Yehor Malikov
2 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-05 11:20 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepages memory while the fdset thread is
still running, causing use-after-free when accessing the fdset
structure.
Add fdset_deinit() function to stop the dispatch thread by setting
the destroy flag and waiting for thread completion. Resource cleanup
(epoll fd, fdsets array, memory) is intentionally skipped since the
function is only called during process exit when the OS will reclaim
all resources anyway, avoiding potential deadlocks from mutex
operations in destructor context.
Use symmetric RTE_FINI destructors for both vhost-user and VDUSE fdsets
to ensure proper cleanup before EAL teardown.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 16 ++++++++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/rte_vhost.h | 15 +++++++++++++++
lib/vhost/socket.c | 29 ++++++++++++++++++++++++++++-
lib/vhost/vduse.c | 17 ++++++++++++++++-
lib/vhost/vduse.h | 1 +
7 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..711a6ceff5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1840,6 +1840,7 @@ Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
Yelena Krivosheev <yelena@marvell.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
Yi Chen <chenyi221@huawei.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/rte_vhost.h b/lib/vhost/rte_vhost.h
index 2f7c4c0080..31cbbe6039 100644
--- a/lib/vhost/rte_vhost.h
+++ b/lib/vhost/rte_vhost.h
@@ -464,6 +464,21 @@ int rte_vhost_driver_register(const char *path, uint64_t flags);
/* Unregister vhost driver. This is only meaningful to vhost user. */
int rte_vhost_driver_unregister(const char *path);
+/**
+ * Cleanup vhost library resources.
+ *
+ * This function stops the fdset event dispatch threads for both vhost-user
+ * and VDUSE backends. It should be called before rte_eal_cleanup() to ensure
+ * proper shutdown without use-after-free issues.
+ *
+ * The function is safe to call multiple times or even if no vhost driver
+ * was ever started.
+ *
+ * @return
+ * 0 on success
+ */
+int rte_vhost_cleanup(void);
+
/**
* Set the vdpa device id, enforce single connection per socket
*
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index ae95e7e6b0..e378028fe1 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1198,7 +1200,7 @@ rte_vhost_driver_start(const char *path)
vsocket->extbuf, vsocket->linearbuf);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
if (vhost_user.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
return -1;
@@ -1210,3 +1212,28 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+static void
+vhost_user_fdset_cleanup(void)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
+
+RTE_EXPORT_SYMBOL(rte_vhost_cleanup)
+int
+rte_vhost_cleanup(void)
+{
+ vhost_user_fdset_cleanup();
+ vduse_fdset_cleanup();
+
+ return 0;
+}
+
+RTE_FINI(vhost_fini)
+{
+ vhost_user_fdset_cleanup();
+ vduse_fdset_cleanup();
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 0b5d158fee..2633871472 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -685,7 +686,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
if (vduse.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
return -1;
@@ -942,3 +943,17 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+void
+vduse_fdset_cleanup(void)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
+
+RTE_FINI(vduse_fdset_fini)
+{
+ vduse_fdset_cleanup();
+}
diff --git a/lib/vhost/vduse.h b/lib/vhost/vduse.h
index b2515bb9df..53f16b0238 100644
--- a/lib/vhost/vduse.h
+++ b/lib/vhost/vduse.h
@@ -11,5 +11,6 @@
int vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf);
int vduse_device_destroy(const char *path);
+void vduse_fdset_cleanup(void);
#endif /* _VDUSE_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v8] vhost: fix use-after-free in fdset during shutdown
2026-02-05 11:20 ` Yehor Malikov
@ 2026-02-05 18:30 ` Yehor Malikov
2026-02-05 18:35 ` [PATCH v9] " Yehor Malikov
0 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-05 18:30 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() releases resources (like hugepages via
rte_eal_memory_detach) while the fdset thread is still running.
This race condition can lead to use-after-free errors if the thread
accesses memory that has been freed.
Standard destructors (RTE_FINI) run after rte_eal_cleanup() returns,
which is too late to prevent this race.
To address this, introduce a mechanism to register cleanup callbacks
that run within rte_eal_cleanup() before memory is detached:
1. Add rte_eal_cleanup_register() API to EAL.
2. Implement fdset_deinit() in vhost to synchronously stop the
dispatch thread, close the epoll fd, and release resources.
3. Register the vhost cleanup handler during initialization to
ensure proper shutdown ordering via EAL.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/eal/common/eal_common_cleanup.c | 48 +++++++++++++++++++++++++++++
lib/eal/common/eal_private.h | 5 +++
lib/eal/common/meson.build | 1 +
lib/eal/freebsd/eal.c | 1 +
lib/eal/include/rte_eal.h | 15 +++++++++
lib/eal/linux/eal.c | 1 +
lib/eal/windows/eal.c | 1 +
lib/vhost/fd_man.c | 16 ++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/rte_vhost.h | 15 +++++++++
lib/vhost/socket.c | 35 ++++++++++++++++++++-
lib/vhost/vduse.c | 12 +++++++-
lib/vhost/vduse.h | 1 +
14 files changed, 151 insertions(+), 2 deletions(-)
create mode 100644 lib/eal/common/eal_common_cleanup.c
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..711a6ceff5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1840,6 +1840,7 @@ Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
Yelena Krivosheev <yelena@marvell.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
Yi Chen <chenyi221@huawei.com>
diff --git a/lib/eal/common/eal_common_cleanup.c b/lib/eal/common/eal_common_cleanup.c
new file mode 100644
index 0000000000..933484e254
--- /dev/null
+++ b/lib/eal/common/eal_common_cleanup.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Red Hat, Inc.
+ */
+
+#include <stdlib.h>
+#include <rte_spinlock.h>
+#include <rte_eal.h>
+
+#include <eal_export.h>
+#include "eal_private.h"
+
+#define MAX_CLEANUP_CALLBACKS 16
+
+static rte_eal_cleanup_callback_t cleanup_callbacks[MAX_CLEANUP_CALLBACKS];
+static int num_cleanup_callbacks;
+static rte_spinlock_t cleanup_lock = RTE_SPINLOCK_INITIALIZER;
+
+RTE_EXPORT_SYMBOL(rte_eal_cleanup_register)
+int
+rte_eal_cleanup_register(rte_eal_cleanup_callback_t callback)
+{
+ int ret = -1;
+
+ if (callback == NULL)
+ return -1;
+
+ rte_spinlock_lock(&cleanup_lock);
+ if (num_cleanup_callbacks < MAX_CLEANUP_CALLBACKS) {
+ cleanup_callbacks[num_cleanup_callbacks++] = callback;
+ ret = 0;
+ }
+ rte_spinlock_unlock(&cleanup_lock);
+
+ return ret;
+}
+
+void
+eal_cleanup_callbacks_run(void)
+{
+ int i;
+
+ rte_spinlock_lock(&cleanup_lock);
+ for (i = 0; i < num_cleanup_callbacks; i++) {
+ if (cleanup_callbacks[i] != NULL)
+ cleanup_callbacks[i]();
+ }
+ rte_spinlock_unlock(&cleanup_lock);
+}
diff --git a/lib/eal/common/eal_private.h b/lib/eal/common/eal_private.h
index e032dd10c9..3443960f13 100644
--- a/lib/eal/common/eal_private.h
+++ b/lib/eal/common/eal_private.h
@@ -479,6 +479,11 @@ struct rte_bus *rte_bus_find_by_device_name(const char *str);
*/
int eal_bus_cleanup(void);
+/**
+ * Call all registered cleanup callbacks.
+ */
+void eal_cleanup_callbacks_run(void);
+
/**
* Create the unix channel for primary/secondary communication.
*
diff --git a/lib/eal/common/meson.build b/lib/eal/common/meson.build
index e273745e93..c36a82c34b 100644
--- a/lib/eal/common/meson.build
+++ b/lib/eal/common/meson.build
@@ -8,6 +8,7 @@ cflags += [ '-DABI_VERSION="@0@"'.format(abi_version) ]
sources += files(
'eal_common_bus.c',
'eal_common_class.c',
+ 'eal_common_cleanup.c',
'eal_common_config.c',
'eal_common_debug.c',
'eal_common_dev.c',
diff --git a/lib/eal/freebsd/eal.c b/lib/eal/freebsd/eal.c
index 6215245ad5..fdb8927b2b 100644
--- a/lib/eal/freebsd/eal.c
+++ b/lib/eal/freebsd/eal.c
@@ -777,6 +777,7 @@ rte_eal_cleanup(void)
struct internal_config *internal_conf =
eal_get_internal_configuration();
+ eal_cleanup_callbacks_run();
rte_service_finalize();
eal_bus_cleanup();
rte_mp_channel_cleanup();
diff --git a/lib/eal/include/rte_eal.h b/lib/eal/include/rte_eal.h
index 7241f3be5d..06d2820010 100644
--- a/lib/eal/include/rte_eal.h
+++ b/lib/eal/include/rte_eal.h
@@ -126,6 +126,21 @@ int rte_eal_init(int argc, char **argv);
*/
int rte_eal_cleanup(void);
+/**
+ * Cleanup callback function type.
+ */
+typedef void (*rte_eal_cleanup_callback_t)(void);
+
+/**
+ * Register a function to be called during rte_eal_cleanup().
+ *
+ * @param callback
+ * The cleanup callback function to register.
+ * @return
+ * 0 on success, -1 on failure.
+ */
+int rte_eal_cleanup_register(rte_eal_cleanup_callback_t callback);
+
/**
* Check if a primary process is currently alive
*
diff --git a/lib/eal/linux/eal.c b/lib/eal/linux/eal.c
index b12f325ddd..ba552f60c8 100644
--- a/lib/eal/linux/eal.c
+++ b/lib/eal/linux/eal.c
@@ -976,6 +976,7 @@ rte_eal_cleanup(void)
internal_conf->hugepage_file.unlink_existing)
rte_memseg_walk(mark_freeable, NULL);
+ eal_cleanup_callbacks_run();
rte_service_finalize();
eal_bus_cleanup();
vfio_mp_sync_cleanup();
diff --git a/lib/eal/windows/eal.c b/lib/eal/windows/eal.c
index 21fe7cb1d9..3ac61bfd2a 100644
--- a/lib/eal/windows/eal.c
+++ b/lib/eal/windows/eal.c
@@ -142,6 +142,7 @@ rte_eal_cleanup(void)
struct internal_config *internal_conf =
eal_get_internal_configuration();
+ eal_cleanup_callbacks_run();
eal_intr_thread_cancel();
eal_mem_virt2iova_cleanup();
eal_bus_cleanup();
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/rte_vhost.h b/lib/vhost/rte_vhost.h
index 2f7c4c0080..31cbbe6039 100644
--- a/lib/vhost/rte_vhost.h
+++ b/lib/vhost/rte_vhost.h
@@ -464,6 +464,21 @@ int rte_vhost_driver_register(const char *path, uint64_t flags);
/* Unregister vhost driver. This is only meaningful to vhost user. */
int rte_vhost_driver_unregister(const char *path);
+/**
+ * Cleanup vhost library resources.
+ *
+ * This function stops the fdset event dispatch threads for both vhost-user
+ * and VDUSE backends. It should be called before rte_eal_cleanup() to ensure
+ * proper shutdown without use-after-free issues.
+ *
+ * The function is safe to call multiple times or even if no vhost driver
+ * was ever started.
+ *
+ * @return
+ * 0 on success
+ */
+int rte_vhost_cleanup(void);
+
/**
* Set the vdpa device id, enforce single connection per socket
*
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index ae95e7e6b0..dc9fd16c62 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1198,7 +1200,7 @@ rte_vhost_driver_start(const char *path)
vsocket->extbuf, vsocket->linearbuf);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
if (vhost_user.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
return -1;
@@ -1210,3 +1212,34 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+static void
+vhost_user_fdset_cleanup(void)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
+
+RTE_EXPORT_SYMBOL(rte_vhost_cleanup)
+int
+rte_vhost_cleanup(void)
+{
+ vhost_user_fdset_cleanup();
+ vduse_fdset_cleanup();
+
+ return 0;
+}
+
+static void
+vhost_cleanup_handler(void)
+{
+ rte_vhost_cleanup();
+}
+
+RTE_INIT(vhost_cleanup_register)
+{
+ if (rte_eal_cleanup_register(vhost_cleanup_handler) < 0)
+ RTE_LOG(ERR, VHOST_CONFIG, "Failed to register vhost cleanup\n");
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 0b5d158fee..5c9e77f11e 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -685,7 +686,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
if (vduse.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
return -1;
@@ -942,3 +943,12 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+void
+vduse_fdset_cleanup(void)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
diff --git a/lib/vhost/vduse.h b/lib/vhost/vduse.h
index b2515bb9df..53f16b0238 100644
--- a/lib/vhost/vduse.h
+++ b/lib/vhost/vduse.h
@@ -11,5 +11,6 @@
int vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf);
int vduse_device_destroy(const char *path);
+void vduse_fdset_cleanup(void);
#endif /* _VDUSE_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v9] vhost: fix use-after-free in fdset during shutdown
2026-02-05 18:30 ` [PATCH v8] " Yehor Malikov
@ 2026-02-05 18:35 ` Yehor Malikov
2026-02-16 10:17 ` Yehor Malikov
2026-02-17 14:31 ` David Marchand
0 siblings, 2 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-05 18:35 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() releases resources (like hugepages via
rte_eal_memory_detach) while the fdset thread is still running.
This race condition can lead to use-after-free errors if the thread
accesses memory that has been freed.
Standard destructors (RTE_FINI) run after rte_eal_cleanup() returns,
which is too late to prevent this race.
To address this, introduce a mechanism to register cleanup callbacks
that run within rte_eal_cleanup() before memory is detached:
1. Add rte_eal_cleanup_register() API to EAL.
2. Implement fdset_deinit() in vhost to synchronously stop the
dispatch thread, close the epoll fd, and release resources.
3. Register the vhost cleanup handler during initialization to
ensure proper shutdown ordering via EAL.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/eal/common/eal_common_cleanup.c | 48 +++++++++++++++++++++++++++++
lib/eal/common/eal_private.h | 5 +++
lib/eal/common/meson.build | 1 +
lib/eal/freebsd/eal.c | 1 +
lib/eal/include/rte_eal.h | 15 +++++++++
lib/eal/linux/eal.c | 1 +
lib/eal/windows/eal.c | 1 +
lib/vhost/fd_man.c | 16 ++++++++++
lib/vhost/fd_man.h | 1 +
lib/vhost/rte_vhost.h | 15 +++++++++
lib/vhost/socket.c | 35 ++++++++++++++++++++-
lib/vhost/vduse.c | 12 +++++++-
lib/vhost/vduse.h | 1 +
14 files changed, 151 insertions(+), 2 deletions(-)
create mode 100644 lib/eal/common/eal_common_cleanup.c
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..711a6ceff5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1840,6 +1840,7 @@ Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
Yelena Krivosheev <yelena@marvell.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
Yi Chen <chenyi221@huawei.com>
diff --git a/lib/eal/common/eal_common_cleanup.c b/lib/eal/common/eal_common_cleanup.c
new file mode 100644
index 0000000000..933484e254
--- /dev/null
+++ b/lib/eal/common/eal_common_cleanup.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Red Hat, Inc.
+ */
+
+#include <stdlib.h>
+#include <rte_spinlock.h>
+#include <rte_eal.h>
+
+#include <eal_export.h>
+#include "eal_private.h"
+
+#define MAX_CLEANUP_CALLBACKS 16
+
+static rte_eal_cleanup_callback_t cleanup_callbacks[MAX_CLEANUP_CALLBACKS];
+static int num_cleanup_callbacks;
+static rte_spinlock_t cleanup_lock = RTE_SPINLOCK_INITIALIZER;
+
+RTE_EXPORT_SYMBOL(rte_eal_cleanup_register)
+int
+rte_eal_cleanup_register(rte_eal_cleanup_callback_t callback)
+{
+ int ret = -1;
+
+ if (callback == NULL)
+ return -1;
+
+ rte_spinlock_lock(&cleanup_lock);
+ if (num_cleanup_callbacks < MAX_CLEANUP_CALLBACKS) {
+ cleanup_callbacks[num_cleanup_callbacks++] = callback;
+ ret = 0;
+ }
+ rte_spinlock_unlock(&cleanup_lock);
+
+ return ret;
+}
+
+void
+eal_cleanup_callbacks_run(void)
+{
+ int i;
+
+ rte_spinlock_lock(&cleanup_lock);
+ for (i = 0; i < num_cleanup_callbacks; i++) {
+ if (cleanup_callbacks[i] != NULL)
+ cleanup_callbacks[i]();
+ }
+ rte_spinlock_unlock(&cleanup_lock);
+}
diff --git a/lib/eal/common/eal_private.h b/lib/eal/common/eal_private.h
index e032dd10c9..3443960f13 100644
--- a/lib/eal/common/eal_private.h
+++ b/lib/eal/common/eal_private.h
@@ -479,6 +479,11 @@ struct rte_bus *rte_bus_find_by_device_name(const char *str);
*/
int eal_bus_cleanup(void);
+/**
+ * Call all registered cleanup callbacks.
+ */
+void eal_cleanup_callbacks_run(void);
+
/**
* Create the unix channel for primary/secondary communication.
*
diff --git a/lib/eal/common/meson.build b/lib/eal/common/meson.build
index e273745e93..c36a82c34b 100644
--- a/lib/eal/common/meson.build
+++ b/lib/eal/common/meson.build
@@ -8,6 +8,7 @@ cflags += [ '-DABI_VERSION="@0@"'.format(abi_version) ]
sources += files(
'eal_common_bus.c',
'eal_common_class.c',
+ 'eal_common_cleanup.c',
'eal_common_config.c',
'eal_common_debug.c',
'eal_common_dev.c',
diff --git a/lib/eal/freebsd/eal.c b/lib/eal/freebsd/eal.c
index 6215245ad5..fdb8927b2b 100644
--- a/lib/eal/freebsd/eal.c
+++ b/lib/eal/freebsd/eal.c
@@ -777,6 +777,7 @@ rte_eal_cleanup(void)
struct internal_config *internal_conf =
eal_get_internal_configuration();
+ eal_cleanup_callbacks_run();
rte_service_finalize();
eal_bus_cleanup();
rte_mp_channel_cleanup();
diff --git a/lib/eal/include/rte_eal.h b/lib/eal/include/rte_eal.h
index 7241f3be5d..06d2820010 100644
--- a/lib/eal/include/rte_eal.h
+++ b/lib/eal/include/rte_eal.h
@@ -126,6 +126,21 @@ int rte_eal_init(int argc, char **argv);
*/
int rte_eal_cleanup(void);
+/**
+ * Cleanup callback function type.
+ */
+typedef void (*rte_eal_cleanup_callback_t)(void);
+
+/**
+ * Register a function to be called during rte_eal_cleanup().
+ *
+ * @param callback
+ * The cleanup callback function to register.
+ * @return
+ * 0 on success, -1 on failure.
+ */
+int rte_eal_cleanup_register(rte_eal_cleanup_callback_t callback);
+
/**
* Check if a primary process is currently alive
*
diff --git a/lib/eal/linux/eal.c b/lib/eal/linux/eal.c
index b12f325ddd..ba552f60c8 100644
--- a/lib/eal/linux/eal.c
+++ b/lib/eal/linux/eal.c
@@ -976,6 +976,7 @@ rte_eal_cleanup(void)
internal_conf->hugepage_file.unlink_existing)
rte_memseg_walk(mark_freeable, NULL);
+ eal_cleanup_callbacks_run();
rte_service_finalize();
eal_bus_cleanup();
vfio_mp_sync_cleanup();
diff --git a/lib/eal/windows/eal.c b/lib/eal/windows/eal.c
index 21fe7cb1d9..3ac61bfd2a 100644
--- a/lib/eal/windows/eal.c
+++ b/lib/eal/windows/eal.c
@@ -142,6 +142,7 @@ rte_eal_cleanup(void)
struct internal_config *internal_conf =
eal_get_internal_configuration();
+ eal_cleanup_callbacks_run();
eal_intr_thread_cancel();
eal_mem_virt2iova_cleanup();
eal_bus_cleanup();
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..9790c8a586 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -149,6 +149,22 @@ fdset_init(const char *name)
return NULL;
}
+void
+fdset_deinit(struct fdset *pfdset)
+{
+ unsigned int val;
+
+ if (pfdset == NULL)
+ return;
+
+ /* Signal the dispatch thread to stop */
+ pfdset->destroy = true;
+
+ /* Wait for the dispatch thread to exit */
+ if (rte_thread_join(pfdset->tid, &val) != 0)
+ VHOST_FDMAN_LOG(ERR, "Failed to join %s event dispatch thread", pfdset->name);
+}
+
static int
fdset_insert_entry(struct fdset *pfdset, int fd, fd_cb rcb, fd_cb wcb, void *dat)
{
diff --git a/lib/vhost/fd_man.h b/lib/vhost/fd_man.h
index eadcc6fb42..c9e51badaa 100644
--- a/lib/vhost/fd_man.h
+++ b/lib/vhost/fd_man.h
@@ -15,6 +15,7 @@ struct fdset;
typedef void (*fd_cb)(int fd, void *dat, int *close);
struct fdset *fdset_init(const char *name);
+void fdset_deinit(struct fdset *pfdset);
int fdset_add(struct fdset *pfdset, int fd,
fd_cb rcb, fd_cb wcb, void *dat);
diff --git a/lib/vhost/rte_vhost.h b/lib/vhost/rte_vhost.h
index 2f7c4c0080..31cbbe6039 100644
--- a/lib/vhost/rte_vhost.h
+++ b/lib/vhost/rte_vhost.h
@@ -464,6 +464,21 @@ int rte_vhost_driver_register(const char *path, uint64_t flags);
/* Unregister vhost driver. This is only meaningful to vhost user. */
int rte_vhost_driver_unregister(const char *path);
+/**
+ * Cleanup vhost library resources.
+ *
+ * This function stops the fdset event dispatch threads for both vhost-user
+ * and VDUSE backends. It should be called before rte_eal_cleanup() to ensure
+ * proper shutdown without use-after-free issues.
+ *
+ * The function is safe to call multiple times or even if no vhost driver
+ * was ever started.
+ *
+ * @return
+ * 0 on success
+ */
+int rte_vhost_cleanup(void);
+
/**
* Set the vdpa device id, enforce single connection per socket
*
diff --git a/lib/vhost/socket.c b/lib/vhost/socket.c
index ae95e7e6b0..78d177e24b 100644
--- a/lib/vhost/socket.c
+++ b/lib/vhost/socket.c
@@ -76,6 +76,8 @@ struct vhost_user_connection {
};
#define MAX_VHOST_SOCKET 1024
+#define VHOST_USER_FDSET_NAME "vhost-evt"
+
struct vhost_user {
struct vhost_user_socket *vsockets[MAX_VHOST_SOCKET];
struct fdset *fdset;
@@ -1198,7 +1200,7 @@ rte_vhost_driver_start(const char *path)
vsocket->extbuf, vsocket->linearbuf);
if (vhost_user.fdset == NULL) {
- vhost_user.fdset = fdset_init("vhost-evt");
+ vhost_user.fdset = fdset_init(VHOST_USER_FDSET_NAME);
if (vhost_user.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init Vhost-user fdset");
return -1;
@@ -1210,3 +1212,34 @@ rte_vhost_driver_start(const char *path)
else
return vhost_user_start_client(vsocket);
}
+
+static void
+vhost_user_fdset_cleanup(void)
+{
+ if (vhost_user.fdset != NULL) {
+ fdset_deinit(vhost_user.fdset);
+ vhost_user.fdset = NULL;
+ }
+}
+
+RTE_EXPORT_SYMBOL(rte_vhost_cleanup)
+int
+rte_vhost_cleanup(void)
+{
+ vhost_user_fdset_cleanup();
+ vduse_fdset_cleanup();
+
+ return 0;
+}
+
+static void
+vhost_cleanup_handler(void)
+{
+ rte_vhost_cleanup();
+}
+
+RTE_INIT(vhost_cleanup_register)
+{
+ if (rte_eal_cleanup_register(vhost_cleanup_handler) < 0)
+ RTE_LOG_LINE(ERR, VHOST_CONFIG, "Failed to register vhost cleanup");
+}
diff --git a/lib/vhost/vduse.c b/lib/vhost/vduse.c
index 0b5d158fee..5c9e77f11e 100644
--- a/lib/vhost/vduse.c
+++ b/lib/vhost/vduse.c
@@ -27,6 +27,7 @@
#define VHOST_VDUSE_API_VERSION 0
#define VDUSE_CTRL_PATH "/dev/vduse/control"
+#define VDUSE_FDSET_NAME "vduse-evt"
struct vduse {
struct fdset *fdset;
@@ -685,7 +686,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
bool reconnect = false;
if (vduse.fdset == NULL) {
- vduse.fdset = fdset_init("vduse-evt");
+ vduse.fdset = fdset_init(VDUSE_FDSET_NAME);
if (vduse.fdset == NULL) {
VHOST_CONFIG_LOG(path, ERR, "failed to init VDUSE fdset");
return -1;
@@ -942,3 +943,12 @@ vduse_device_destroy(const char *path)
return 0;
}
+
+void
+vduse_fdset_cleanup(void)
+{
+ if (vduse.fdset != NULL) {
+ fdset_deinit(vduse.fdset);
+ vduse.fdset = NULL;
+ }
+}
diff --git a/lib/vhost/vduse.h b/lib/vhost/vduse.h
index b2515bb9df..53f16b0238 100644
--- a/lib/vhost/vduse.h
+++ b/lib/vhost/vduse.h
@@ -11,5 +11,6 @@
int vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf);
int vduse_device_destroy(const char *path);
+void vduse_fdset_cleanup(void);
#endif /* _VDUSE_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v9] vhost: fix use-after-free in fdset during shutdown
2026-02-05 18:35 ` [PATCH v9] " Yehor Malikov
@ 2026-02-16 10:17 ` Yehor Malikov
2026-02-17 14:31 ` David Marchand
1 sibling, 0 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-16 10:17 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, chenbox, stephen
Hi everyone,
Just a friendly ping on this patch. Let me know if there is any feedback or if anything else is needed from my side!
Thanks,
Yehor
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v9] vhost: fix use-after-free in fdset during shutdown
2026-02-05 18:35 ` [PATCH v9] " Yehor Malikov
2026-02-16 10:17 ` Yehor Malikov
@ 2026-02-17 14:31 ` David Marchand
2026-02-18 7:50 ` [PATCH v10] " Yehor Malikov
1 sibling, 1 reply; 23+ messages in thread
From: David Marchand @ 2026-02-17 14:31 UTC (permalink / raw)
To: Yehor Malikov
Cc: dev, maxime.coquelin, chenbox, stephen, Yehor Malikov,
Tyler Retzlaff, Andre Muezerie
On Thu, 5 Feb 2026 at 19:35, Yehor Malikov <malikovyehor@gmail.com> wrote:
>
> From: Yehor Malikov <Yehor.Malikov@solidigm.com>
>
> The fdset_event_dispatch thread runs in a loop checking the destroy
> flag after each epoll_wait iteration. During process exit,
> rte_eal_cleanup() releases resources (like hugepages via
> rte_eal_memory_detach) while the fdset thread is still running.
> This race condition can lead to use-after-free errors if the thread
> accesses memory that has been freed.
>
> Standard destructors (RTE_FINI) run after rte_eal_cleanup() returns,
> which is too late to prevent this race.
>
> To address this, introduce a mechanism to register cleanup callbacks
> that run within rte_eal_cleanup() before memory is detached:
> 1. Add rte_eal_cleanup_register() API to EAL.
> 2. Implement fdset_deinit() in vhost to synchronously stop the
> dispatch thread, close the epoll fd, and release resources.
> 3. Register the vhost cleanup handler during initialization to
> ensure proper shutdown ordering via EAL.
As a first step, I suggest switching to libc allocations instead of
DPDK memory for the fdset objects (those are only for control path of
the vhost-user library).
This should avoid the UAF you noticed and restore the situation to
what it was before the vhost library change you pointed out.
Now, on this proposal for an EAL change, the fixed size for the
callbacks array makes little sense to me: in the case of fdset objects
in vhost, we only have two atm but this could increase.
On the other hand, this is control path stuff, not performance sensitive.
We can do some allocations and go with a list of callbacks.
The cleanup callbacks as you propose are really vague.
The problem here is linked to control thread objects, so maybe we
could add this cleanup notion in the control thread creation itself.
This notion has been skipped since the introduction of control
threads, maybe it is worth investigating?
The semantics must be sorted out, but it seems cleaner to me.
--
David Marchand
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v10] vhost: fix use-after-free in fdset during shutdown
2026-02-17 14:31 ` David Marchand
@ 2026-02-18 7:50 ` Yehor Malikov
2026-02-18 8:01 ` [PATCH v11] " Yehor Malikov
0 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-18 7:50 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, david.marchand, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepage memory while the fdset thread is
still running. Since the fdset structure was allocated with
rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
causes use-after-free.
Switch fdset allocation from rte_zmalloc/rte_free to libc
calloc/free. The fdset is a control-path structure that does not
need hugepage memory. Using libc allocation ensures the fdset
remains valid after rte_eal_cleanup() releases hugepages.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
lib/vhost/fd_man.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..fae6d787b6 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -8,9 +8,10 @@
#include <sys/epoll.h>
#include <unistd.h>
+#include <stdlib.h>
+
#include <rte_common.h>
#include <rte_log.h>
-#include <rte_malloc.h>
#include <rte_string_fns.h>
#include <rte_thread.h>
@@ -94,7 +95,7 @@ fdset_init(const char *name)
return fdset;
}
- fdset = rte_zmalloc(NULL, sizeof(*fdset), 0);
+ fdset = calloc(1, sizeof(*fdset));
if (!fdset) {
VHOST_FDMAN_LOG(ERR, "failed to alloc fdset %s", name);
goto err_unlock;
@@ -142,7 +143,7 @@ fdset_init(const char *name)
err_epoll:
close(fdset->epfd);
err_free:
- rte_free(fdset);
+ free(fdset);
err_unlock:
pthread_mutex_unlock(&fdsets_mutex);
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v11] vhost: fix use-after-free in fdset during shutdown
2026-02-18 7:50 ` [PATCH v10] " Yehor Malikov
@ 2026-02-18 8:01 ` Yehor Malikov
2026-02-18 8:52 ` David Marchand
0 siblings, 1 reply; 23+ messages in thread
From: Yehor Malikov @ 2026-02-18 8:01 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, david.marchand, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepage memory while the fdset thread is
still running. Since the fdset structure was allocated with
rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
causes use-after-free.
Switch fdset allocation from rte_zmalloc/rte_free to libc
calloc/free. The fdset is a control-path structure that does not
need hugepage memory. Using libc allocation ensures the fdset
remains valid after rte_eal_cleanup() releases hugepages.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 7 ++++---
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..711a6ceff5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1840,6 +1840,7 @@ Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
Yelena Krivosheev <yelena@marvell.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
Yi Chen <chenyi221@huawei.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..fae6d787b6 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -8,9 +8,10 @@
#include <sys/epoll.h>
#include <unistd.h>
+#include <stdlib.h>
+
#include <rte_common.h>
#include <rte_log.h>
-#include <rte_malloc.h>
#include <rte_string_fns.h>
#include <rte_thread.h>
@@ -94,7 +95,7 @@ fdset_init(const char *name)
return fdset;
}
- fdset = rte_zmalloc(NULL, sizeof(*fdset), 0);
+ fdset = calloc(1, sizeof(*fdset));
if (!fdset) {
VHOST_FDMAN_LOG(ERR, "failed to alloc fdset %s", name);
goto err_unlock;
@@ -142,7 +143,7 @@ fdset_init(const char *name)
err_epoll:
close(fdset->epfd);
err_free:
- rte_free(fdset);
+ free(fdset);
err_unlock:
pthread_mutex_unlock(&fdsets_mutex);
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v11] vhost: fix use-after-free in fdset during shutdown
2026-02-18 8:01 ` [PATCH v11] " Yehor Malikov
@ 2026-02-18 8:52 ` David Marchand
2026-02-18 9:05 ` [PATCH v12] " Yehor Malikov
0 siblings, 1 reply; 23+ messages in thread
From: David Marchand @ 2026-02-18 8:52 UTC (permalink / raw)
To: Yehor Malikov; +Cc: dev, maxime.coquelin, chenbox, stephen, Yehor Malikov
Hello,
On Wed, 18 Feb 2026 at 09:02, Yehor Malikov <malikovyehor@gmail.com> wrote:
>
> From: Yehor Malikov <Yehor.Malikov@solidigm.com>
>
> The fdset_event_dispatch thread runs in a loop checking the destroy
> flag after each epoll_wait iteration. During process exit,
> rte_eal_cleanup() frees hugepage memory while the fdset thread is
> still running. Since the fdset structure was allocated with
> rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
> causes use-after-free.
>
> Switch fdset allocation from rte_zmalloc/rte_free to libc
> calloc/free. The fdset is a control-path structure that does not
> need hugepage memory. Using libc allocation ensures the fdset
> remains valid after rte_eal_cleanup() releases hugepages.
>
> Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
>
> Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
On the principle, the fix lgtm and deserves being backported.
The commit e68a6feaa3b3 ("vhost: improve fdset initialization") has
been there since v24.07.
Is this something that you hit easily?
> diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
> index f9147edee7..fae6d787b6 100644
> --- a/lib/vhost/fd_man.c
> +++ b/lib/vhost/fd_man.c
> @@ -8,9 +8,10 @@
> #include <sys/epoll.h>
> #include <unistd.h>
>
> +#include <stdlib.h>
> +
Nit: this can be fixed when applying, stdlib.h header include can go
in the first block with other libc includes.
--
David Marchand
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v12] vhost: fix use-after-free in fdset during shutdown
2026-02-18 8:52 ` David Marchand
@ 2026-02-18 9:05 ` Yehor Malikov
2026-02-18 10:27 ` David Marchand
2026-03-05 10:50 ` Maxime Coquelin
0 siblings, 2 replies; 23+ messages in thread
From: Yehor Malikov @ 2026-02-18 9:05 UTC (permalink / raw)
To: dev; +Cc: maxime.coquelin, david.marchand, chenbox, stephen, Yehor Malikov
From: Yehor Malikov <Yehor.Malikov@solidigm.com>
The fdset_event_dispatch thread runs in a loop checking the destroy
flag after each epoll_wait iteration. During process exit,
rte_eal_cleanup() frees hugepage memory while the fdset thread is
still running. Since the fdset structure was allocated with
rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
causes use-after-free.
Switch fdset allocation from rte_zmalloc/rte_free to libc
calloc/free. The fdset is a control-path structure that does not
need hugepage memory. Using libc allocation ensures the fdset
remains valid after rte_eal_cleanup() releases hugepages.
Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
---
.mailmap | 1 +
lib/vhost/fd_man.c | 6 +++---
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/.mailmap b/.mailmap
index fc53ed2a55..711a6ceff5 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1840,6 +1840,7 @@ Yaroslav Brustinov <ybrustin@cisco.com>
Yash Sharma <ysharma@marvell.com>
Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp> <yasufum.o@gmail.com>
Yelena Krivosheev <yelena@marvell.com>
+Yehor Malikov <Yehor.Malikov@solidigm.com>
Yerden Zhumabekov <e_zhumabekov@sts.kz> <yerden.zhumabekov@sts.kz>
Yevgeny Kliteynik <kliteyn@nvidia.com>
Yi Chen <chenyi221@huawei.com>
diff --git a/lib/vhost/fd_man.c b/lib/vhost/fd_man.c
index f9147edee7..5748bc31e2 100644
--- a/lib/vhost/fd_man.c
+++ b/lib/vhost/fd_man.c
@@ -4,13 +4,13 @@
#include <errno.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <rte_common.h>
#include <rte_log.h>
-#include <rte_malloc.h>
#include <rte_string_fns.h>
#include <rte_thread.h>
@@ -94,7 +94,7 @@ fdset_init(const char *name)
return fdset;
}
- fdset = rte_zmalloc(NULL, sizeof(*fdset), 0);
+ fdset = calloc(1, sizeof(*fdset));
if (!fdset) {
VHOST_FDMAN_LOG(ERR, "failed to alloc fdset %s", name);
goto err_unlock;
@@ -142,7 +142,7 @@ fdset_init(const char *name)
err_epoll:
close(fdset->epfd);
err_free:
- rte_free(fdset);
+ free(fdset);
err_unlock:
pthread_mutex_unlock(&fdsets_mutex);
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v12] vhost: fix use-after-free in fdset during shutdown
2026-02-18 9:05 ` [PATCH v12] " Yehor Malikov
@ 2026-02-18 10:27 ` David Marchand
2026-02-27 9:00 ` fengchengwen
2026-03-05 10:50 ` Maxime Coquelin
1 sibling, 1 reply; 23+ messages in thread
From: David Marchand @ 2026-02-18 10:27 UTC (permalink / raw)
To: Yehor Malikov; +Cc: dev, maxime.coquelin, chenbox, stephen, Yehor Malikov
On Wed, 18 Feb 2026 at 10:05, Yehor Malikov <malikovyehor@gmail.com> wrote:
>
> From: Yehor Malikov <Yehor.Malikov@solidigm.com>
>
> The fdset_event_dispatch thread runs in a loop checking the destroy
> flag after each epoll_wait iteration. During process exit,
> rte_eal_cleanup() frees hugepage memory while the fdset thread is
> still running. Since the fdset structure was allocated with
> rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
> causes use-after-free.
>
> Switch fdset allocation from rte_zmalloc/rte_free to libc
> calloc/free. The fdset is a control-path structure that does not
> need hugepage memory. Using libc allocation ensures the fdset
> remains valid after rte_eal_cleanup() releases hugepages.
>
> Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
Cc: stable@dpdk.org
>
> Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
Thanks for the fix.
Acked-by: David Marchand <david.marchand@redhat.com>
--
David Marchand
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v12] vhost: fix use-after-free in fdset during shutdown
2026-02-18 10:27 ` David Marchand
@ 2026-02-27 9:00 ` fengchengwen
0 siblings, 0 replies; 23+ messages in thread
From: fengchengwen @ 2026-02-27 9:00 UTC (permalink / raw)
To: David Marchand, Yehor Malikov
Cc: dev, maxime.coquelin, chenbox, stephen, Yehor Malikov
Acked-by: Chengwen Feng <fengchengwen@huawei.com>
On 2/18/2026 6:27 PM, David Marchand wrote:
> On Wed, 18 Feb 2026 at 10:05, Yehor Malikov <malikovyehor@gmail.com> wrote:
>>
>> From: Yehor Malikov <Yehor.Malikov@solidigm.com>
>>
>> The fdset_event_dispatch thread runs in a loop checking the destroy
>> flag after each epoll_wait iteration. During process exit,
>> rte_eal_cleanup() frees hugepage memory while the fdset thread is
>> still running. Since the fdset structure was allocated with
>> rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
>> causes use-after-free.
>>
>> Switch fdset allocation from rte_zmalloc/rte_free to libc
>> calloc/free. The fdset is a control-path structure that does not
>> need hugepage memory. Using libc allocation ensures the fdset
>> remains valid after rte_eal_cleanup() releases hugepages.
>>
>> Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
> Cc: stable@dpdk.org
>
>>
>> Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
>
> Thanks for the fix.
>
> Acked-by: David Marchand <david.marchand@redhat.com>
>
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v12] vhost: fix use-after-free in fdset during shutdown
2026-02-18 9:05 ` [PATCH v12] " Yehor Malikov
2026-02-18 10:27 ` David Marchand
@ 2026-03-05 10:50 ` Maxime Coquelin
2026-03-05 13:52 ` Maxime Coquelin
1 sibling, 1 reply; 23+ messages in thread
From: Maxime Coquelin @ 2026-03-05 10:50 UTC (permalink / raw)
To: Yehor Malikov; +Cc: dev, david.marchand, chenbox, stephen, Yehor Malikov
On Wed, Feb 18, 2026 at 10:05 AM Yehor Malikov <malikovyehor@gmail.com> wrote:
>
> From: Yehor Malikov <Yehor.Malikov@solidigm.com>
>
> The fdset_event_dispatch thread runs in a loop checking the destroy
> flag after each epoll_wait iteration. During process exit,
> rte_eal_cleanup() frees hugepage memory while the fdset thread is
> still running. Since the fdset structure was allocated with
> rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
> causes use-after-free.
>
> Switch fdset allocation from rte_zmalloc/rte_free to libc
> calloc/free. The fdset is a control-path structure that does not
> need hugepage memory. Using libc allocation ensures the fdset
> remains valid after rte_eal_cleanup() releases hugepages.
>
> Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
>
> Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
> ---
> .mailmap | 1 +
> lib/vhost/fd_man.c | 6 +++---
> 2 files changed, 4 insertions(+), 3 deletions(-)
>
Reviewed-by: Maxime Coquelin <maxime.coquelin@redhat.com>
Thanks,
Maxime
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v12] vhost: fix use-after-free in fdset during shutdown
2026-03-05 10:50 ` Maxime Coquelin
@ 2026-03-05 13:52 ` Maxime Coquelin
0 siblings, 0 replies; 23+ messages in thread
From: Maxime Coquelin @ 2026-03-05 13:52 UTC (permalink / raw)
To: Yehor Malikov; +Cc: dev, david.marchand, chenbox, stephen, Yehor Malikov
Applied to next-virtio/for-next-net.
Please note that I had to solve a trivial build issue due another
other patch from Shani I applied just before.
Feel free to check my branch to confirm this is OK to you.
Thanks,
Maxime
On Thu, Mar 5, 2026 at 11:50 AM Maxime Coquelin
<maxime.coquelin@redhat.com> wrote:
>
> On Wed, Feb 18, 2026 at 10:05 AM Yehor Malikov <malikovyehor@gmail.com> wrote:
> >
> > From: Yehor Malikov <Yehor.Malikov@solidigm.com>
> >
> > The fdset_event_dispatch thread runs in a loop checking the destroy
> > flag after each epoll_wait iteration. During process exit,
> > rte_eal_cleanup() frees hugepage memory while the fdset thread is
> > still running. Since the fdset structure was allocated with
> > rte_zmalloc() (hugepage-backed), accessing it after rte_eal_cleanup()
> > causes use-after-free.
> >
> > Switch fdset allocation from rte_zmalloc/rte_free to libc
> > calloc/free. The fdset is a control-path structure that does not
> > need hugepage memory. Using libc allocation ensures the fdset
> > remains valid after rte_eal_cleanup() releases hugepages.
> >
> > Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization")
> >
> > Signed-off-by: Yehor Malikov <Yehor.Malikov@solidigm.com>
> > ---
> > .mailmap | 1 +
> > lib/vhost/fd_man.c | 6 +++---
> > 2 files changed, 4 insertions(+), 3 deletions(-)
> >
>
> Reviewed-by: Maxime Coquelin <maxime.coquelin@redhat.com>
>
> Thanks,
> Maxime
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-03-05 13:53 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-04 18:03 [PATCH] vhost: fix use-after-free in fdset during shutdown Yehor Malikov
2026-02-04 18:48 ` [PATCH v2] " malikovyehor
2026-02-04 18:58 ` [PATCH v3] " Yehor Malikov
2026-02-04 20:34 ` Stephen Hemminger
2026-02-04 21:32 ` Yehor Malikov
2026-02-04 21:35 ` [PATCH v4] " Yehor Malikov
2026-02-04 22:00 ` [PATCH v5] " Yehor Malikov
2026-02-04 23:05 ` [PATCH v6] " Yehor Malikov
2026-02-05 1:17 ` Stephen Hemminger
2026-02-05 11:16 ` [PATCH v7] " Yehor Malikov
2026-02-05 11:20 ` Yehor Malikov
2026-02-05 18:30 ` [PATCH v8] " Yehor Malikov
2026-02-05 18:35 ` [PATCH v9] " Yehor Malikov
2026-02-16 10:17 ` Yehor Malikov
2026-02-17 14:31 ` David Marchand
2026-02-18 7:50 ` [PATCH v10] " Yehor Malikov
2026-02-18 8:01 ` [PATCH v11] " Yehor Malikov
2026-02-18 8:52 ` David Marchand
2026-02-18 9:05 ` [PATCH v12] " Yehor Malikov
2026-02-18 10:27 ` David Marchand
2026-02-27 9:00 ` fengchengwen
2026-03-05 10:50 ` Maxime Coquelin
2026-03-05 13:52 ` Maxime Coquelin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox