Files
cloud-hypervisor/virtio-devices/src/thread_helper.rs
Anatol Belski 3402bc0762 virtio-devices: NEEDS_RESET on worker thread Err
Worker threads spawned through spawn_virtio_thread previously wrote to
exit_evt on any clean Err return, taking the whole VMM down on a single
failed device worker. A guest induced fault in any virtio device thus
propagated into a host wide failure.

Route the Err return through the shared mark_device_needs_reset helper
instead. The helper sets the DEVICE_NEEDS_RESET bit on device_status
and triggers a config change interrupt, so the device goes idle and
the guest is informed. The thread exits cleanly without killing the
rest of the VMM.

The panic and the seccomp filter apply paths keep writing to exit_evt.
A panicked worker may have left poisoned locks or partially mutated
state, so a hard exit remains the right policy there.

spawn_virtio_thread now takes the device_status and the interrupt
callback. Every native virtio and vhost-user call site is updated to
pass them in.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2026-05-06 21:45:18 +01:00

73 lines
2.3 KiB
Rust

// Copyright © 2021 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use std::sync::atomic::AtomicU8;
use std::thread::{self, JoinHandle};
use log::error;
use seccompiler::{SeccompAction, apply_filter};
use vmm_sys_util::eventfd::EventFd;
use crate::epoll_helper::EpollHelperError;
use crate::seccomp_filters::{Thread, get_seccomp_filter};
use crate::{ActivateError, VirtioInterrupt, mark_device_needs_reset};
#[allow(clippy::too_many_arguments)]
pub(crate) fn spawn_virtio_thread<F>(
name: &str,
seccomp_action: &SeccompAction,
thread_type: Thread,
epoll_threads: &mut Vec<JoinHandle<()>>,
exit_evt: &EventFd,
device_status: Arc<AtomicU8>,
interrupt_cb: Arc<dyn VirtioInterrupt>,
f: F,
) -> Result<(), ActivateError>
where
F: FnOnce() -> std::result::Result<(), EpollHelperError>,
F: Send + 'static,
{
let seccomp_filter = get_seccomp_filter(seccomp_action, thread_type)
.map_err(ActivateError::CreateSeccompFilter)?;
let thread_exit_evt = exit_evt
.try_clone()
.map_err(ActivateError::CloneExitEventFd)?;
let thread_name = name.to_string();
thread::Builder::new()
.name(name.to_string())
.spawn(move || {
if !seccomp_filter.is_empty()
&& let Err(e) = apply_filter(&seccomp_filter)
{
error!("Error applying seccomp filter: {e:?}");
thread_exit_evt.write(1).ok();
return;
}
match std::panic::catch_unwind(AssertUnwindSafe(f)) {
Err(_) => {
error!("{thread_name} thread panicked");
thread_exit_evt.write(1).ok();
}
Ok(Err(e)) => {
mark_device_needs_reset(
&device_status,
interrupt_cb.as_ref(),
format_args!("{thread_name}: worker exited with error: {e:?}"),
);
}
Ok(Ok(())) => {}
}
})
.map(|thread| epoll_threads.push(thread))
.map_err(|e| {
error!("Failed to spawn thread for {name}: {e}");
ActivateError::ThreadSpawn(e)
})
}