RHEL Engineering is moving the tracking of its product development work on RHEL 6 through RHEL 9 to Red Hat Jira (issues.redhat.com). If you're a Red Hat customer, please continue to file support cases via the Red Hat customer portal. If you're not, please head to the "RHEL project" in Red Hat Jira and file new tickets here. Individual Bugzilla bugs in the statuses "NEW", "ASSIGNED", and "POST" are being migrated throughout September 2023. Bugs of Red Hat partners with an assigned Engineering Partner Manager (EPM) are migrated in late September as per pre-agreed dates. Bugs against components "kernel", "kernel-rt", and "kpatch" are only migrated if still in "NEW" or "ASSIGNED". If you cannot log in to RH Jira, please consult article #7032570. That failing, please send an e-mail to the RH Jira admins at rh-issues@redhat.com to troubleshoot your issue as a user management inquiry. The email creates a ServiceNow ticket with Red Hat. Individual Bugzilla bugs that are migrated will be moved to status "CLOSED", resolution "MIGRATED", and set with "MigratedToJIRA" in "Keywords". The link to the successor Jira issue will be found under "Links", have a little "two-footprint" icon next to it, and direct you to the "RHEL project" in Red Hat Jira (issue links are of type "https://issues.redhat.com/browse/RHEL-XXXX", where "X" is a digit). This same link will be available in a blue banner at the top of the page informing you that that bug has been migrated.
Bug 1751990 - DxeImageVerificationLib handles "DENY execute on security violation" like "DEFER execute on security violation" [rhel7]
Summary: DxeImageVerificationLib handles "DENY execute on security violation" like "DE...
Keywords:
Status: CLOSED DEFERRED
Alias: None
Product: Red Hat Enterprise Linux 7
Classification: Red Hat
Component: ovmf
Version: 7.7
Hardware: Unspecified
OS: Unspecified
low
low
Target Milestone: rc
: 7.8
Assignee: Laszlo Ersek
QA Contact: Xueqiang Wei
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2019-09-13 11:55 UTC by Laszlo Ersek
Modified: 2020-01-10 10:43 UTC (History)
4 users (show)

Fixed In Version:
Doc Type: If docs needed, set a value
Doc Text:
Clone Of:
: 1751993 (view as bug list)
Environment:
Last Closed: 2019-12-05 17:17:54 UTC
Target Upstream Version:
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
TianoCore 2129 0 None None None 2019-09-13 11:55:40 UTC

Description Laszlo Ersek 2019-09-13 11:55:41 UTC
* Description of problem:

- Launching a UEFI executable consists of two steps. First, the
  LoadImage() boot service is used to load the executable. Second, the
  StartImage() boot service is used to start the loaded executable.

- LoadImage() may return one of *two* error codes if Secure Boot
  verification fails for the executable being loaded: EFI_ACCESS_DENIED,
  or EFI_SECURITY_VIOLATION. The error code depends on firmware policy,
  as follows.

- If the firmware sets "*deny* execution on security violation", then
  EFI_ACCESS_DENIED is returned, and no allocated resources are retained
  for the image. In other words, the executable is never loaded.

- If the firmware sets "*defer* execution on security violation", then
  EFI_SECURITY_VIOLATION is returned, and the image *remains* loaded
  (with a number of other resources allocated). Here the intent is that
  the firmware may investigate the loaded image in platform-specific
  ways, and *override* the secure boot verification failure -- and
  finally start the image with StartImage().

- The issue is that "DxeImageVerificationLib", which is the component
  responsible for mapping the "deny" policy to EFI_ACCESS_DENIED, and
  "defer" to EFI_SECURITY_VIOLATION, has a bug. It maps "deny" to
  EFI_SECURITY_VIOLATION as well.

Consequences:

- If a firmware sets the "deny" policy, and (accordingly) does not
  specifically handle the EFI_SECURITY_VIOLATION return value from
  LoadImage(), it may *leak* loaded images that fail Secure Boot
  verification. The reason is that the firmware does not expect to have
  to call the UnloadImage() boot service in response to an *error* code
  returned by LoadImage().

- With the loaded image leaked, some resources may remain in RAM that
  could perhaps be utilized to mount a more complex attack. For example,
  a buffer overflow in another part of the firmware could be exploited
  for returning into executable code that is present in the leaked
  loaded image.

OVMF sets the "deny" policy for the following executable sources:

- executables loaded from removable media (such as network, CD-ROM)
- executables loaded from fixed media (such as hard disk)
- (in SEV guests only:) executables loaded from PCI(e) device ROM BARs
  (i.e., UEFI drivers loaded from PCI expansion ROMs)

* Version-Release number of selected component (if applicable):

ovmf-20180508-6.gitee3198e672e2.el7

* How reproducible:

