Hi Boyang,
On Thu, Dec 22, 2022 at 10:37:01PM +0800, Zhang Boyang wrote:
[...]
On 2022/12/22 19:14, Leo Yan wrote:
When boot from menu and the flag GRUB_VERIFY_FLAGS_DEFER_AUTH is set,
grub returns error:
Booting a command list
error: verification requested but nobody cares: (hd0,gpt1)/Image.
Press any key to continue...
In this case, the image should be deferred for authentication, grub
should return the file handle and pass down to later firmware (e.g.
U-Boot, etc) for authentication.
This is probably not what verification framework designed to be. It seems to
be designed to verify files during GRUB is executing (e.g. check file
signature if UEFI Secure Boot is enabled).
Good point. We expect the solution is grub can defer authentication for
an image and invokes EFI LoadImage service, then EFI loader can load
and verify the image.
Since you mentioned "authentication" and "verify", I guess you are
using/implementing some kind of secure boot mechanism. Is it an standard
UEFI Secure Boot implementation?
I don't read specification for standard UEFI secure boot, but from my
very limited understanding, I don't think now I am using a standard
UEFI secure boot sequence.
Anyway, I will explain more in below comments.
The standard UEFI Secure Boot for Linux works like this (at least on x86):
1) Firmware verifies and loads shim (shimx64.efi), which is signed by
Microsoft. (https://github.com/rhboot/shim )
2) shim registers an EFI protocol, to provide an API for verifying files.
3) shim verifies and loads GRUB (grubx64.efi), using certificate embedded in
itself. The certificate is generated by vendor (e.g. Debian).
4) GRUB opens kernel image file and loads and executes that file. The key
point is, during grub_file_open() of the kernel image, the verifier
framework will call shim lock verifier, which calls the API provided by
shim, to verify the signature of kernel file. (grub-core/kern/efi/sb.c)
In the above example, the kernel is loaded by GRUB, not shim, not EFI
firmware. GRUB calls the API provided by shim to verify the kernel.
Another example is GRUB can also chainload Windows:
1) 2) 3) is same
4) GRUB invokes EFI firmware's LoadFile() and StartImage(). The verification
is completely delegated to EFI firmware. shim is not envolved.
In this example, the verification is done by EFI firmware, and GRUB/shim has
no control of it. You can't even chainload GRUB itself because it's not
signed by Microsoft.
Very good summary, thanks a lot!
For more specific, now I am debugging U-boot EFI with grub, since U-boot
EFI provides functionality for loading and authentication image (see
efi_load_image() in [1]), this is my purpose to use U-boot EFI to
authenticate kernel image (and even for initrd image).
It seems efi_load_image() is just a wrapper of EFI firmware's LoadFile() and
there is no implementation of verification in U-boot?
This isn't right. U-boot EFI service verifies image with below flow:
efi_load_image()
`> efi_load_pe()
`> efi_image_authenticate()
I'd like to ask what kind of image you are trying to boot/execute? If it is
an EFI application, I think you can "chainloader" it and forget U-boot (if
U-boot have no verification in it self).
We want to create a trust of chian:
firmware -> U-boot -> GRUB -> Linux kernel image / initrd / grub.cfg
Comparing against you mentioned solution which uses shim to verify and
load GRUB, we use U-boot to verify and load GRUB. Therefore, in our
implementation we don't use shim verifier for GRUB, and not for later
Linux kernel image / initrd / GRUB config files.
Let me guess: You are using standard Secure Boot, and want to boot linux
kernel image directly signed by keys in EFI firmware, and your GRUB is
installed with --disable-shim-lock. If this is what you are doing, you might
run into the following situation:
1) During GRUB init, grub_efi_init() detects Secure Boot is enabled, so it
calls grub_lockdown(). [grub-core/kern/efi/init.c]
2) grub_lockdown() registers lockdown verifier. [grub-core/kern/lockdown.c]
3) grub_efi_init() also calls grub_shim_lock_verifier_setup(). However,
grub_shim_lock_verifier_setup() does nothing because of
"--disable-shim-lock" [grub-core/kern/efi/sb.c]
4) During GRUB load linux kernel, the lockdown verifier sets a flag
(GRUB_VERIFY_FLAGS_DEFER_AUTH) for kernel file, and expect shim lock
verifier to do the verification.
5) However, there is no shim lock verifier. As a security measure, it
reports the error "verification requested but nobody cares".
So the root cause seems like --disable-shim-lock is broken?
Excellent analysis, and it's exactly an issue I am facing.
As a workaround, you can use shim: build shim for you platform and install
GRUB with shim support.
You can also submit a fix to --disable-shim-lock (recommended). However it
should be done very carefully. I'm afraid you can't remove the security
measure (i.e. the "verification requested but nobody cares") directly. I
think it is a good idea to add an EFI verifier (like shim verifier), which
use LoadImage() to do verification, and enable it if (and only if)
--disable-shim-lock is specified. You can talk to GRUB maintainers about
your proposal before coding.
Thanks a lot for the suggestion. As you proposed to add an EFI
verifier, this would be a candidate solution we should consider
seriously, seems to me, this is a neat way to allow us to hook the
verifier in U-boot and GRUB.
However, I found actually we have another choice (though it's not neat
but it does work after I did some experiments). We can enable GRUB's
internal verifier 'gpg', thus we can sign grub.cfg / kernel image
/ initrd with the gpg key and GRUB will use 'gpg' verifier to load
these images, please note, we sign these files with detached format so
every file has a corresponding signature file with suffix '.sig'.
The tricky thing is the kernel image, since GRUB invokes EFI LoadImage()
to load kernel image, and the kernel image has a self-contained
signature, so finally the kernel image also will be authenticated by
U-boot's EFI service. This means the kernel image will be
authenticated for twice, one is by GRUB's verifier and another is by
U-boot's efi_load_image().
At least it's a pragmatic solution for me, I will check internally if we
should move forward for using a central place (like U-boot) for all files
authentication and it also can simplify the key management.
The above is what I guess about what's happening. It might be wrong. Please
point out if there is something wrong.
I would like to step back a bit and ask a question: if GRUB doesn't
enable any crypto verifier and it runs into grub_verifiers_open(), in
current code grub_verifiers_open() prevents to load kernel image. In
this case, here I am confused why we cannot proceed to load kernel
image? Or I want to check with maintainers if this patch is still
valid :)