Skip to content

Commit 59cca16

Browse files
committed
add "secret_free" parameter to /machine-config endpoint
This will later indicate to Firecracker that guest memory should be backed by guest_memfd. Mark vhost-user and async block engine as incompatible, as I/O will require userspace bounce buffers. For vhost-user-blk, we would need to communicate the need for bounce buffers to the backend somehow, and for the async block engine we would need to somehow keep the bounce buffers around until io_uring finishes requests (which is not impossible, but complicated and not needed for now). Signed-off-by: Patrick Roy <roypat@amazon.co.uk>
1 parent c8fd23e commit 59cca16

File tree

9 files changed

+136
-8
lines changed

9 files changed

+136
-8
lines changed

src/firecracker/src/api_server/request/machine_configuration.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ mod tests {
119119
let expected_config = MachineConfigUpdate {
120120
vcpu_count: Some(8),
121121
mem_size_mib: Some(1024),
122+
secret_free: Some(false),
122123
smt: Some(false),
123124
cpu_template: None,
124125
track_dirty_pages: Some(false),
@@ -140,6 +141,7 @@ mod tests {
140141
let expected_config = MachineConfigUpdate {
141142
vcpu_count: Some(8),
142143
mem_size_mib: Some(1024),
144+
secret_free: Some(false),
143145
smt: Some(false),
144146
cpu_template: Some(StaticCpuTemplate::None),
145147
track_dirty_pages: Some(false),
@@ -161,6 +163,7 @@ mod tests {
161163
let expected_config = MachineConfigUpdate {
162164
vcpu_count: Some(8),
163165
mem_size_mib: Some(1024),
166+
secret_free: Some(false),
164167
smt: Some(false),
165168
cpu_template: None,
166169
track_dirty_pages: Some(true),
@@ -186,6 +189,7 @@ mod tests {
186189
let expected_config = MachineConfigUpdate {
187190
vcpu_count: Some(8),
188191
mem_size_mib: Some(1024),
192+
secret_free: Some(false),
189193
smt: Some(false),
190194
cpu_template: Some(StaticCpuTemplate::T2),
191195
track_dirty_pages: Some(true),
@@ -213,6 +217,7 @@ mod tests {
213217
let expected_config = MachineConfigUpdate {
214218
vcpu_count: Some(8),
215219
mem_size_mib: Some(1024),
220+
secret_free: Some(false),
216221
smt: Some(true),
217222
cpu_template: None,
218223
track_dirty_pages: Some(true),

src/firecracker/swagger/firecracker.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,11 @@ definitions:
10441044
mem_size_mib:
10451045
type: integer
10461046
description: Memory size of VM
1047+
secret_free:
1048+
type: boolean
1049+
description:
1050+
If enabled, guest memory will be unmapped from the host kernel's address space, providing additional
1051+
protection against transitive execution issues. All I/O then goes through a bounce buffer.
10471052
track_dirty_pages:
10481053
type: boolean
10491054
description:

src/vmm/src/device_manager/persist.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,7 @@ mod tests {
846846
"machine-config": {{
847847
"vcpu_count": 1,
848848
"mem_size_mib": 128,
849+
"secret_free": false,
849850
"smt": false,
850851
"track_dirty_pages": false,
851852
"huge_pages": "None"

src/vmm/src/persist.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ use crate::{EventManager, Vmm, vstate};
4747
pub struct VmInfo {
4848
/// Guest memory size.
4949
pub mem_size_mib: u64,
50+
/// Memory config
51+
pub secret_free: bool,
5052
/// smt information
5153
pub smt: bool,
5254
/// CPU template type
@@ -61,6 +63,7 @@ impl From<&VmResources> for VmInfo {
6163
fn from(value: &VmResources) -> Self {
6264
Self {
6365
mem_size_mib: value.machine_config.mem_size_mib as u64,
66+
secret_free: value.machine_config.secret_free,
6467
smt: value.machine_config.smt,
6568
cpu_template: StaticCpuTemplate::from(&value.machine_config.cpu_template),
6669
boot_source: value.boot_source.config.clone(),
@@ -360,6 +363,7 @@ pub fn restore_from_snapshot(
360363
.update_machine_config(&MachineConfigUpdate {
361364
vcpu_count: Some(vcpu_count),
362365
mem_size_mib: Some(u64_to_usize(microvm_state.vm_info.mem_size_mib)),
366+
secret_free: Some(microvm_state.vm_info.secret_free),
363367
smt: Some(microvm_state.vm_info.smt),
364368
cpu_template: Some(microvm_state.vm_info.cpu_template),
365369
track_dirty_pages: Some(track_dirty_pages),

src/vmm/src/resources.rs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
99

1010
use crate::cpu_config::templates::CustomCpuTemplate;
1111
use crate::device_manager::persist::SharedDeviceType;
12+
use crate::devices::virtio::block::device::Block;
1213
use crate::logger::info;
1314
use crate::mmds;
1415
use crate::mmds::data_store::{Mmds, MmdsVersion};
@@ -217,6 +218,11 @@ impl VmResources {
217218
BalloonConfigError::IncompatibleWith("huge pages"),
218219
));
219220
}
221+
if self.machine_config.secret_free {
222+
return Err(ResourcesError::BalloonDevice(
223+
BalloonConfigError::IncompatibleWith("secret freedom"),
224+
));
225+
}
220226
}
221227

222228
SharedDeviceType::Vsock(vsock) => {
@@ -262,6 +268,27 @@ impl VmResources {
262268
"huge pages",
263269
));
264270
}
271+
if self.balloon.get().is_some() && updated.secret_free {
272+
return Err(MachineConfigError::Incompatible(
273+
"balloon device",
274+
"secret freedom",
275+
));
276+
}
277+
if updated.secret_free {
278+
if self.vhost_user_devices_used() {
279+
return Err(MachineConfigError::Incompatible(
280+
"vhost-user devices",
281+
"userspace bounce buffers",
282+
));
283+
}
284+
285+
if self.async_block_engine_used() {
286+
return Err(MachineConfigError::Incompatible(
287+
"async block engine",
288+
"userspace bounce buffers",
289+
));
290+
}
291+
}
265292
self.machine_config = updated;
266293

267294
Ok(())
@@ -320,6 +347,10 @@ impl VmResources {
320347
return Err(BalloonConfigError::IncompatibleWith("huge pages"));
321348
}
322349

350+
if self.machine_config.secret_free {
351+
return Err(BalloonConfigError::IncompatibleWith("secret freedom"));
352+
}
353+
323354
self.balloon.set(config)
324355
}
325356

@@ -343,6 +374,17 @@ impl VmResources {
343374
&mut self,
344375
block_device_config: BlockDeviceConfig,
345376
) -> Result<(), DriveError> {
377+
if self.machine_config.secret_free {
378+
if block_device_config.file_engine_type == Some(FileEngineType::Async) {
379+
return Err(DriveError::IncompatibleWithSecretFreedom(
380+
"async file engine",
381+
));
382+
}
383+
384+
if block_device_config.socket.is_some() {
385+
return Err(DriveError::IncompatibleWithSecretFreedom("vhost-user-blk"));
386+
}
387+
}
346388
self.block.insert(block_device_config)
347389
}
348390

@@ -442,17 +484,29 @@ impl VmResources {
442484
Ok(())
443485
}
444486

487+
/// Returns true if any vhost user devices are configured int his [`VmResources`] object
488+
pub fn vhost_user_devices_used(&self) -> bool {
489+
self.block
490+
.devices
491+
.iter()
492+
.any(|b| b.lock().expect("Poisoned lock").is_vhost_user())
493+
}
494+
495+
fn async_block_engine_used(&self) -> bool {
496+
self.block
497+
.devices
498+
.iter()
499+
.any(|b| match &*b.lock().unwrap() {
500+
Block::Virtio(b) => b.file_engine_type() == FileEngineType::Async,
501+
Block::VhostUser(_) => false,
502+
})
503+
}
504+
445505
/// Allocates guest memory in a configuration most appropriate for these [`VmResources`].
446506
///
447507
/// If vhost-user-blk devices are in use, allocates memfd-backed shared memory, otherwise
448508
/// prefers anonymous memory for performance reasons.
449509
pub fn allocate_guest_memory(&self) -> Result<Vec<GuestRegionMmap>, MemoryError> {
450-
let vhost_user_device_used = self
451-
.block
452-
.devices
453-
.iter()
454-
.any(|b| b.lock().expect("Poisoned lock").is_vhost_user());
455-
456510
// Page faults are more expensive for shared memory mapping, including memfd.
457511
// For this reason, we only back guest memory with a memfd
458512
// if a vhost-user-blk device is configured in the VM, otherwise we fall back to
@@ -464,7 +518,7 @@ impl VmResources {
464518
// that would not be worth the effort.
465519
let regions =
466520
crate::arch::arch_memory_regions(0, mib_to_bytes(self.machine_config.mem_size_mib));
467-
if vhost_user_device_used {
521+
if self.vhost_user_devices_used() {
468522
memory::memfd_backed(
469523
regions.as_ref(),
470524
self.machine_config.track_dirty_pages,
@@ -1307,6 +1361,7 @@ mod tests {
13071361
let mut aux_vm_config = MachineConfigUpdate {
13081362
vcpu_count: Some(32),
13091363
mem_size_mib: Some(512),
1364+
secret_free: Some(false),
13101365
smt: Some(false),
13111366
#[cfg(target_arch = "x86_64")]
13121367
cpu_template: Some(StaticCpuTemplate::T2),

src/vmm/src/vmm_config/drive.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pub enum DriveError {
2424
DeviceUpdate(VmmError),
2525
/// A root block device already exists!
2626
RootBlockDeviceAlreadyAdded,
27+
/// {0} is incompatible with secret freedom.
28+
IncompatibleWithSecretFreedom(&'static str),
2729
}
2830

2931
/// Use this structure to set up the Block Device before booting the kernel.

src/vmm/src/vmm_config/machine_config.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ pub struct MachineConfig {
9595
pub vcpu_count: u8,
9696
/// The memory size in MiB.
9797
pub mem_size_mib: usize,
98+
/// Whether guest_memfd should be used to back normal guest memory. If this is enabled
99+
/// and any devices are attached to the VM, userspace bounce buffers will be used
100+
/// as I/O into secret free memory is not possible.
101+
#[serde(default)]
102+
pub secret_free: bool,
98103
/// Enables or disabled SMT.
99104
#[serde(default)]
100105
pub smt: bool,
@@ -151,6 +156,7 @@ impl Default for MachineConfig {
151156
Self {
152157
vcpu_count: 1,
153158
mem_size_mib: DEFAULT_MEM_SIZE_MIB,
159+
secret_free: false,
154160
smt: false,
155161
cpu_template: None,
156162
track_dirty_pages: false,
@@ -176,6 +182,9 @@ pub struct MachineConfigUpdate {
176182
/// The memory size in MiB.
177183
#[serde(default)]
178184
pub mem_size_mib: Option<usize>,
185+
/// Whether secret freedom should be enabled
186+
#[serde(default)]
187+
pub secret_free: Option<bool>,
179188
/// Enables or disabled SMT.
180189
#[serde(default)]
181190
pub smt: Option<bool>,
@@ -208,6 +217,7 @@ impl From<MachineConfig> for MachineConfigUpdate {
208217
MachineConfigUpdate {
209218
vcpu_count: Some(cfg.vcpu_count),
210219
mem_size_mib: Some(cfg.mem_size_mib),
220+
secret_free: Some(cfg.secret_free),
211221
smt: Some(cfg.smt),
212222
cpu_template: cfg.static_template(),
213223
track_dirty_pages: Some(cfg.track_dirty_pages),
@@ -261,11 +271,27 @@ impl MachineConfig {
261271

262272
let mem_size_mib = update.mem_size_mib.unwrap_or(self.mem_size_mib);
263273
let page_config = update.huge_pages.unwrap_or(self.huge_pages);
274+
let secret_free = update.secret_free.unwrap_or(self.secret_free);
275+
let track_dirty_pages = update.track_dirty_pages.unwrap_or(self.track_dirty_pages);
264276

265277
if mem_size_mib == 0 || !page_config.is_valid_mem_size(mem_size_mib) {
266278
return Err(MachineConfigError::InvalidMemorySize);
267279
}
268280

281+
if secret_free && page_config != HugePageConfig::None {
282+
return Err(MachineConfigError::Incompatible(
283+
"secret freedom",
284+
"huge pages",
285+
));
286+
}
287+
288+
if secret_free && track_dirty_pages {
289+
return Err(MachineConfigError::Incompatible(
290+
"secret freedom",
291+
"diff snapshots",
292+
));
293+
}
294+
269295
let cpu_template = match update.cpu_template {
270296
None => self.cpu_template.clone(),
271297
Some(StaticCpuTemplate::None) => None,
@@ -275,9 +301,10 @@ impl MachineConfig {
275301
Ok(MachineConfig {
276302
vcpu_count,
277303
mem_size_mib,
304+
secret_free,
278305
smt,
279306
cpu_template,
280-
track_dirty_pages: update.track_dirty_pages.unwrap_or(self.track_dirty_pages),
307+
track_dirty_pages,
281308
huge_pages: page_config,
282309
#[cfg(feature = "gdb")]
283310
gdb_socket_path: update.gdb_socket_path.clone(),
@@ -343,6 +370,32 @@ mod tests {
343370
.unwrap();
344371
assert_eq!(updated.huge_pages, HugePageConfig::Hugetlbfs2M);
345372
assert_eq!(updated.mem_size_mib, 32);
373+
374+
let res = mconf.update(&MachineConfigUpdate {
375+
huge_pages: Some(HugePageConfig::Hugetlbfs2M),
376+
secret_free: Some(true),
377+
..Default::default()
378+
});
379+
assert_eq!(
380+
res,
381+
Err(MachineConfigError::Incompatible(
382+
"secret freedom",
383+
"huge pages"
384+
))
385+
);
386+
387+
let res = mconf.update(&MachineConfigUpdate {
388+
track_dirty_pages: Some(true),
389+
secret_free: Some(true),
390+
..Default::default()
391+
});
392+
assert_eq!(
393+
res,
394+
Err(MachineConfigError::Incompatible(
395+
"secret freedom",
396+
"diff snapshots"
397+
))
398+
);
346399
}
347400

348401
#[test]

tests/framework/vm_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"machine-config": {
2121
"vcpu_count": 2,
2222
"mem_size_mib": 1024,
23+
"secret_free": false,
2324
"smt": false,
2425
"track_dirty_pages": false,
2526
"huge_pages": "None"

tests/integration_tests/functional/test_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,7 @@ def test_get_full_config_after_restoring_snapshot(microvm_factory, uvm_nano):
10621062
setup_cfg["machine-config"] = {
10631063
"vcpu_count": 2,
10641064
"mem_size_mib": 256,
1065+
"secret_free": False,
10651066
"smt": True,
10661067
"track_dirty_pages": False,
10671068
"huge_pages": "None",
@@ -1175,6 +1176,7 @@ def test_get_full_config(uvm_plain):
11751176
expected_cfg["machine-config"] = {
11761177
"vcpu_count": 2,
11771178
"mem_size_mib": 256,
1179+
"secret_free": False,
11781180
"smt": False,
11791181
"track_dirty_pages": False,
11801182
"huge_pages": "None",

0 commit comments

Comments
 (0)