diff --git a/SOURCES/edk2-ovmf-logo.bmp b/SOURCES/edk2-ovmf-logo.bmp new file mode 100644 index 0000000..b3be84c Binary files /dev/null and b/SOURCES/edk2-ovmf-logo.bmp differ diff --git a/SOURCES/ovmf-vars-generator b/SOURCES/ovmf-vars-generator new file mode 100644 index 0000000..e04e976 --- /dev/null +++ b/SOURCES/ovmf-vars-generator @@ -0,0 +1,273 @@ +#!/bin/python +# Copyright (C) 2017 Red Hat +# Authors: +# - Patrick Uiterwijk +# - Kashyap Chamarthy +# +# Licensed under MIT License, for full text see LICENSE +# +# Purpose: Launch a QEMU guest and enroll ithe UEFI keys into an OVMF +# variables ("VARS") file. Then boot a Linux kernel with QEMU. +# Finally, perform a check to verify if Secure Boot +# is enabled. + +from __future__ import print_function + +import argparse +import os +import logging +import tempfile +import shutil +import string +import subprocess + + +def strip_special(line): + return ''.join([c for c in str(line) if c in string.printable]) + + +def generate_qemu_cmd(args, readonly, *extra_args): + if args.disable_smm: + machinetype = 'pc' + else: + machinetype = 'q35,smm=on' + machinetype += ',accel=%s' % ('kvm' if args.enable_kvm else 'tcg') + return [ + args.qemu_binary, + '-machine', machinetype, + '-display', 'none', + '-no-user-config', + '-nodefaults', + '-m', '256', + '-smp', '2,sockets=2,cores=1,threads=1', + '-chardev', 'pty,id=charserial1', + '-device', 'isa-serial,chardev=charserial1,id=serial1', + '-global', 'driver=cfi.pflash01,property=secure,value=%s' % ( + 'off' if args.disable_smm else 'on'), + '-drive', + 'file=%s,if=pflash,format=raw,unit=0,readonly=on' % ( + args.ovmf_binary), + '-drive', + 'file=%s,if=pflash,format=raw,unit=1,readonly=%s' % ( + args.out_temp, 'on' if readonly else 'off'), + '-serial', 'stdio'] + list(extra_args) + + +def download(url, target, suffix, no_download): + istemp = False + if target and os.path.exists(target): + return target, istemp + if not target: + temped = tempfile.mkstemp(prefix='qosb.', suffix='.%s' % suffix) + os.close(temped[0]) + target = temped[1] + istemp = True + if no_download: + raise Exception('%s did not exist, but downloading was disabled' % + target) + import requests + logging.debug('Downloading %s to %s', url, target) + r = requests.get(url, stream=True) + with open(target, 'wb') as f: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + return target, istemp + + +def enroll_keys(args): + shutil.copy(args.ovmf_template_vars, args.out_temp) + + logging.info('Starting enrollment') + + cmd = generate_qemu_cmd( + args, + False, + '-drive', + 'file=%s,format=raw,if=none,media=cdrom,id=drive-cd1,' + 'readonly=on' % args.uefi_shell_iso, + '-device', + 'ide-cd,drive=drive-cd1,id=cd1,' + 'bootindex=1') + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + logging.info('Performing enrollment') + # Wait until the UEFI shell starts (first line is printed) + read = p.stdout.readline() + #if b'char device redirected' in read: + # read = p.stdout.readline() + #if args.print_output: + # print(strip_special(read), end='') + # print() + # Send the escape char to enter the UEFI shell early + p.stdin.write(b'\x1b') + p.stdin.flush() + # And then run the following three commands from the UEFI shell: + # change into the first file system device; install the default + # keys and certificates, and reboot + p.stdin.write(b'fs0:\r\n') + p.stdin.write(b'EnrollDefaultKeys.efi\r\n') + p.stdin.write(b'reset -s\r\n') + p.stdin.flush() + #while True: + # read = p.stdout.readline() + # if args.print_output: + # print('OUT: %s' % strip_special(read), end='') + # print() + # if b'info: success' in read: + # break + #p.wait() + if args.print_output: + print(strip_special(p.stdout.read()), end='') + logging.info('Finished enrollment') + + +def test_keys(args): + logging.info('Grabbing test kernel') + kernel, kerneltemp = download(args.kernel_url, args.kernel_path, + 'kernel', args.no_download) + + logging.info('Starting verification') + try: + cmd = generate_qemu_cmd( + args, + True, + '-append', 'console=tty0 console=ttyS0,115200n8', + '-kernel', kernel) + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + logging.info('Performing verification') + while True: + read = p.stdout.readline() + if args.print_output: + print('OUT: %s' % strip_special(read), end='') + print() + if b'Secure boot disabled' in read: + raise Exception('Secure Boot was disabled') + elif b'Secure boot enabled' in read: + logging.info('Confirmed: Secure Boot is enabled') + break + elif b'Kernel is locked down from EFI secure boot' in read: + logging.info('Confirmed: Secure Boot is enabled') + break + p.kill() + if args.print_output: + print(strip_special(p.stdout.read()), end='') + logging.info('Finished verification') + finally: + if kerneltemp: + os.remove(kernel) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('output', help='Filename for output vars file') + parser.add_argument('--out-temp', help=argparse.SUPPRESS) + parser.add_argument('--force', help='Overwrite existing output file', + action='store_true') + parser.add_argument('--print-output', help='Print the QEMU guest output', + action='store_true') + parser.add_argument('--verbose', '-v', help='Increase verbosity', + action='count') + parser.add_argument('--quiet', '-q', help='Decrease verbosity', + action='count') + parser.add_argument('--qemu-binary', help='QEMU binary path', + default='/usr/bin/qemu-system-x86_64') + parser.add_argument('--enable-kvm', help='Enable KVM acceleration', + action='store_true') + parser.add_argument('--ovmf-binary', help='OVMF secureboot code file', + default='/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd') + parser.add_argument('--ovmf-template-vars', help='OVMF empty vars file', + default='/usr/share/edk2/ovmf/OVMF_VARS.fd') + parser.add_argument('--uefi-shell-iso', help='Path to uefi shell iso', + default='/usr/share/edk2/ovmf/UefiShell.iso') + parser.add_argument('--skip-enrollment', + help='Skip enrollment, only test', action='store_true') + parser.add_argument('--skip-testing', + help='Skip testing generated "VARS" file', + action='store_true') + parser.add_argument('--kernel-path', + help='Specify a consistent path for kernel') + parser.add_argument('--no-download', action='store_true', + help='Never download a kernel') + parser.add_argument('--fedora-version', + help='Fedora version to get kernel for checking', + default='27') + parser.add_argument('--kernel-url', help='Kernel URL', + default='https://download.fedoraproject.org/pub/fedora' + '/linux/releases/%(version)s/Everything/x86_64' + '/os/images/pxeboot/vmlinuz') + parser.add_argument('--disable-smm', + help=('Don\'t restrict varstore pflash writes to ' + 'guest code that executes in SMM. Use this ' + 'option only if your OVMF binary doesn\'t have ' + 'the edk2 SMM driver stack built into it ' + '(possibly because your QEMU binary lacks SMM ' + 'emulation). Note that without restricting ' + 'varstore pflash writes to guest code that ' + 'executes in SMM, a malicious guest kernel, ' + 'used for testing, could undermine Secure ' + 'Boot.'), + action='store_true') + args = parser.parse_args() + args.kernel_url = args.kernel_url % {'version': args.fedora_version} + + validate_args(args) + return args + + +def validate_args(args): + if (os.path.exists(args.output) + and not args.force + and not args.skip_enrollment): + raise Exception('%s already exists' % args.output) + + if args.skip_enrollment and not os.path.exists(args.output): + raise Exception('%s does not yet exist' % args.output) + + verbosity = (args.verbose or 1) - (args.quiet or 0) + if verbosity >= 2: + logging.basicConfig(level=logging.DEBUG) + elif verbosity == 1: + logging.basicConfig(level=logging.INFO) + elif verbosity < 0: + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(level=logging.WARN) + + if args.skip_enrollment: + args.out_temp = args.output + else: + temped = tempfile.mkstemp(prefix='qosb.', suffix='.vars') + os.close(temped[0]) + args.out_temp = temped[1] + logging.debug('Temp output: %s', args.out_temp) + + +def move_to_dest(args): + shutil.copy(args.out_temp, args.output) + os.remove(args.out_temp) + + +def main(): + args = parse_args() + if not args.skip_enrollment: + enroll_keys(args) + if not args.skip_testing: + test_keys(args) + if not args.skip_enrollment: + move_to_dest(args) + if args.skip_testing: + logging.info('Created %s' % args.output) + else: + logging.info('Created and verified %s' % args.output) + else: + logging.info('Verified %s', args.output) + + +if __name__ == '__main__': + main() diff --git a/SPECS/ovmf.spec b/SPECS/ovmf.spec new file mode 100644 index 0000000..bf81d39 --- /dev/null +++ b/SPECS/ovmf.spec @@ -0,0 +1,151 @@ +%global debug_package %{nil} +%global __python %{__python} +%define GITDATE 201905 +%define OPENSSLVER 1_1_1b + +Name: ovmf +Version: %{GITDATE} +Release: 1%{?dist} +Summary: UEFI firmware for 64-bit virtual machines +Group: Applications/Emulators +License: BSD and OpenSSL and MIT +URL: http://www.tianocore.org +Source0: https://github.com/tianocore/edk2/archive/edk2-stable%{GITDATE}.tar.gz +Source1: https://github.com/openssl/openssl/archive/OpenSSL_%{OPENSSLVER}.tar.gz +Source2: ovmf-vars-generator +Source3: edk2-ovmf-logo.bmp +ExclusiveArch: x86_64 +BuildRequires: python2-devel +BuildRequires: libuuid-devel +BuildRequires: /usr/bin/iasl +BuildRequires: binutils gcc git +BuildRequires: nasm +BuildRequires: dosfstools +BuildRequires: mtools +BuildRequires: genisoimage +BuildRequires: qemu-kvm >= 1.5.3-44 +BuildRequires: kernel >= 3.10.0-52 +BuildRequires: rpmdevtools + + +%description +EDK II is a modern, feature-rich, cross-platform firmware development +environment for the UEFI and PI specifications. This package contains sample +64-bit UEFI firmware builds for QEMU and KVM. + + +%prep +%setup -q -n edk2-edk2-stable%{GITDATE} +tar xvf %{SOURCE1} -C CryptoPkg/Library/OpensslLib/ +mv CryptoPkg/Library/OpensslLib/openssl-OpenSSL_%{OPENSSLVER}/* CryptoPkg/Library/OpensslLib/openssl/ +cp %{SOURCE2} . +cp %{SOURCE3} MdeModulePkg/Logo/Logo.bmp + + +%build +make -j32 -C BaseTools/ +source ./edksetup.sh +make -C "$EDK_TOOLS_PATH" + +SMP_MFLAGS="%{?_smp_mflags}" +if [[ x"$SMP_MFLAGS" = x-j* ]]; then + CC_FLAGS="$CC_FLAGS -n ${SMP_MFLAGS#-j}" +elif [ -n "%{?jobs}" ]; then + CC_FLAGS="$CC_FLAGS -n %{?jobs}" +fi + +CC_FLAGS="$CC_FLAGS --cmd-len=65536 -t GCC48 -b DEBUG --hash" + +# Build with SB but without SMM; include UEFI shell. +build ${CC_FLAGS} -D FD_SIZE_4MB -a X64 -p OvmfPkg/OvmfPkgX64.dsc -D SECURE_BOOT_ENABLE +# Build with SB and SMM; exclude UEFI shell. +build -D SECURE_BOOT_ENABLE -D EXCLUDE_SHELL_FROM_FD ${CC_FLAGS} -a IA32 -a X64 -p OvmfPkg/OvmfPkgIa32X64.dsc -D SMM_REQUIRE -D FD_SIZE_4MB +# Sanity check: the varstore templates must be identical. +cmp Build/OvmfX64/DEBUG_GCC4?/FV/OVMF_VARS.fd Build/Ovmf3264/DEBUG_GCC4?/FV/OVMF_VARS.fd + +# Prepare an ISO image that boots the UEFI shell. +( + UEFI_SHELL_BINARY=Build/Ovmf3264/DEBUG_GCC48/X64/Shell.efi + ENROLLER_BINARY=Build/Ovmf3264/DEBUG_GCC48/X64/EnrollDefaultKeys.efi + UEFI_SHELL_IMAGE=uefi_shell.img + ISO_IMAGE=UefiShell.iso + + UEFI_SHELL_BINARY_BNAME=$(basename -- "$UEFI_SHELL_BINARY") + UEFI_SHELL_SIZE=$(stat --format=%s -- "$UEFI_SHELL_BINARY") + ENROLLER_SIZE=$(stat --format=%s -- "$ENROLLER_BINARY") + + # add 1MB then 10% for metadata + UEFI_SHELL_IMAGE_KB=$(( + (UEFI_SHELL_SIZE + ENROLLER_SIZE + 1 * 1024 * 1024) * 11 / 10 / 1024 + )) + + # create non-partitioned FAT image + rm -f -- "$UEFI_SHELL_IMAGE" + mkdosfs -C "$UEFI_SHELL_IMAGE" -n UEFI_SHELL -- "$UEFI_SHELL_IMAGE_KB" + + # copy the shell binary into the FAT image + export MTOOLS_SKIP_CHECK=1 + mmd -i "$UEFI_SHELL_IMAGE" ::efi + mmd -i "$UEFI_SHELL_IMAGE" ::efi/boot + mcopy -i "$UEFI_SHELL_IMAGE" "$UEFI_SHELL_BINARY" ::efi/boot/bootx64.efi + mcopy -i "$UEFI_SHELL_IMAGE" "$ENROLLER_BINARY" :: + mdir -i "$UEFI_SHELL_IMAGE" -/ :: + + # build ISO with FAT image file as El Torito EFI boot image + genisoimage -input-charset ASCII -J -rational-rock \ + -efi-boot "$UEFI_SHELL_IMAGE" -no-emul-boot \ + -o "$ISO_IMAGE" -- "$UEFI_SHELL_IMAGE" +) + +# Enroll the default certificates in a separate variable store template. Base +# RHEL7 qemu-kvm does not emulate SMM, but we don't need SMM for the enrollment +# here. +chmod +x ./ovmf-vars-generator +./ovmf-vars-generator --verbose --verbose \ + --qemu-binary /usr/bin/qemu-system-x86_64 \ + --ovmf-binary Build/OvmfX64/DEBUG_GCC4?/FV/OVMF_CODE.fd \ + --ovmf-template-vars Build/OvmfX64/DEBUG_GCC4?/FV/OVMF_VARS.fd \ + --uefi-shell-iso UefiShell.iso \ + --skip-testing \ + --disable-smm \ + OVMF_VARS.secboot.fd + + +%install + +copy_license() { + install -m 0644 $1 $RPM_BUILD_ROOT%{_docdir}/%{name}/Licenses/$2-License.txt +} + +mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{name}/Licenses +copy_license License.txt edk2 +copy_license OvmfPkg/License.txt OvmfPkg + +mkdir -p $RPM_BUILD_ROOT%{_datadir}/OVMF + +install -m 0644 Build/OvmfX64/DEBUG_GCC4?/FV/OVMF_CODE.fd $RPM_BUILD_ROOT%{_datadir}/OVMF/OVMF_CODE.fd +install -m 0644 Build/Ovmf3264/DEBUG_GCC4?/FV/OVMF_CODE.fd $RPM_BUILD_ROOT%{_datadir}/OVMF/OVMF_CODE.secboot.fd +install -m 0644 Build/OvmfX64/DEBUG_GCC4?/FV/OVMF_VARS.fd $RPM_BUILD_ROOT%{_datadir}/OVMF/OVMF_VARS.fd +install -m 0644 OVMF_VARS.secboot.fd $RPM_BUILD_ROOT%{_datadir}/OVMF/OVMF_VARS.secboot.fd +install -m 0644 UefiShell.iso $RPM_BUILD_ROOT%{_datadir}/OVMF/UefiShell.iso +install -m 0644 OvmfPkg/README $RPM_BUILD_ROOT%{_docdir}/%{name}/README + +copy_license CryptoPkg/Library/OpensslLib/openssl/LICENSE OpensslLib + + +%files +%defattr(-,root,root,-) +%dir %{_docdir}/%{name}/Licenses +%doc %{_docdir}/%{name}/Licenses/edk2-License.txt +%doc %{_docdir}/%{name}/Licenses/OvmfPkg-License.txt +%doc %{_docdir}/%{name}/Licenses/OpensslLib-License.txt +%doc %{_docdir}/%{name}/README +%dir %{_datadir}/OVMF/ +%{_datadir}/OVMF/OVMF_CODE.fd +%{_datadir}/OVMF/OVMF_CODE.secboot.fd +%{_datadir}/OVMF/OVMF_VARS.fd +%{_datadir}/OVMF/OVMF_VARS.secboot.fd +%{_datadir}/OVMF/UefiShell.iso + + +%changelog