Always, when enabling SB, and loading an unsigned or incorrectly signed
(or blacklisted) binary.

* Steps to Reproduce:

1. Define a new domain with SB enabled (using the "OVMF_VARS.secboot.fd"
   varstore template).

2. Attempt booting "UefiShell.iso".

3. Capture the UEFI console traffic on the serial port.

* Actual results:

> BdsDxe: loading Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]
> BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]: Security Violation

* Expected results:

> BdsDxe: loading Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]
> BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]: Access Denied

* Additional info:

It's not only the platform firmware that may call LoadImage(), and
therefore be susceptible to a leak on EFI_SECURITY_VIOLATION. UEFI boot
loaders would typically call LoadImage() too. Two examples (running in
guests):

(a) grub2:

- repository: https://github.com/rhboot/grub2
- branch:     master
- file:       grub-core/loader/efi/chainloader.c
- function:   grub_cmd_chainloader()

At commit 2a2e10c1b396, the code goes like:

>   status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path,
>                        boot_image, size,
>                        &image_handle);
>   if (status != GRUB_EFI_SUCCESS)
>     {
>       if (status == GRUB_EFI_OUT_OF_RESOURCES)
>         grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of resources");
>       else
>         grub_error (GRUB_ERR_BAD_OS, "cannot load image");
>
>       goto fail;
>     }

This code exhibits the leak. Even though the OVMF platform policy is set
to "deny execute on security violation", the LoadImage() boot service
will return EFI_SECURITY_VIOLATION rather than EFI_ACCESS_DENIED, in
case secure boot verification fails for the UEFI binary loaded for the
"chainloader" command. Then the above grub2 code will leak
"image_handle".

grub_cmd_chainloader() handles the "chainloader" command, which is put
to use in RHEL7 for example in "/etc/grub.d/30_os-prober", for
chain-loading UEFI boot loaders of other operating systems.

(b) systemd-boot (known earlier as "gummiboot"):

It is available in RHEL8 guests, for example: the "systemd-udev" package
provides:

  /usr/lib/systemd/boot/efi/systemd-bootx64.efi

AIUI this is an alternative to grub2, and like grub2, it can be
installed on the EFI system partition.

The source code is at:

- repository: https://github.com/systemd/systemd.git
- branch:     master
- file:       src/boot/efi/boot.c
- function:   image_start()

At commit 26fe3af8ae7d, we have

>         err = uefi_call_wrapper(BS->LoadImage, 6, FALSE, parent_image, path, NULL, 0, &image);
>         if (EFI_ERROR(err)) {
>                 Print(L"Error loading %s: %r", entry->loader, err);
>                 uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
>                 return err;
>         }

Which is another affected call site -- it leaks "image" if "err" is
EFI_SECURITY_VIOLATION.


The applications and the firmware are both at fault here. The
applications have bugs; they do not handle EFI_SECURITY_VIOLATION from
LoadImage() the way the UEFI spec requires. On the other hand, edk2 is
at fault too, because in a "DENY execute on security violation" policy
(as opposed to "DEFER execute on security violation" policy),
EFI_SECURITY_VIOLATION should never be returned, only EFI_ACCESS_DENIED
should. (OVMF configures the DENY policy, not the DEFER policy.) And
with the EFI_ACCESS_DENIED error code, there is no leak, regardless of
application-level error handling.

Other UEFI applications (e.g. custom ones that customers run) could
invoke LoadImage() too.


Once the upstream bug (TianoCore#2129) is fixed, we should backport the
patches, so that in OVMF, LoadImage() only return EFI_ACCESS_DENIED on
SB verification failure. This will prevent leaks regardless of whether
the LoadImage() caller handles EFI_SECURITY_VIOLATION specifically.

Comment 2 Laszlo Ersek 2019-09-13 12:18:30 UTC
(In reply to Laszlo Ersek from comment #0)

> * Actual results:
>
> > BdsDxe: loading Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]
> > BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]: Security Violation
>
> * Expected results:
>
> > BdsDxe: loading Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]
> > BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU CD-ROM " from PciRoot(0x0)/[...]: Access Denied

Actually, "ovmf-20180508-6.gitee3198e672e2.el7" does not print these
messages to the UEFI console -- because, in the end, I didn't backport
the usptream patch series linked in
<https://bugzilla.redhat.com/show_bug.cgi?id=1515418#c30> to RHEL7.

(See also <https://bugzilla.redhat.com/show_bug.cgi?id=1515418#c27>.)

The issue reported in the present BZ exists nonetheless, it's just not
as easy to see on the UEFI console as it is with edk2-ovmf from RHEL8
(with which the above messages are printed).

For RHEL7, I guess we'll just go with Secure Boot sanity testing then.


Note You need to log in before you can comment on or make changes to this bug.