vmm: migration seccomp: add plumbing for all migration-related threads

So far, we only have seccomp rules for the postcopy-send thread. This
commit introduces the basic plumbing to add seccomp rules also for the
migration worker (the migration coordinator) as well as the TCP workers
(both, send and receive) in the following.

To streamline code setup, all filters are created at a central place
early in the migration code. Although this means that some filters are
created without the need to do so (e.g., postcopy), this massively
simplifies code setup and error handling. This overhead is negligible.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit is contained in:
Philipp Schuster
2026-06-24 21:34:58 +02:00
committed by Bo Chen
parent e7371984b8
commit b5c028c286
2 changed files with 41 additions and 17 deletions

View File

@@ -65,7 +65,9 @@ use crate::migration::get_vm_snapshot;
use crate::migration::transport::{
self, ReceiveAdditionalConnections, ReceiveListener, SendAdditionalConnections, SocketStream,
};
use crate::migration::worker::{MigrationWorker, MigrationWorkerHandle, MigrationWorkerResult};
use crate::migration::worker::{
MigrationSeccompFilters, MigrationWorker, MigrationWorkerHandle, MigrationWorkerResult,
};
use crate::migration::{recv_vm_config, recv_vm_state};
use crate::seccomp_filters::{Thread, get_seccomp_filter};
use crate::vm::{Error as VmError, Vm, VmState};
@@ -1548,7 +1550,7 @@ impl Vmm {
hypervisor: &dyn hypervisor::Hypervisor,
send_data_migration: &VmSendMigrationData,
initial_vm_state: VmState,
seccomp_action: &SeccompAction,
seccomp_filters: &MigrationSeccompFilters,
) -> result::Result<(), MigratableError> {
// State machine that is updated with more context as we progress.
let mut ctx = OngoingMigrationContext::new();
@@ -1684,15 +1686,17 @@ impl Vmm {
send_data_migration.tls_dir.as_deref(),
)?;
let guest_memory = vm.guest_memory();
// Build the seccomp filter on the parent thread so any failure aborts
// the migration before the serve thread is spawned.
let seccomp_filter =
get_seccomp_filter(seccomp_action, Thread::MigrateSendPostcopy, None)
.context("creating postcopy serve seccomp filter")
.map_err(MigratableError::MigrateSend)?;
let seccomp_filters_clone = seccomp_filters.clone();
let handle = thread::Builder::new()
.name("migrate-send-postcopy".to_owned())
.spawn(move || Self::serve_postcopy(seccomp_filter, fault_stream, guest_memory))
.spawn(move || {
Self::serve_postcopy(
&seccomp_filters_clone.postcopy_server,
fault_stream,
guest_memory,
)
})
.context("spawning postcopy serve thread")
.map_err(MigratableError::MigrateSend)?;
Some(handle)
@@ -1773,7 +1777,7 @@ impl Vmm {
reason = "runs on a dedicated thread and must own its arguments"
)]
fn serve_postcopy(
seccomp_filter: BpfProgram,
seccomp_filter: &BpfProgram,
mut socket: SocketStream,
guest_memory: GuestMemoryAtomic<GuestMemoryMmap>,
) -> result::Result<(), MigratableError> {
@@ -1781,7 +1785,7 @@ impl Vmm {
// seccomp is disabled (SeccompAction::Allow), in which case there is
// nothing to apply.
if !seccomp_filter.is_empty() {
apply_filter(&seccomp_filter)
apply_filter(seccomp_filter)
.context("applying postcopy serve seccomp filter")
.map_err(MigratableError::MigrateSend)?;
}
@@ -3144,6 +3148,21 @@ impl RequestHandler for Vmm {
.with_context(|| "Failed to clone check_migration_evt FD")
.map_err(MigratableError::MigrateSend)?;
// We are creating all seccomp filters beforehand, as:
// - this simplifies the code (especially error propagation)
// - the overhead is negligible
let seccomp_filters = {
let postcopy_server =
get_seccomp_filter(&self.seccomp_action, Thread::MigrateSendPostcopy, None)
.map_err(|e| {
MigratableError::MigrateSend(anyhow!(
"creating postcopy serve seccomp filter: {e}"
))
})?;
MigrationSeccompFilters { postcopy_server }
};
// Take VM ownership. This also means that API events can no longer
// change the VM (e.g. net device hotplug).
let vm = self
@@ -3160,7 +3179,7 @@ impl RequestHandler for Vmm {
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
self.hypervisor.clone(),
initial_vm_state,
self.seccomp_action.clone(),
seccomp_filters,
) {
Ok(handle) => {
self.vm = VmOwnership::Migration {

View File

@@ -21,7 +21,7 @@ use std::{io, thread};
use event_monitor::event;
use log::warn;
use seccompiler::SeccompAction;
use seccompiler::BpfProgram;
use vm_migration::MigratableError;
use vmm_sys_util::eventfd::EventFd;
@@ -68,6 +68,11 @@ impl Drop for MigrationWorkerHandle {
}
}
#[derive(Clone, Debug)]
pub struct MigrationSeccompFilters {
pub postcopy_server: BpfProgram,
}
pub struct MigrationWorker {
// Keep the VM out of the thread closure until spawning succeeds.
vm_receiver: Receiver<Vm>,
@@ -76,7 +81,7 @@ pub struct MigrationWorker {
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
hypervisor: Arc<dyn hypervisor::Hypervisor>,
initial_vm_state: VmState,
seccomp_action: SeccompAction,
seccomp_filters: MigrationSeccompFilters,
}
impl MigrationWorker {
@@ -92,7 +97,7 @@ impl MigrationWorker {
self.hypervisor.as_ref(),
&self.config,
self.initial_vm_state,
&self.seccomp_action,
&self.seccomp_filters,
)
.inspect(|_| event!("vm", "migration-finished"))
.inspect_err(|_| event!("vm", "migration-failed"));
@@ -119,7 +124,7 @@ impl MigrationWorker {
dyn hypervisor::Hypervisor,
>,
initial_vm_state: VmState,
seccomp_action: SeccompAction,
seccomp_filters: MigrationSeccompFilters,
) -> Result<MigrationWorkerHandle, MigrationWorkerSpawnError> {
let (vm_sender, vm_receiver) = mpsc::sync_channel(0);
let worker = MigrationWorker {
@@ -129,7 +134,7 @@ impl MigrationWorker {
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
hypervisor,
initial_vm_state,
seccomp_action,
seccomp_filters,
};
let inner_handle = match thread::Builder::new()