Skip to content

Commit 478bf67

Browse files
authored
vmgs: reprovision marker and guest state command line (#2126)
Adds a reprovisioned marker to the VMGS header to prevent the VMGS from repeatedly being reset when GuestStateLifetime is set to Reprovision. Adds HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT, HCL_GUEST_STATE_LIFETIME, and HCL_STRICT_ENCRYPTION_POLICY so that these behaviors can be controlled on hosts without the required WMI properties. Refactors OPENHCL_DISABLE_UEFI_FRONTPAGE to be treated similarly. Adds Hyper-V tests for default boot and reprovision that are now possible with these changes.
1 parent c066ad2 commit 478bf67

File tree

15 files changed

+330
-156
lines changed

15 files changed

+330
-156
lines changed

openhcl/underhill_core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,10 @@ async fn launch_workers(
328328
nvme_always_flr: opt.nvme_always_flr,
329329
test_configuration: opt.test_configuration,
330330
disable_uefi_frontpage: opt.disable_uefi_frontpage,
331+
default_boot_always_attempt: opt.default_boot_always_attempt,
332+
guest_state_lifetime: opt.guest_state_lifetime,
331333
guest_state_encryption_policy: opt.guest_state_encryption_policy,
334+
strict_encryption_policy: opt.strict_encryption_policy,
332335
attempt_ak_cert_callback: opt.attempt_ak_cert_callback,
333336
enable_vpci_relay: opt.enable_vpci_relay,
334337
};

openhcl/underhill_core/src/loader/mod.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ pub struct Config {
9595
/// A string to append to the current VTL0 command line. Currently only used
9696
/// when booting linux directly.
9797
pub cmdline_append: CString,
98-
/// Disable the UEFI frontpage or not, if loading UEFI.
99-
pub disable_uefi_frontpage: bool,
10098
}
10199

102100
/// Load VTL0 based on measured config. Returns any VP state that should be set.
@@ -133,7 +131,6 @@ pub fn load(
133131
platform_config,
134132
caps,
135133
isolated,
136-
config.disable_uefi_frontpage,
137134
)?;
138135
uefi_info.vp_context.clone()
139136
}
@@ -415,7 +412,6 @@ pub fn write_uefi_config(
415412
platform_config: &DevicePlatformSettings,
416413
caps: &virt::PartitionCapabilities,
417414
isolated: bool,
418-
disable_frontpage: bool,
419415
) -> Result<(), Error> {
420416
use guest_emulation_transport::api::platform_settings::UefiConsoleMode;
421417

@@ -626,7 +622,7 @@ pub fn write_uefi_config(
626622

627623
// Frontpage is disabled if either the host requests it, or the openhcl
628624
// cmdline specifies it.
629-
flags.set_disable_frontpage(disable_frontpage || platform_config.general.disable_frontpage);
625+
flags.set_disable_frontpage(platform_config.general.disable_frontpage);
630626

631627
flags.set_console(match platform_config.general.console_mode {
632628
UefiConsoleMode::Default => config::ConsolePort::Default,

openhcl/underhill_core/src/options.rs

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::collections::BTreeMap;
1212
use std::ffi::OsStr;
1313
use std::ffi::OsString;
1414
use std::path::PathBuf;
15+
use std::str::FromStr;
1516

1617
#[derive(Clone, Debug, MeshPayload)]
1718
pub enum TestScenarioConfig {
@@ -20,7 +21,7 @@ pub enum TestScenarioConfig {
2021
SaveStuck,
2122
}
2223

23-
impl std::str::FromStr for TestScenarioConfig {
24+
impl FromStr for TestScenarioConfig {
2425
type Err = anyhow::Error;
2526

2627
fn from_str(s: &str) -> Result<TestScenarioConfig, anyhow::Error> {
@@ -33,6 +34,28 @@ impl std::str::FromStr for TestScenarioConfig {
3334
}
3435
}
3536

37+
#[derive(Clone, Debug, MeshPayload)]
38+
pub enum GuestStateLifetimeCli {
39+
Default,
40+
ReprovisionOnFailure,
41+
Reprovision,
42+
Ephemeral,
43+
}
44+
45+
impl FromStr for GuestStateLifetimeCli {
46+
type Err = anyhow::Error;
47+
48+
fn from_str(s: &str) -> Result<GuestStateLifetimeCli, anyhow::Error> {
49+
match s {
50+
"DEFAULT" | "0" => Ok(GuestStateLifetimeCli::Default),
51+
"REPROVISION_ON_FAILURE" | "1" => Ok(GuestStateLifetimeCli::ReprovisionOnFailure),
52+
"REPROVISION" | "2" => Ok(GuestStateLifetimeCli::Reprovision),
53+
"EPHEMERAL" | "3" => Ok(GuestStateLifetimeCli::Ephemeral),
54+
_ => Err(anyhow::anyhow!("Invalid lifetime: {}", s)),
55+
}
56+
}
57+
}
58+
3659
#[derive(Clone, Debug, MeshPayload)]
3760
pub enum GuestStateEncryptionPolicyCli {
3861
Auto,
@@ -41,7 +64,7 @@ pub enum GuestStateEncryptionPolicyCli {
4164
GspKey,
4265
}
4366

44-
impl std::str::FromStr for GuestStateEncryptionPolicyCli {
67+
impl FromStr for GuestStateEncryptionPolicyCli {
4568
type Err = anyhow::Error;
4669

4770
fn from_str(s: &str) -> Result<GuestStateEncryptionPolicyCli, anyhow::Error> {
@@ -178,12 +201,23 @@ pub struct Options {
178201
/// (OPENHCL_DISABLE_UEFI_FRONTPAGE=1) Disable the frontpage in UEFI which
179202
/// will result in UEFI terminating, shutting down the guest instead of
180203
/// showing the frontpage.
181-
pub disable_uefi_frontpage: bool,
204+
pub disable_uefi_frontpage: Option<bool>,
205+
206+
/// (HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT=1) Instruct UEFI to always attempt a
207+
/// default boot, even if existing boot entries fail.
208+
pub default_boot_always_attempt: Option<bool>,
209+
210+
/// (HCL_GUEST_STATE_LIFETIME=\<GuestStateLifetimeCli\>)
211+
/// Specify which guest state lifetime to use.
212+
pub guest_state_lifetime: Option<GuestStateLifetimeCli>,
182213

183214
/// (HCL_GUEST_STATE_ENCRYPTION_POLICY=\<GuestStateEncryptionPolicyCli\>)
184215
/// Specify which guest state encryption policy to use.
185216
pub guest_state_encryption_policy: Option<GuestStateEncryptionPolicyCli>,
186217

218+
/// (HCL_STRICT_ENCRYPTION_POLICY=1) Strict guest state encryption policy.
219+
pub strict_encryption_policy: Option<bool>,
220+
187221
/// (HCL_ATTEMPT_AK_CERT_CALLBACK=1) Attempt to renew the AK cert.
188222
/// If not specified, use the configuration in DPSv2 ManagementVtlFeatures.
189223
pub attempt_ak_cert_callback: Option<bool>,
@@ -208,7 +242,7 @@ impl Options {
208242

209243
// Reads an environment variable, falling back to a legacy variable (replacing
210244
// "OPENHCL_" with "UNDERHILL_") if the original is not set.
211-
let legacy_openhcl_env = |name: &str| -> Option<&OsString> {
245+
let read_legacy_openhcl_env = |name: &str| -> Option<&OsString> {
212246
env.get::<OsStr>(name.as_ref()).or_else(|| {
213247
env.get::<OsStr>(
214248
format!(
@@ -221,8 +255,7 @@ impl Options {
221255
};
222256

223257
// Reads an environment variable strings.
224-
let parse_env_string =
225-
|name: &str| -> Option<&OsString> { env.get::<OsStr>(name.as_ref()) };
258+
let read_env = |name: &str| -> Option<&OsString> { env.get::<OsStr>(name.as_ref()) };
226259

227260
fn parse_bool_opt(value: Option<&OsString>) -> anyhow::Result<Option<bool>> {
228261
value
@@ -245,9 +278,14 @@ impl Options {
245278
parse_bool_opt(value).ok().flatten().unwrap_or_default()
246279
}
247280

248-
let parse_legacy_env_bool = |name| parse_bool(legacy_openhcl_env(name));
249-
let parse_env_bool = |name: &str| parse_bool(parse_env_string(name));
250-
let parse_env_bool_opt = |name: &str| parse_bool_opt(parse_env_string(name));
281+
let parse_legacy_env_bool = |name| parse_bool(read_legacy_openhcl_env(name));
282+
let parse_env_bool = |name: &str| parse_bool(read_env(name));
283+
let parse_env_bool_opt = |name: &str| {
284+
parse_bool_opt(read_env(name))
285+
.map_err(|e| tracing::warn!("failed to parse {name}: {e:#}"))
286+
.ok()
287+
.flatten()
288+
};
251289

252290
fn parse_number(value: Option<&OsString>) -> anyhow::Result<Option<u64>> {
253291
value
@@ -260,29 +298,29 @@ impl Options {
260298
}
261299

262300
let parse_legacy_env_number = |name| {
263-
parse_number(legacy_openhcl_env(name))
301+
parse_number(read_legacy_openhcl_env(name))
264302
.context(format!("parsing legacy env number: {name}"))
265303
};
266304

267305
let mut wait_for_start = parse_legacy_env_bool("OPENHCL_WAIT_FOR_START");
268306
let mut reformat_vmgs = parse_legacy_env_bool("OPENHCL_REFORMAT_VMGS");
269-
let mut pid = legacy_openhcl_env("OPENHCL_PID_FILE_PATH")
307+
let mut pid = read_legacy_openhcl_env("OPENHCL_PID_FILE_PATH")
270308
.map(|x| x.to_string_lossy().into_owned().into());
271-
let vmbus_max_version = legacy_openhcl_env("OPENHCL_VMBUS_MAX_VERSION")
309+
let vmbus_max_version = read_legacy_openhcl_env("OPENHCL_VMBUS_MAX_VERSION")
272310
.map(|x| {
273311
vmbus_core::parse_vmbus_version(&(x.to_string_lossy()))
274312
.map_err(|x| anyhow::anyhow!("Error parsing vmbus max version: {}", x))
275313
})
276314
.transpose()?;
277315
let vmbus_enable_mnf =
278-
legacy_openhcl_env("OPENHCL_VMBUS_ENABLE_MNF").map(|v| parse_bool(Some(v)));
316+
read_legacy_openhcl_env("OPENHCL_VMBUS_ENABLE_MNF").map(|v| parse_bool(Some(v)));
279317
let vmbus_force_confidential_external_memory =
280318
parse_env_bool("OPENHCL_VMBUS_FORCE_CONFIDENTIAL_EXTERNAL_MEMORY");
281319
let vmbus_channel_unstick_delay_ms =
282320
parse_legacy_env_number("OPENHCL_VMBUS_CHANNEL_UNSTICK_DELAY_MS")?;
283-
let cmdline_append =
284-
legacy_openhcl_env("OPENHCL_CMDLINE_APPEND").map(|x| x.to_string_lossy().into_owned());
285-
let force_load_vtl0_image = legacy_openhcl_env("OPENHCL_FORCE_LOAD_VTL0_IMAGE")
321+
let cmdline_append = read_legacy_openhcl_env("OPENHCL_CMDLINE_APPEND")
322+
.map(|x| x.to_string_lossy().into_owned());
323+
let force_load_vtl0_image = read_legacy_openhcl_env("OPENHCL_FORCE_LOAD_VTL0_IMAGE")
286324
.map(|x| x.to_string_lossy().into_owned());
287325
let mut vnc_port = parse_legacy_env_number("OPENHCL_VNC_PORT")?.map(|x| x as u32);
288326
let framebuffer_gpa_base = parse_legacy_env_number("OPENHCL_FRAMEBUFFER_GPA_BASE")?;
@@ -299,7 +337,7 @@ impl Options {
299337
let gdbstub_port = parse_legacy_env_number("OPENHCL_GDBSTUB_PORT")?.map(|x| x as u32);
300338
let nvme_keep_alive = parse_env_bool("OPENHCL_NVME_KEEP_ALIVE");
301339
let nvme_always_flr = parse_env_bool("OPENHCL_NVME_ALWAYS_FLR");
302-
let test_configuration = parse_env_string("OPENHCL_TEST_CONFIG").and_then(|x| {
340+
let test_configuration = read_env("OPENHCL_TEST_CONFIG").and_then(|x| {
303341
x.to_string_lossy()
304342
.parse::<TestScenarioConfig>()
305343
.map_err(|e| {
@@ -310,24 +348,27 @@ impl Options {
310348
})
311349
.ok()
312350
});
313-
let disable_uefi_frontpage = parse_env_bool("OPENHCL_DISABLE_UEFI_FRONTPAGE");
351+
let disable_uefi_frontpage = parse_env_bool_opt("OPENHCL_DISABLE_UEFI_FRONTPAGE");
314352
let signal_vtl0_started = parse_env_bool("OPENHCL_SIGNAL_VTL0_STARTED");
315-
let guest_state_encryption_policy = parse_env_string("HCL_GUEST_STATE_ENCRYPTION_POLICY")
316-
.and_then(|x| {
353+
let default_boot_always_attempt = parse_env_bool_opt("HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT");
354+
let guest_state_lifetime = read_env("HCL_GUEST_STATE_LIFETIME").and_then(|x| {
355+
x.to_string_lossy()
356+
.parse::<GuestStateLifetimeCli>()
357+
.map_err(|e| tracing::warn!("failed to parse HCL_GUEST_STATE_LIFETIME: {:#}", e))
358+
.ok()
359+
});
360+
let guest_state_encryption_policy =
361+
read_env("HCL_GUEST_STATE_ENCRYPTION_POLICY").and_then(|x| {
317362
x.to_string_lossy()
318363
.parse::<GuestStateEncryptionPolicyCli>()
319364
.map_err(|e| {
320365
tracing::warn!("failed to parse HCL_GUEST_STATE_ENCRYPTION_POLICY: {:#}", e)
321366
})
322367
.ok()
323368
});
324-
let attempt_ak_cert_callback = parse_env_bool_opt("HCL_ATTEMPT_AK_CERT_CALLBACK")
325-
.map_err(|e| tracing::warn!("failed to parse HCL_ATTEMPT_AK_CERT_CALLBACK: {:#}", e))
326-
.ok()
327-
.flatten();
328-
let enable_vpci_relay = parse_env_bool_opt("OPENHCL_ENABLE_VPCI_RELAY")
329-
.ok()
330-
.flatten();
369+
let strict_encryption_policy = parse_env_bool_opt("HCL_STRICT_ENCRYPTION_POLICY");
370+
let attempt_ak_cert_callback = parse_env_bool_opt("HCL_ATTEMPT_AK_CERT_CALLBACK");
371+
let enable_vpci_relay = parse_env_bool_opt("OPENHCL_ENABLE_VPCI_RELAY");
331372

332373
let mut args = std::env::args().chain(extra_args);
333374
// Skip our own filename.
@@ -385,7 +426,10 @@ impl Options {
385426
nvme_always_flr,
386427
test_configuration,
387428
disable_uefi_frontpage,
429+
default_boot_always_attempt,
430+
guest_state_lifetime,
388431
guest_state_encryption_policy,
432+
strict_encryption_policy,
389433
attempt_ak_cert_callback,
390434
enable_vpci_relay,
391435
})

openhcl/underhill_core/src/worker.rs

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use crate::nvme_manager::manager::NvmeDiskConfig;
4646
use crate::nvme_manager::manager::NvmeDiskResolver;
4747
use crate::nvme_manager::manager::NvmeManager;
4848
use crate::options::GuestStateEncryptionPolicyCli;
49+
use crate::options::GuestStateLifetimeCli;
4950
use crate::options::TestScenarioConfig;
5051
use crate::reference_time::ReferenceTime;
5152
use crate::servicing;
@@ -291,9 +292,15 @@ pub struct UnderhillEnvCfg {
291292
/// test configuration
292293
pub test_configuration: Option<TestScenarioConfig>,
293294
/// Disable the UEFI front page.
294-
pub disable_uefi_frontpage: bool,
295+
pub disable_uefi_frontpage: Option<bool>,
296+
/// Always attempt a default boot
297+
pub default_boot_always_attempt: Option<bool>,
298+
/// Guest state lifetime
299+
pub guest_state_lifetime: Option<GuestStateLifetimeCli>,
295300
/// Guest state encryption policy
296301
pub guest_state_encryption_policy: Option<GuestStateEncryptionPolicyCli>,
302+
/// Strict guest state encryption policy
303+
pub strict_encryption_policy: Option<bool>,
297304
/// Attempt to renew the AK cert
298305
pub attempt_ak_cert_callback: Option<bool>,
299306
/// Enable the VPCI relay
@@ -1218,11 +1225,30 @@ async fn new_underhill_vm(
12181225

12191226
// override dps values with env_cfg values as necessary
12201227
let dps = {
1221-
if let Some(value) = env_cfg.attempt_ak_cert_callback {
1222-
tracing::info!("using HCL_ATTEMPT_AK_CERT_CALLBACK={value} from cmdline");
1223-
dps.general
1224-
.management_vtl_features
1225-
.set_attempt_ak_cert_callback(value);
1228+
if let Some(value) = env_cfg.disable_uefi_frontpage {
1229+
tracing::info!("using OPENHCL_DISABLE_UEFI_FRONTPAGE={value} from cmdline");
1230+
dps.general.disable_frontpage = value;
1231+
}
1232+
1233+
if let Some(value) = env_cfg.default_boot_always_attempt {
1234+
tracing::info!("using HCL_DEFAULT_BOOT_ALWAYS_ATTEMPT={value} from cmdline");
1235+
dps.general.default_boot_always_attempt = value;
1236+
}
1237+
1238+
if let Some(lifetime) = env_cfg.guest_state_lifetime {
1239+
tracing::info!("using HCL_GUEST_STATE_LIFETIME={lifetime:?} from cmdline");
1240+
dps.general.guest_state_lifetime = match lifetime {
1241+
GuestStateLifetimeCli::Default => GuestStateLifetime::Default,
1242+
GuestStateLifetimeCli::ReprovisionOnFailure => {
1243+
GuestStateLifetime::ReprovisionOnFailure
1244+
}
1245+
GuestStateLifetimeCli::Reprovision => GuestStateLifetime::Reprovision,
1246+
GuestStateLifetimeCli::Ephemeral => GuestStateLifetime::Ephemeral,
1247+
};
1248+
}
1249+
1250+
if env_cfg.reformat_vmgs {
1251+
dps.general.guest_state_lifetime = GuestStateLifetime::Reprovision;
12261252
}
12271253

12281254
if let Some(policy) = env_cfg.guest_state_encryption_policy {
@@ -1235,6 +1261,20 @@ async fn new_underhill_vm(
12351261
};
12361262
}
12371263

1264+
if let Some(value) = env_cfg.strict_encryption_policy {
1265+
tracing::info!("using HCL_STRICT_ENCRYPTION_POLICY={value} from cmdline");
1266+
dps.general
1267+
.management_vtl_features
1268+
.set_strict_encryption_policy(value);
1269+
}
1270+
1271+
if let Some(value) = env_cfg.attempt_ak_cert_callback {
1272+
tracing::info!("using HCL_ATTEMPT_AK_CERT_CALLBACK={value} from cmdline");
1273+
dps.general
1274+
.management_vtl_features
1275+
.set_attempt_ak_cert_callback(value);
1276+
}
1277+
12381278
dps
12391279
};
12401280

@@ -1480,13 +1520,11 @@ async fn new_underhill_vm(
14801520
let disk = Disk::new(disk).context("invalid vmgs disk")?;
14811521
let logger = Arc::new(GetVmgsLogger::new(get_client.clone()));
14821522

1483-
let vmgs = if env_cfg.reformat_vmgs
1484-
|| matches!(
1485-
dps.general.guest_state_lifetime,
1486-
GuestStateLifetime::Reprovision
1487-
) {
1488-
tracing::info!(CVM_ALLOWED, "formatting vmgs file on request");
1489-
Vmgs::format_new(disk, Some(logger))
1523+
let vmgs = if matches!(
1524+
dps.general.guest_state_lifetime,
1525+
GuestStateLifetime::Reprovision
1526+
) {
1527+
Vmgs::request_format(disk, Some(logger))
14901528
.instrument(tracing::info_span!("vmgs_format", CVM_ALLOWED))
14911529
.await
14921530
.context("failed to format vmgs")?
@@ -2572,7 +2610,8 @@ async fn new_underhill_vm(
25722610
});
25732611

25742612
if dps.general.tpm_enabled {
2575-
let no_persistent_secrets = dps.general.suppress_attestation.unwrap_or(false);
2613+
let no_persistent_secrets =
2614+
vmgs_client.is_none() || dps.general.suppress_attestation.unwrap_or(false);
25762615
let (ppi_store, nvram_store) = if no_persistent_secrets {
25772616
(
25782617
EphemeralNonVolatileStoreHandle.into_resource(),
@@ -3247,7 +3286,6 @@ async fn new_underhill_vm(
32473286
load_kind,
32483287
&dps,
32493288
isolation.is_isolated(),
3250-
env_cfg.disable_uefi_frontpage,
32513289
)
32523290
.instrument(tracing::info_span!("load_firmware", CVM_ALLOWED))
32533291
.await?;
@@ -3521,16 +3559,12 @@ async fn load_firmware(
35213559
load_kind: LoadKind,
35223560
dps: &DevicePlatformSettings,
35233561
isolated: bool,
3524-
disable_uefi_frontpage: bool,
35253562
) -> Result<(), anyhow::Error> {
35263563
let cmdline_append = match cmdline_append {
35273564
Some(cmdline) => CString::new(cmdline.as_bytes()).context("bad command line")?,
35283565
None => CString::default(),
35293566
};
3530-
let loader_config = crate::loader::Config {
3531-
cmdline_append,
3532-
disable_uefi_frontpage,
3533-
};
3567+
let loader_config = crate::loader::Config { cmdline_append };
35343568
let caps = partition.caps();
35353569
let vtl0_vp_context = crate::loader::load(
35363570
gm,

0 commit comments

Comments
 (0)