From 0703a70295c335490abe01f45fe68aee65118d7c Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 29 Jan 2024 17:52:53 -0500 Subject: [PATCH 1/6] OVMF: Expose flag to require SMM with secure boot --- pkgs/applications/virtualization/OVMF/default.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/applications/virtualization/OVMF/default.nix b/pkgs/applications/virtualization/OVMF/default.nix index 63c137c220c2..909581cc8f21 100644 --- a/pkgs/applications/virtualization/OVMF/default.nix +++ b/pkgs/applications/virtualization/OVMF/default.nix @@ -3,6 +3,7 @@ , fdSize2MB ? csmSupport , fdSize4MB ? false , secureBoot ? false +, systemManagementModeRequired ? secureBoot && stdenv.hostPlatform.isx86 , httpSupport ? false , tpmSupport ? false , tlsSupport ? false @@ -36,6 +37,8 @@ let in +assert systemManagementModeRequired -> stdenv.hostPlatform.isx86; + edk2.mkDerivation projectDscPath (finalAttrs: { pname = "OVMF"; inherit version; @@ -54,6 +57,7 @@ edk2.mkDerivation projectDscPath (finalAttrs: { ++ lib.optionals debug [ "-D DEBUG_ON_SERIAL_PORT=TRUE" ] ++ lib.optionals sourceDebug [ "-D SOURCE_DEBUG_ENABLE=TRUE" ] ++ lib.optionals secureBoot [ "-D SECURE_BOOT_ENABLE=TRUE" ] + ++ lib.optionals systemManagementModeRequired [ "-D SMM_REQUIRE=TRUE" ] ++ lib.optionals csmSupport [ "-D CSM_ENABLE" ] ++ lib.optionals fdSize2MB ["-D FD_SIZE_2MB"] ++ lib.optionals fdSize4MB ["-D FD_SIZE_4MB"] From d68a84a55a49e40108662a3f974be18e631eb790 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 29 Jan 2024 17:57:25 -0500 Subject: [PATCH 2/6] OVMF: Use larger flash size with secure boot. Debian claims the default is too small. --- pkgs/applications/virtualization/OVMF/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/applications/virtualization/OVMF/default.nix b/pkgs/applications/virtualization/OVMF/default.nix index 909581cc8f21..e6c1f274afdd 100644 --- a/pkgs/applications/virtualization/OVMF/default.nix +++ b/pkgs/applications/virtualization/OVMF/default.nix @@ -1,7 +1,7 @@ { stdenv, nixosTests, lib, edk2, util-linux, nasm, acpica-tools, llvmPackages , csmSupport ? false, seabios , fdSize2MB ? csmSupport -, fdSize4MB ? false +, fdSize4MB ? secureBoot , secureBoot ? false , systemManagementModeRequired ? secureBoot && stdenv.hostPlatform.isx86 , httpSupport ? false From 4673ad725460b0539f3bbe94494cc013260d4015 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 29 Jan 2024 19:21:18 -0500 Subject: [PATCH 3/6] OVMF: Enable building an NVRAM variables template with MSFT keys. --- .../virtualization/OVMF/default.nix | 61 ++++++++++++++++++- pkgs/top-level/all-packages.nix | 6 +- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/pkgs/applications/virtualization/OVMF/default.nix b/pkgs/applications/virtualization/OVMF/default.nix index e6c1f274afdd..fe1223b8964f 100644 --- a/pkgs/applications/virtualization/OVMF/default.nix +++ b/pkgs/applications/virtualization/OVMF/default.nix @@ -1,9 +1,23 @@ { stdenv, nixosTests, lib, edk2, util-linux, nasm, acpica-tools, llvmPackages +, fetchurl, python3, pexpect, xorriso, qemu, dosfstools, mtools , csmSupport ? false, seabios , fdSize2MB ? csmSupport , fdSize4MB ? secureBoot , secureBoot ? false , systemManagementModeRequired ? secureBoot && stdenv.hostPlatform.isx86 +# Whether to create an nvram variables template +# which includes the MSFT secure boot keys +, msVarsTemplate ? false +# When creating the nvram variables template with +# the MSFT keys, we also must provide a certificate +# to use as the PK and first KEK for the keystore. +# +# By default, we use Debian's cert. This default +# should chnage to a NixOS cert once we have our +# own secure boot signing infrastructure. +# +# Ignored if msVarsTemplate is false. +, vendorPkKek ? "$NIX_BUILD_TOP/debian/PkKek-1-Debian.pem" , httpSupport ? false , tpmSupport ? false , tlsSupport ? false @@ -35,9 +49,25 @@ let riscv64 = "FV/RISCV_VIRT"; }; + OvmfPkKek1AppPrefix = "4e32566d-8e9e-4f52-81d3-5bb9715f9727"; + + debian-edk-src = fetchurl { + url = "http://deb.debian.org/debian/pool/main/e/edk2/edk2_2023.11-5.debian.tar.xz"; + sha256 = "1yxlab4md30pxvjadr6b4xn6cyfw0c292q63pyfv4vylvhsb24g4"; + }; + + buildPrefix = "Build/*/*"; + in assert systemManagementModeRequired -> stdenv.hostPlatform.isx86; +assert msVarsTemplate -> fdSize4MB; +# TODO: Support other platforms. +# +# Need to set the --flavor flag to edk2-vars-generator +# (currently supports AAVMF, but not RISCV_VIRT), and +# need to adjust the locations passed to edk2-vars-generator. +assert msVarsTemplate -> stdenv.isx86_64; edk2.mkDerivation projectDscPath (finalAttrs: { pname = "OVMF"; @@ -46,7 +76,8 @@ edk2.mkDerivation projectDscPath (finalAttrs: { outputs = [ "out" "fd" ]; nativeBuildInputs = [ util-linux nasm acpica-tools ] - ++ lib.optionals stdenv.cc.isClang [ llvmPackages.bintools llvmPackages.llvm ]; + ++ lib.optionals stdenv.cc.isClang [ llvmPackages.bintools llvmPackages.llvm ] + ++ lib.optionals msVarsTemplate [ python3 pexpect xorriso qemu dosfstools mtools ]; strictDeps = true; hardeningDisable = [ "format" "stackprotector" "pic" "fortify" ]; @@ -70,10 +101,38 @@ edk2.mkDerivation projectDscPath (finalAttrs: { env.PYTHON_COMMAND = "python3"; + postUnpack = lib.optionalDrvAttr msVarsTemplate '' + unpackFile ${debian-edk-src} + ''; + postPatch = lib.optionalString csmSupport '' cp ${seabios}/share/seabios/Csm16.bin OvmfPkg/Csm/Csm16/Csm16.bin ''; + postConfigure = lib.optionalDrvAttr msVarsTemplate '' + tr -d '\n' < ${vendorPkKek} | sed \ + -e 's/.*-----BEGIN CERTIFICATE-----/${OvmfPkKek1AppPrefix}:/' \ + -e 's/-----END CERTIFICATE-----//' > vendor-cert-string + export PYTHONPATH=$NIX_BUILD_TOP/debian/python:$PYTHONPATH + ''; + + postBuild = lib.optionalDrvAttr msVarsTemplate '' + python3 $NIX_BUILD_TOP/debian/edk2-vars-generator.py \ + --flavor OVMF_4M \ + --enrolldefaultkeys ${buildPrefix}/X64/EnrollDefaultKeys.efi \ + --shell ${buildPrefix}/X64/Shell.efi \ + --code ${buildPrefix}/FV/OVMF_CODE.fd \ + --vars-template ${buildPrefix}/FV/OVMF_VARS.fd \ + --certificate `< vendor-cert-string` \ + --out-file OVMF_VARS.ms.fd + ''; + + postInstall = lib.optionalDrvAttr msVarsTemplate '' + mkdir -vp $fd/FV + install -v -m644 OVMF_VARS.ms.fd $fd/FV + ln -sv $fd/FV/OVMF_CODE{,.ms}.fd + ''; + postFixup = ( if stdenv.hostPlatform.isAarch then '' mkdir -vp $fd/FV diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index f978d8f3867b..ef46c7bc1929 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -26895,12 +26895,16 @@ with pkgs; rust-hypervisor-firmware = callPackage ../applications/virtualization/rust-hypervisor-firmware { }; - OVMF = callPackage ../applications/virtualization/OVMF { }; + OVMF = callPackage ../applications/virtualization/OVMF { + inherit (python3Packages) pexpect; + }; OVMFFull = callPackage ../applications/virtualization/OVMF { + inherit (python3Packages) pexpect; secureBoot = true; httpSupport = true; tpmSupport = true; tlsSupport = true; + msVarsTemplate = stdenv.isx86_64; }; ops = callPackage ../applications/virtualization/ops { }; From 9188bb5186c88b9e98da1de9420e53bf9d68180d Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 4 Feb 2024 08:28:30 -0500 Subject: [PATCH 4/6] OVMF: Add test with secure boot enabled Co-authored-by: Arthur Gautier --- nixos/lib/make-disk-image.nix | 3 +++ nixos/modules/virtualisation/qemu-vm.nix | 6 +++++ nixos/tests/systemd-boot.nix | 26 +++++++++++++++++++ .../virtualization/OVMF/default.nix | 2 ++ 4 files changed, 37 insertions(+) diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix index 1a33abd01ea1..047e72e2ac0d 100644 --- a/nixos/lib/make-disk-image.nix +++ b/nixos/lib/make-disk-image.nix @@ -536,6 +536,9 @@ let format' = format; in let concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}" ++ lib.optionals touchEFIVars [ "-drive if=pflash,format=raw,unit=1,file=$efiVars" + ] ++ lib.optionals (OVMF.systemManagementModeRequired or false) [ + "-machine" "q35,smm=on" + "-global" "driver=cfi.pflash01,property=secure,value=on" ] ); inherit memSize; diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index 55a214325118..75ba6dacc122 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -877,9 +877,11 @@ in type = types.package; default = (pkgs.OVMF.override { secureBoot = cfg.useSecureBoot; + systemManagementModeRequired = cfg.useSecureBoot; }).fd; defaultText = ''(pkgs.OVMF.override { secureBoot = cfg.useSecureBoot; + systemManagementModeRequired = cfg.useSecureBoot; }).fd''; description = lib.mdDoc "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; @@ -1183,6 +1185,10 @@ in "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" ]) + (mkIf (cfg.efi.OVMF.systemManagementModeRequired or false) [ + "-machine" "q35,smm=on" + "-global" "driver=cfi.pflash01,property=secure,value=on" + ]) ]; virtualisation.qemu.drives = mkMerge [ diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index c0b37a230df0..ce3245f3d862 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -39,6 +39,32 @@ in ''; }; + # Test that systemd-boot works with secure boot + secureBoot = makeTest { + name = "systemd-boot-secure-boot"; + + nodes.machine = { + imports = [ common ]; + environment.systemPackages = [ pkgs.sbctl ]; + virtualisation.useSecureBoot = true; + }; + + testScript = '' + machine.start(allow_reboot=True) + machine.wait_for_unit("multi-user.target") + + machine.succeed("sbctl create-keys") + machine.succeed("sbctl enroll-keys --yes-this-might-brick-my-machine") + machine.succeed('sbctl sign /boot/EFI/systemd/systemd-bootx64.efi') + machine.succeed('sbctl sign /boot/EFI/BOOT/BOOTX64.EFI') + machine.succeed('sbctl sign /boot/EFI/nixos/*bzImage.efi') + + machine.reboot() + + assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status") + ''; + }; + # Check that specialisations create corresponding boot entries. specialisation = makeTest { name = "systemd-boot-specialisation"; diff --git a/pkgs/applications/virtualization/OVMF/default.nix b/pkgs/applications/virtualization/OVMF/default.nix index fe1223b8964f..bfd318af5798 100644 --- a/pkgs/applications/virtualization/OVMF/default.nix +++ b/pkgs/applications/virtualization/OVMF/default.nix @@ -172,6 +172,8 @@ edk2.mkDerivation projectDscPath (finalAttrs: { variables = "${prefix}_VARS.fd"; # This will test the EFI firmware for the host platform as part of the NixOS Tests setup. tests.basic-systemd-boot = nixosTests.systemd-boot.basic; + tests.secureBoot-systemd-boot = nixosTests.systemd-boot.secureBoot; + inherit secureBoot systemManagementModeRequired; }; meta = { From f6fc51dc9ffa06e8a6f939e7ab52392824cbd79b Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 16 Feb 2024 06:11:47 -0500 Subject: [PATCH 5/6] OVMF: Clean up platform-specific code. --- .../virtualization/OVMF/default.nix | 126 +++++++++--------- 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/pkgs/applications/virtualization/OVMF/default.nix b/pkgs/applications/virtualization/OVMF/default.nix index bfd318af5798..11f8c3037584 100644 --- a/pkgs/applications/virtualization/OVMF/default.nix +++ b/pkgs/applications/virtualization/OVMF/default.nix @@ -29,26 +29,36 @@ let - projectDscPath = if stdenv.isi686 then - "OvmfPkg/OvmfPkgIa32.dsc" - else if stdenv.isx86_64 then - "OvmfPkg/OvmfPkgX64.dsc" - else if stdenv.hostPlatform.isAarch then - "ArmVirtPkg/ArmVirtQemu.dsc" - else if stdenv.hostPlatform.isRiscV then - "OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc" - else - throw "Unsupported architecture"; + platformSpecific = { + i686 = { + projectDscPath = "OvmfPkg/OvmfPkgIa32.dsc"; + fwPrefix = "OVMF"; + }; + x86_64 = { + projectDscPath = "OvmfPkg/OvmfPkgX64.dsc"; + fwPrefix = "OVMF"; + msVarsArgs = { + flavor = "OVMF_4M"; + archDir = "X64"; + }; + }; + aarch64 = { + projectDscPath = "ArmVirtPkg/ArmVirtQemu.dsc"; + fwPrefix = "AAVMF"; + }; + riscv64 = { + projectDscPath = "OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc"; + fwPrefix = "RISCV_VIRT"; + }; + }; + + cpuName = stdenv.hostPlatform.parsed.cpu.name; + + inherit (platformSpecific.${cpuName}) + projectDscPath fwPrefix msVarsArgs; version = lib.getVersion edk2; - suffixes = { - i686 = "FV/OVMF"; - x86_64 = "FV/OVMF"; - aarch64 = "FV/AAVMF"; - riscv64 = "FV/RISCV_VIRT"; - }; - OvmfPkKek1AppPrefix = "4e32566d-8e9e-4f52-81d3-5bb9715f9727"; debian-edk-src = fetchurl { @@ -60,14 +70,10 @@ let in +assert platformSpecific ? ${cpuName}; assert systemManagementModeRequired -> stdenv.hostPlatform.isx86; assert msVarsTemplate -> fdSize4MB; -# TODO: Support other platforms. -# -# Need to set the --flavor flag to edk2-vars-generator -# (currently supports AAVMF, but not RISCV_VIRT), and -# need to adjust the locations passed to edk2-vars-generator. -assert msVarsTemplate -> stdenv.isx86_64; +assert msVarsTemplate -> platformSpecific.${cpuName} ? msVarsArgs; edk2.mkDerivation projectDscPath (finalAttrs: { pname = "OVMF"; @@ -116,57 +122,53 @@ edk2.mkDerivation projectDscPath (finalAttrs: { export PYTHONPATH=$NIX_BUILD_TOP/debian/python:$PYTHONPATH ''; - postBuild = lib.optionalDrvAttr msVarsTemplate '' + postBuild = lib.optionalString stdenv.hostPlatform.isAarch '' + ( + cd ${buildPrefix}/FV + cp QEMU_EFI.fd ${fwPrefix}_CODE.fd + cp QEMU_VARS.fd ${fwPrefix}_VARS.fd + + # QEMU expects 64MiB CODE and VARS files on ARM/AARCH64 architectures + # Truncate the firmware files to the expected size + truncate -s 64M ${fwPrefix}_CODE.fd + truncate -s 64M ${fwPrefix}_VARS.fd + ) + '' + lib.optionalString stdenv.hostPlatform.isRiscV '' + truncate -s 32M ${buildPrefix}/FV/${fwPrefix}_CODE.fd + truncate -s 32M ${buildPrefix}/FV/${fwPrefix}_VARS.fd + '' + lib.optionalString msVarsTemplate '' + ( + cd ${buildPrefix} python3 $NIX_BUILD_TOP/debian/edk2-vars-generator.py \ - --flavor OVMF_4M \ - --enrolldefaultkeys ${buildPrefix}/X64/EnrollDefaultKeys.efi \ - --shell ${buildPrefix}/X64/Shell.efi \ - --code ${buildPrefix}/FV/OVMF_CODE.fd \ - --vars-template ${buildPrefix}/FV/OVMF_VARS.fd \ - --certificate `< vendor-cert-string` \ - --out-file OVMF_VARS.ms.fd + --flavor ${msVarsArgs.flavor} \ + --enrolldefaultkeys ${msVarsArgs.archDir}/EnrollDefaultKeys.efi \ + --shell ${msVarsArgs.archDir}/Shell.efi \ + --code FV/${fwPrefix}_CODE.fd \ + --vars-template FV/${fwPrefix}_VARS.fd \ + --certificate `< $NIX_BUILD_TOP/$sourceRoot/vendor-cert-string` \ + --out-file FV/${fwPrefix}_VARS.ms.fd + ) ''; - postInstall = lib.optionalDrvAttr msVarsTemplate '' + postInstall = '' mkdir -vp $fd/FV - install -v -m644 OVMF_VARS.ms.fd $fd/FV - ln -sv $fd/FV/OVMF_CODE{,.ms}.fd - ''; - - postFixup = ( - if stdenv.hostPlatform.isAarch then '' - mkdir -vp $fd/FV - mkdir -vp $fd/AAVMF + mv -v $out/FV/${fwPrefix}_{CODE,VARS}.fd $fd/FV + '' + lib.optionalString msVarsTemplate '' + mv -v $out/FV/${fwPrefix}_VARS.ms.fd $fd/FV + ln -sv $fd/FV/${fwPrefix}_CODE{,.ms}.fd + '' + lib.optionalString stdenv.hostPlatform.isAarch '' mv -v $out/FV/QEMU_{EFI,VARS}.fd $fd/FV - - # Use Debian dir layout: https://salsa.debian.org/qemu-team/edk2/blob/debian/debian/rules - dd of=$fd/FV/AAVMF_CODE.fd if=/dev/zero bs=1M count=64 - dd of=$fd/FV/AAVMF_CODE.fd if=$fd/FV/QEMU_EFI.fd conv=notrunc - dd of=$fd/FV/AAVMF_VARS.fd if=/dev/zero bs=1M count=64 - - # Also add symlinks for Fedora dir layout: https://src.fedoraproject.org/cgit/rpms/edk2.git/tree/edk2.spec + # Add symlinks for Fedora dir layout: https://src.fedoraproject.org/cgit/rpms/edk2.git/tree/edk2.spec + mkdir -vp $fd/AAVMF ln -s $fd/FV/AAVMF_CODE.fd $fd/AAVMF/QEMU_EFI-pflash.raw ln -s $fd/FV/AAVMF_VARS.fd $fd/AAVMF/vars-template-pflash.raw - '' - else if stdenv.hostPlatform.isRiscV then '' - mkdir -vp $fd/FV - - mv -v $out/FV/RISCV_VIRT_{CODE,VARS}.fd $fd/FV/ - truncate -s 32M $fd/FV/RISCV_VIRT_CODE.fd - truncate -s 32M $fd/FV/RISCV_VIRT_VARS.fd - '' - else '' - mkdir -vp $fd/FV - mv -v $out/FV/OVMF{,_CODE,_VARS}.fd $fd/FV - ''); + ''; dontPatchELF = true; passthru = let - cpuName = stdenv.hostPlatform.parsed.cpu.name; - suffix = suffixes."${cpuName}" or (throw "Host cpu name `${cpuName}` is not supported in this OVMF derivation!"); - prefix = "${finalAttrs.finalPackage.fd}/${suffix}"; + prefix = "${finalAttrs.finalPackage.fd}/FV/${fwPrefix}"; in { firmware = "${prefix}_CODE.fd"; variables = "${prefix}_VARS.fd"; From 88a1349dfe606be461057b553087eb6b787b4118 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Fri, 16 Feb 2024 08:27:37 -0500 Subject: [PATCH 6/6] OVMF: Enable bundling the MS secure boot variables on aarch64. --- pkgs/applications/virtualization/OVMF/default.nix | 4 ++++ pkgs/top-level/all-packages.nix | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/applications/virtualization/OVMF/default.nix b/pkgs/applications/virtualization/OVMF/default.nix index 11f8c3037584..cff31a759a2b 100644 --- a/pkgs/applications/virtualization/OVMF/default.nix +++ b/pkgs/applications/virtualization/OVMF/default.nix @@ -45,6 +45,10 @@ let aarch64 = { projectDscPath = "ArmVirtPkg/ArmVirtQemu.dsc"; fwPrefix = "AAVMF"; + msVarsArgs = { + flavor = "AAVMF"; + archDir = "AARCH64"; + }; }; riscv64 = { projectDscPath = "OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc"; diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index ef46c7bc1929..e76c96f82be1 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -26904,7 +26904,7 @@ with pkgs; httpSupport = true; tpmSupport = true; tlsSupport = true; - msVarsTemplate = stdenv.isx86_64; + msVarsTemplate = stdenv.isx86_64 || stdenv.isAarch64; }; ops = callPackage ../applications/virtualization/ops { };