runtime-rs: ch: Add TDX CH features check

If you attempt to create a container (a TD) on a TDX system using a
custom build of Cloud Hypervisor (CH) that was not built with the `tdx`
CH feature, Kata will report the following, somewhat cryptic, CH error:

```
ApiError(VmBoot(InvalidPayload))
```

Newer versions of CH now report their build-time features in the ping
API response message so we now use that, if available, to detect this
scenario and generate a user-friendly error message instead.

This changes improves the readability of `handle_guest_protection()` and
adds a couple of additional tests for that method.

Fixes: #8152.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
James O. D. Hunt 2023-10-12 11:18:07 +01:00
parent 409eadddb2
commit 0e0867f15d
2 changed files with 62 additions and 0 deletions

View File

@ -67,6 +67,12 @@ pub struct CloudHypervisorInner {
// The cloud-hypervisor device-id is later looked up and used while
// removing the device.
pub(crate) device_ids: HashMap<String, String>,
// List of Cloud Hypervisor features enabled at Cloud Hypervisor build-time.
//
// If the version of CH does not provide these details, the value will be
// None.
pub(crate) ch_features: Option<Vec<String>>,
}
const CH_DEFAULT_TIMEOUT_SECS: u32 = 10;
@ -104,6 +110,7 @@ impl CloudHypervisorInner {
shutdown_rx: Some(rx),
tasks: None,
guest_protection_to_use: GuestProtection::NoProtection,
ch_features: None,
}
}

View File

@ -23,6 +23,8 @@ use kata_sys_util::protection::{available_guest_protection, GuestProtection};
use kata_types::capabilities::{Capabilities, CapabilityBits};
use kata_types::config::default::DEFAULT_CH_ROOTFS_TYPE;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::convert::TryFrom;
use std::fs::create_dir_all;
use std::os::unix::net::UnixStream;
@ -42,6 +44,20 @@ const CH_NAME: &str = "cloud-hypervisor";
/// Number of milliseconds to wait before retrying a CH operation.
const CH_POLL_TIME_MS: u64 = 50;
// The name of the CH JSON key for the build-time features list.
const CH_FEATURES_KEY: &str = "features";
// The name of the CH build-time feature for Intel TDX.
const CH_FEATURE_TDX: &str = "tdx";
#[derive(Clone, Deserialize, Serialize)]
pub struct VmmPingResponse {
pub build_version: String,
pub version: String,
pub pid: i64,
pub features: Vec<String>,
}
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum GuestProtectionError {
#[error("guest protection requested but no guest protection available")]
@ -75,6 +91,14 @@ impl CloudHypervisorInner {
.await
.context("hypervisor running check failed")?;
if guest_protection_is_tdx(self.guest_protection_to_use.clone()) {
if let Some(features) = &self.ch_features {
if !features.contains(&CH_FEATURE_TDX.to_string()) {
return Err(anyhow!("Cloud Hypervisor is not built with TDX support"));
}
}
}
self.state = VmmState::VmmServerReady;
Ok(())
@ -424,6 +448,33 @@ impl CloudHypervisorInner {
Ok(())
}
// Check the specified ping API response to see if it contains CH's
// build-time features list. If so, save them.
async fn handle_ch_build_features(&mut self, ping_response: &str) -> Result<()> {
let v: Value = serde_json::from_str(ping_response)?;
let got = &v[CH_FEATURES_KEY];
if got.is_null() {
return Ok(());
}
let features_list = got
.as_array()
.ok_or("expected CH to return array of features")
.map_err(|e| anyhow!(e))?;
let features: Vec<String> = features_list
.iter()
.map(Value::to_string)
.map(|s| s.trim_start_matches('"').trim_end_matches('"').to_string())
.collect();
self.ch_features = Some(features);
Ok(())
}
async fn cloud_hypervisor_ping_until_ready(&mut self, _poll_time_ms: u64) -> Result<()> {
let socket = self
.api_socket
@ -439,7 +490,11 @@ impl CloudHypervisorInner {
if let Ok(response) = response {
if let Some(detail) = response {
// Check for a list of built-in features, returned by this
// API call in newer versions of CH.
debug!(sl!(), "ping response: {:?}", detail);
self.handle_ch_build_features(&detail).await?;
}
break;
}