<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--coco=</option></term>
+
+ <listitem><para>Caveat: This feature is experimental, and is likely to be changed (or removed in
+ its current form) in a future version of systemd.</para>
+
+ <para>Configures whether to run the guest as a confidential VM. Takes one of
+ <literal>no</literal> or <literal>sev-snp</literal>. Defaults to <literal>no</literal>.</para>
+
+ <para><literal>sev-snp</literal> enables AMD SEV-SNP. This requires KVM on an x86_64 host with
+ SNP-capable hardware and firmware. <option>--firmware=</option> must point to a raw SNP-built
+ OVMF <filename>.fd</filename> image; the standard pflash + NVRAM split is not supported under
+ SNP, so the firmware is loaded via QEMU's <option>-bios</option> and Secure Boot is
+ unavailable. SMBIOS credentials passed via <option>--set-credential=</option> or
+ <option>--load-credential=</option> are rejected because they are outside the SNP launch
+ measurement. Direct kernel boot via <option>--linux=</option> is required so that the
+ kernel, initrd and command line are hashed into the launch measurement
+ (<literal>kernel-hashes=on</literal>); booting the kernel off the disk image via the
+ firmware would leave it outside the measurement. A vTPM, if attached via
+ <option>--tpm=</option>, must be treated as untrusted by the guest.</para>
+
+ <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--grow-image=<replaceable>BYTES</replaceable></option></term>
<term><option>-G <replaceable>BYTES</replaceable></option></term>
static bool arg_firmware_describe = false;
static Set *arg_firmware_features_include = NULL;
static Set *arg_firmware_features_exclude = NULL;
+static ConfidentialComputing arg_confidential_computing = COCO_NO;
static char *arg_forward_journal = NULL;
static uint64_t arg_forward_journal_max_use = UINT64_MAX;
static uint64_t arg_forward_journal_keep_free = UINT64_MAX;
break;
}
+ OPTION_LONG("coco", "no|sev-snp", "Run the guest as a confidential VM"): {
+ ConfidentialComputing cc = confidential_computing_from_string(opts.arg);
+ if (cc < 0)
+ return log_error_errno(cc, "Unknown --coco= value: %s", opts.arg);
+ arg_confidential_computing = cc;
+ break;
+ }
+
OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"):
r = parse_boolean_argument("--discard-disk=", opts.arg, &arg_discard_disk);
if (r < 0)
use_kvm = r;
}
- if (arg_firmware_type == FIRMWARE_UEFI) {
+ if (arg_confidential_computing == COCO_AMD_SEV_SNP && !use_kvm)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "--coco=sev-snp requires KVM, but KVM is not available.");
+
+ if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing != COCO_AMD_SEV_SNP) {
if (arg_firmware)
r = load_ovmf_config(arg_firmware, &ovmf_config);
else
if (r < 0)
return r;
+ if (arg_confidential_computing == COCO_AMD_SEV_SNP) {
+ r = qemu_config_key(config_file, "kernel-irqchip", "split");
+ if (r < 0)
+ return r;
+ }
+
if (ovmf_config && ARCHITECTURE_SUPPORTS_SMM) {
r = qemu_config_key(config_file, "smm", on_off(ovmf_config->supports_sb));
if (r < 0)
return r;
}
- if (ARCHITECTURE_SUPPORTS_CXL) {
+ if (ARCHITECTURE_SUPPORTS_CXL && arg_confidential_computing == COCO_NO) {
r = qemu_config_key(config_file, "cxl", "on");
if (r < 0)
return r;
return r;
}
+ if (arg_confidential_computing == COCO_AMD_SEV_SNP) {
+ r = qemu_config_key(config_file, "confidential-guest-support", "snp0");
+ if (r < 0)
+ return r;
+ }
+
r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL,
"cpus", arg_cpus ?: "1");
if (r < 0)
if (r < 0)
return r;
- r = qemu_config_section(config_file, "device", "balloon0",
- "driver", "virtio-balloon",
- "free-page-reporting", "on");
- if (r < 0)
- return r;
+ if (arg_confidential_computing == COCO_NO) {
+ r = qemu_config_section(config_file, "device", "balloon0",
+ "driver", "virtio-balloon",
+ "free-page-reporting", "on");
+ if (r < 0)
+ return r;
+ }
if (ARCHITECTURE_SUPPORTS_VMGENID) {
sd_id128_t vmgenid;
return r;
}
+ if (arg_confidential_computing == COCO_AMD_SEV_SNP) {
+ /* SNP marks encrypted guest pages via the "C-bit" in the page table entry. On all
+ * SNP-capable processors (Milan and later) the C-bit lives at bit 51, which reduces
+ * the usable guest physical address space by one bit.
+ * Embed the hashes of kernel, initrd and cmdline into the firmware
+ * so they are covered by the launch measurement and the guest's
+ * boot chain starts from a measured state. */
+ r = qemu_config_section(config_file, "object", "snp0",
+ "qom-type", "sev-snp-guest",
+ "cbitpos", "51",
+ "reduced-phys-bits", "1",
+ "kernel-hashes", "on");
+ if (r < 0)
+ return r;
+ }
+
unsigned child_cid = arg_vsock_cid;
if (use_vsock) {
config.vsock.fd = TAKE_FD(vhost_device_fd);
}
_cleanup_(unlink_and_freep) char *ovmf_vars = NULL;
- r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars);
- if (r < 0)
- return r;
+ if (arg_confidential_computing != COCO_NO) {
+ r = strv_extend_many(&cmdline, "-bios", arg_firmware);
+ if (r < 0)
+ return r;
+ } else {
+ r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars);
+ if (r < 0)
+ return r;
+ }
if (arg_linux) {
r = strv_extend_many(&cmdline, "-kernel", arg_linux);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'.");
+ if (arg_confidential_computing == COCO_AMD_SEV_SNP) {
+ if (native_architecture() != ARCHITECTURE_X86_64)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "--coco=sev-snp is only supported on x86_64.");
+ if (arg_kvm == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--coco=sev-snp requires KVM, remove --kvm=no.");
+ if (arg_firmware_type != FIRMWARE_UEFI)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--coco can't be used with %s firmware",
+ firmware_to_string(arg_firmware_type));
+ /* SNP can't use pflash + NVRAM split, so the firmware-descriptor
+ * machinery doesn't apply. Require an explicit raw .fd path and
+ * use it verbatim with -bios later. */
+ if (!arg_firmware)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--coco=sev-snp requires --firmware=PATH "
+ "pointing at a raw SNP-built OVMF .fd binary.");
+ log_debug("Using raw SNP firmware at %s (no NVRAM, no Secure Boot).", arg_firmware);
+ if (set_contains(arg_firmware_features_include, "secure-boot"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--secure-boot=yes cannot be combined with --coco.");
+ if (arg_credentials.n_credentials != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "SMBIOS credentials aren't trusted by the confidential computing guest and will be rejected.");
+ if (arg_tpm > 0)
+ log_warning("TPM can't be trusted by the confidential computing guest");
+ /* kernel-hashes=on only covers what QEMU itself loads via -kernel/-initrd/-append.
+ * Without --linux= the kernel and initrd come off disk via OVMF and aren't part
+ * of the launch measurement, leaving the guest unattestable in any meaningful
+ * way. Require direct kernel boot so the boot chain starts from a measured state. */
+ if (!arg_linux)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--coco=sev-snp requires --linux= "
+ "so kernel, initrd and cmdline are covered by the launch measurement.");
+ }
+
return 0;
}