vmm: http_api: Accept a VFIO device FD via SCM_RIGHTS

Signed-off-by: Bo Chen <bchen@crusoe.ai>
Assisted-by: Claude:Opus-4.7
This commit is contained in:
Bo Chen
2026-04-23 23:51:45 +00:00
committed by Rob Bradford
parent 0e4b98ae8e
commit d9f89ef2ab

View File

@@ -45,7 +45,7 @@ use crate::api::VmCoredump;
use crate::api::http::http_endpoint::fds_helper::{attach_fds_to_cfg, attach_fds_to_cfgs};
use crate::api::http::{EndpointHandler, HttpError, error_response};
use crate::api::{
AddDisk, ApiAction, ApiError, ApiRequest, NetConfig, VmAddDevice, VmAddFs,
AddDisk, ApiAction, ApiError, ApiRequest, DeviceConfig, NetConfig, VmAddDevice, VmAddFs,
VmAddGenericVhostUser, VmAddNet, VmAddPmem, VmAddUserDevice, VmAddVdpa, VmAddVsock, VmBoot,
VmConfig, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot, VmReceiveMigration,
VmRemoveDevice, VmResize, VmResizeDisk, VmResizeZone, VmRestore, VmResume, VmSendMigration,
@@ -115,10 +115,11 @@ mod fds_helper {
mod config_with_fds_impls {
use std::os::fd::RawFd;
use std::slice::from_ref;
use super::{ConfigWithFDs, ConfigWithVariableFDs};
use crate::config::RestoredNetConfig;
use crate::vm_config::NetConfig;
use crate::vm_config::{DeviceConfig, NetConfig};
impl ConfigWithFDs for NetConfig {
fn id(&self) -> Option<&str> {
@@ -134,6 +135,24 @@ mod fds_helper {
}
}
impl ConfigWithFDs for DeviceConfig {
fn id(&self) -> Option<&str> {
self.pci_common.id.as_deref()
}
fn fds_from_http_body(&self) -> Option<&[RawFd]> {
// A DeviceConfig carries at most one FD.
self.fd.as_ref().map(from_ref)
}
fn set_fds(&mut self, fds: Option<Vec<RawFd>>) {
// The VmAddDevice PutHandler enforces `fds.len() <= 1`
// before calling into this trait, so `pop()` yields the
// single FD when present.
self.fd = fds.and_then(|mut v| v.pop());
}
}
impl ConfigWithFDs for RestoredNetConfig {
fn id(&self) -> Option<&str> {
Some(self.id.as_str())
@@ -295,6 +314,19 @@ impl EndpointHandler for VmCreate {
}
}
if let Some(ref mut devices) = vm_config.devices {
// For the VmCreate call, we do not accept FDs from the socket currently.
// This call sets all FDs to null while doing the same logging as
// similar code paths.
for cfg in devices.iter_mut() {
if let Err(e) =
attach_fds_to_cfg(vec![], cfg).map_err(error_response)
{
return e;
}
}
}
match crate::api::VmCreate
.send(api_notifier, api_sender, vm_config)
.map_err(HttpError::ApiError)
@@ -417,7 +449,6 @@ vm_action_put_handler!(VmResume);
vm_action_put_handler!(VmPowerButton);
vm_action_put_handler!(VmNmi);
vm_action_put_handler_body!(VmAddDevice);
vm_action_put_handler_body!(AddDisk);
vm_action_put_handler_body!(VmAddFs);
vm_action_put_handler_body!(VmAddGenericVhostUser);
@@ -435,6 +466,34 @@ vm_action_put_handler_body!(VmSendMigration);
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
vm_action_put_handler_body!(VmCoredump);
// Special handling for VFIO devices backed by an externally-opened cdev FD.
// See module description for more info.
impl PutHandler for VmAddDevice {
fn handle_request(
&'static self,
api_notifier: EventFd,
api_sender: Sender<ApiRequest>,
body: &Option<Body>,
files: Vec<File>,
) -> Result<Option<Body>, HttpError> {
if let Some(body) = body {
// A DeviceConfig is backed by at most one FD.
if files.len() > 1 {
return Err(HttpError::BadRequest);
}
let mut device_cfg: DeviceConfig = serde_json::from_slice(body.raw())?;
attach_fds_to_cfg(files, &mut device_cfg)?;
self.send(api_notifier, api_sender, device_cfg)
.map_err(HttpError::ApiError)
} else {
Err(HttpError::BadRequest)
}
}
}
impl GetHandler for VmAddDevice {}
// Special handling for virtio-net devices backed by network FDs.
// See module description for more info.
impl PutHandler for VmAddNet {