Unverified Commit af810fc6 authored by Adam C. Stephens's avatar Adam C. Stephens Committed by GitHub
Browse files

Merge pull request #284874 from shlevy/ovmf-ms

Enable MS-compatible secure boot with OVMF
parents cff84a20 88a1349d
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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;
+6 −0
Original line number Diff line number Diff line
@@ -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 [
+26 −0
Original line number Diff line number Diff line
@@ -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";
+113 −42
Original line number Diff line number Diff line
{ stdenv, nixosTests, lib, edk2, util-linux, nasm, acpica-tools, llvmPackages
, fetchurl, python3, pexpect, xorriso, qemu, dosfstools, mtools
, csmSupport ? false, seabios
, fdSize2MB ? csmSupport
, fdSize4MB ? false
, 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
@@ -14,28 +29,56 @@

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";
      msVarsArgs = {
        flavor = "AAVMF";
        archDir = "AARCH64";
      };
    };
    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 {
    url = "http://deb.debian.org/debian/pool/main/e/edk2/edk2_2023.11-5.debian.tar.xz";
    sha256 = "1yxlab4md30pxvjadr6b4xn6cyfw0c292q63pyfv4vylvhsb24g4";
  };

  buildPrefix = "Build/*/*";

in

assert platformSpecific ? ${cpuName};
assert systemManagementModeRequired -> stdenv.hostPlatform.isx86;
assert msVarsTemplate -> fdSize4MB;
assert msVarsTemplate -> platformSpecific.${cpuName} ? msVarsArgs;

edk2.mkDerivation projectDscPath (finalAttrs: {
  pname = "OVMF";
  inherit version;
@@ -43,7 +86,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" ];
@@ -54,6 +98,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"]
@@ -66,49 +111,75 @@ 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
  '';

  postFixup = (
    if stdenv.hostPlatform.isAarch then ''
    mkdir -vp $fd/FV
    mkdir -vp $fd/AAVMF
    mv -v $out/FV/QEMU_{EFI,VARS}.fd $fd/FV
  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
  '';

    # 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
  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 ${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
    )
  '';

    # Also add symlinks for Fedora dir layout: https://src.fedoraproject.org/cgit/rpms/edk2.git/tree/edk2.spec
  postInstall = ''
    mkdir -vp $fd/FV
    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
    # 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";
    # 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 = {
+5 −1
Original line number Diff line number Diff line
@@ -26824,12 +26824,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 || stdenv.isAarch64;
  };
  ops = callPackage ../applications/virtualization/ops { };