vmm: device_manager: reject duplicate socket in add_user_device

Calling vm.add-user-device a second time with a socket path already
in use makes the VMM thread block indefinitely inside
vfio_user::Client::new(). libvfio-user servers (SPDK, the reference
libvfio-user daemon) accept a single active client per socket, so
the second connect(2) succeeds at the OS level but the handshake
recvmsg(2) waits for a response that never arrives.

All subsequent API requests queue behind the stuck VMM event loop
and also hang (vm.info, vmm.ping, vm.remove-device). The VM itself
keeps running on vcpu threads, making the symptom confusing: the
guest looks healthy, only the API is unreachable.

This is easy to hit from management software that uses an idempotent
reconcile / ensure pattern for user devices.

Reject the call up-front when another user_device already has the
same socket path, returning an HTTP 500 with a descriptive
UserDeviceSocketInUse error in milliseconds instead of hanging.

Signed-off-by: Max Makarov <maxpain@linux.com>
This commit is contained in:
Max Makarov
2026-04-16 21:05:27 +00:00
committed by Rob Bradford
parent e2c51042d3
commit cdfedfaab2

View File

@@ -657,6 +657,10 @@ pub enum DeviceManagerError {
#[error("Invalid identifier: {0}")]
InvalidIdentifier(String),
/// vfio-user socket path already in use by another user device.
#[error("vfio-user socket path already in use: {0:?}")]
UserDeviceSocketInUse(std::path::PathBuf),
/// Error activating virtio device
#[error("Error activating virtio device")]
VirtioActivate(#[source] ActivateError),
@@ -4707,6 +4711,17 @@ impl DeviceManager {
) -> DeviceManagerResult<PciDeviceInfo> {
self.validate_identifier(&device_cfg.pci_common.id)?;
// Reject duplicate socket up-front: libvfio-user servers accept a
// single client, so a second Client::new() on the same socket blocks
// indefinitely in the handshake recvmsg() and hangs the VMM thread.
if let Some(existing) = &self.config.lock().unwrap().user_devices
&& existing.iter().any(|d| d.socket == device_cfg.socket)
{
return Err(DeviceManagerError::UserDeviceSocketInUse(
device_cfg.socket.clone(),
));
}
let (bdf, device_name) = self.add_vfio_user_device(device_cfg)?;
// Update the PCIU bitmap