From 585101964de4dadde0c3bb0192d2aacc23c508d1 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 28 Jan 2026 02:59:48 +0000 Subject: [PATCH] fix(vmm): Forbid host API listening on non-vsock addresses The host API is designed for CVM-to-host communication via vsock only. When users accidentally configure TCP or Unix socket addresses, it silently falls back without proper error, making troubleshooting difficult. Changes: - Add validation in HostApiConfig to ensure address starts with 'vsock:' - Validate config at startup and fail fast with clear error message - Remove TCP fallback code from run_host_api since only vsock is supported Fixes #417 --- vmm/src/config.rs | 16 ++++++++++++++++ vmm/src/main.rs | 30 +++++++++++++----------------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 8a593fc0..302a7bc9 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -409,6 +409,22 @@ pub struct HostApiConfig { pub port: u32, } +impl HostApiConfig { + /// Validate that the host API address is a vsock address. + /// The host API must only listen on vsock for security reasons. + /// TCP/Unix socket listening is not supported. + pub fn validate(&self) -> Result<()> { + if !self.address.starts_with("vsock:") { + anyhow::bail!( + "Host API address must be a vsock address (e.g., 'vsock:2'), got: '{}'. \ + TCP/Unix socket listening is not supported for the host API.", + self.address + ); + } + Ok(()) + } +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct KeyProviderConfig { pub enabled: bool, diff --git a/vmm/src/main.rs b/vmm/src/main.rs index bb1f7873..8551fdf3 100644 --- a/vmm/src/main.rs +++ b/vmm/src/main.rs @@ -15,7 +15,6 @@ use path_absolutize::Absolutize; use rocket::{ fairing::AdHoc, figment::{providers::Serialized, Figment}, - listener::{Bind, DefaultListener}, }; use rocket_apitoken::ApiToken; use rocket_vsock_listener::VsockListener; @@ -119,22 +118,13 @@ async fn run_host_api(app: App, figment: Figment) -> Result<()> { .ignite() .await .map_err(|err| anyhow!("Failed to ignite rocket: {err}"))?; - if DefaultListener::bind_endpoint(&ignite).is_ok() { - let listener = DefaultListener::bind(&ignite) - .await - .map_err(|err| anyhow!("Failed to bind host API : {err}"))?; - ignite - .launch_on(listener) - .await - .map_err(|err| anyhow!(err.to_string()))?; - } else { - let listener = VsockListener::bind_rocket(&ignite) - .map_err(|err| anyhow!("Failed to bind host API : {err}"))?; - ignite - .launch_on(listener) - .await - .map_err(|err| anyhow!(err.to_string()))?; - } + // Host API only supports vsock listener (validated at startup) + let listener = VsockListener::bind_rocket(&ignite) + .map_err(|err| anyhow!("Failed to bind host API: {err}"))?; + ignite + .launch_on(listener) + .await + .map_err(|err| anyhow!(err.to_string()))?; Ok(()) } @@ -166,6 +156,12 @@ async fn main() -> Result<()> { let figment = config::load_config_figment(args.config.as_deref()); let config = Config::extract_or_default(&figment)?.abs_path()?; + // Validate host API configuration + config + .host_api + .validate() + .context("Invalid host_api configuration")?; + // Handle commands match args.command.unwrap_or_default() { Command::Run(run_args) => {