diff mbox series

efi_loader: tighten PE verification code?

Message ID 20200508185637.GA4433@nox.fritz.box
State New
Headers show
Series efi_loader: tighten PE verification code? | expand

Commit Message

Patrick Wildt May 8, 2020, 6:56 p.m. UTC
Hi,

even though this mail has a diff, it's not supposed to be a patch.  I
have started adjusting my fuzzer to the upstreamed EFI Secure Boot code
and I hit a few issues right away.  Two of those I have already sent and
were reviewed.  I have seen more, but since I needed to reschedule some
things I couldn't continue and unfortunately have to postpone fuzzing
the code.  I can assure you if someone starts fuzzing that code, we will
find a few more bugs.

Especially since this is "Secure Boot", this part should definitely be
air tight.

One thing I saw is that the virt_size is smaller then the memcpy below
at the "Copy PE headers" line then actually copies.

Another thing I saw is SizeOfRawData can be bigger then the VirtualSize,
and PointerToRawData + SizeOfRawData bigger then the allocated size.

I'm not sure if this is the right place to do the checks.  Maybe they
need to be at the place where we are adding the image regions.  I guess
the fields should be properly verified in view of "does it make sense?".

Also I'm questioning whether it's a good idea to continue parsing the
file if it's already clear that the signature can't be verified against
the "db".  I can understand that you'd like to finish loading the file
into an object, and some other instance decides whether or not it's fine
to start that image, but you also open yourself to issues when you're
parsing a file that obviously is against the current security policy.

If you keep on parsing a file that obviously (because tested against the
"db") cannot be allowed to boot anyway, the checks for the headers need
to be even tighter.

I'd be unhappy to see U-Boot CVEs come up regarding PE parsing issues.

Best regards,
Patrick

Comments

Simon Glass May 10, 2020, 8:36 p.m. UTC | #1
Hi Patrick,

On Fri, 8 May 2020 at 12:56, Patrick Wildt <patrick at blueri.se> wrote:
>
> Hi,
>
> even though this mail has a diff, it's not supposed to be a patch.  I
> have started adjusting my fuzzer to the upstreamed EFI Secure Boot code
> and I hit a few issues right away.  Two of those I have already sent and
> were reviewed.  I have seen more, but since I needed to reschedule some
> things I couldn't continue and unfortunately have to postpone fuzzing
> the code.  I can assure you if someone starts fuzzing that code, we will
> find a few more bugs.
>
> Especially since this is "Secure Boot", this part should definitely be
> air tight.
>
> One thing I saw is that the virt_size is smaller then the memcpy below
> at the "Copy PE headers" line then actually copies.
>
> Another thing I saw is SizeOfRawData can be bigger then the VirtualSize,
> and PointerToRawData + SizeOfRawData bigger then the allocated size.
>
> I'm not sure if this is the right place to do the checks.  Maybe they
> need to be at the place where we are adding the image regions.  I guess
> the fields should be properly verified in view of "does it make sense?".
>
> Also I'm questioning whether it's a good idea to continue parsing the
> file if it's already clear that the signature can't be verified against
> the "db".  I can understand that you'd like to finish loading the file
> into an object, and some other instance decides whether or not it's fine
> to start that image, but you also open yourself to issues when you're
> parsing a file that obviously is against the current security policy.
>
> If you keep on parsing a file that obviously (because tested against the
> "db") cannot be allowed to boot anyway, the checks for the headers need
> to be even tighter.
>
> I'd be unhappy to see U-Boot CVEs come up regarding PE parsing issues.
>
> Best regards,
> Patrick

Can I suggest adding some more unit tests?

Regards,
Simon
Patrick Wildt May 10, 2020, 9 p.m. UTC | #2
On Sun, May 10, 2020 at 02:36:51PM -0600, Simon Glass wrote:
> Hi Patrick,
> 
> On Fri, 8 May 2020 at 12:56, Patrick Wildt <patrick at blueri.se> wrote:
> >
> > Hi,
> >
> > even though this mail has a diff, it's not supposed to be a patch.  I
> > have started adjusting my fuzzer to the upstreamed EFI Secure Boot code
> > and I hit a few issues right away.  Two of those I have already sent and
> > were reviewed.  I have seen more, but since I needed to reschedule some
> > things I couldn't continue and unfortunately have to postpone fuzzing
> > the code.  I can assure you if someone starts fuzzing that code, we will
> > find a few more bugs.
> >
> > Especially since this is "Secure Boot", this part should definitely be
> > air tight.
> >
> > One thing I saw is that the virt_size is smaller then the memcpy below
> > at the "Copy PE headers" line then actually copies.
> >
> > Another thing I saw is SizeOfRawData can be bigger then the VirtualSize,
> > and PointerToRawData + SizeOfRawData bigger then the allocated size.
> >
> > I'm not sure if this is the right place to do the checks.  Maybe they
> > need to be at the place where we are adding the image regions.  I guess
> > the fields should be properly verified in view of "does it make sense?".
> >
> > Also I'm questioning whether it's a good idea to continue parsing the
> > file if it's already clear that the signature can't be verified against
> > the "db".  I can understand that you'd like to finish loading the file
> > into an object, and some other instance decides whether or not it's fine
> > to start that image, but you also open yourself to issues when you're
> > parsing a file that obviously is against the current security policy.
> >
> > If you keep on parsing a file that obviously (because tested against the
> > "db") cannot be allowed to boot anyway, the checks for the headers need
> > to be even tighter.
> >
> > I'd be unhappy to see U-Boot CVEs come up regarding PE parsing issues.
> >
> > Best regards,
> > Patrick
> 
> Can I suggest adding some more unit tests?
> 
> Regards,
> Simon

Hi,

It's a good suggestion.  Now it only needs someone with enough time to
actually do it. :-)  I'll come back to this in a few weeks when my
current project becomes quieter.  I wish I could right now.

Regards,
Patrick
AKASHI Takahiro May 11, 2020, 2:27 a.m. UTC | #3
Patrick,

On Fri, May 08, 2020 at 08:56:37PM +0200, Patrick Wildt wrote:
> Hi,
> 
> even though this mail has a diff, it's not supposed to be a patch.  I
> have started adjusting my fuzzer to the upstreamed EFI Secure Boot code
> and I hit a few issues right away.  Two of those I have already sent and
> were reviewed.  I have seen more, but since I needed to reschedule some
> things I couldn't continue and unfortunately have to postpone fuzzing
> the code.  I can assure you if someone starts fuzzing that code, we will
> find a few more bugs.

Thank you for examining the code.

> Especially since this is "Secure Boot", this part should definitely be
> air tight.

Yeah, we definitely need more eyes on the code.

> One thing I saw is that the virt_size is smaller then the memcpy below
> at the "Copy PE headers" line then actually copies.
> 
> Another thing I saw is SizeOfRawData can be bigger then the VirtualSize,
> and PointerToRawData + SizeOfRawData bigger then the allocated size.
> 
> I'm not sure if this is the right place to do the checks.  Maybe they
> need to be at the place where we are adding the image regions.  I guess
> the fields should be properly verified in view of "does it make sense?".
>
> Also I'm questioning whether it's a good idea to continue parsing the
> file if it's already clear that the signature can't be verified against
> the "db".  I can understand that you'd like to finish loading the file
> into an object, and some other instance decides whether or not it's fine
> to start that image, but you also open yourself to issues when you're
> parsing a file that obviously is against the current security policy.

One comment:
I have changed the logic in a past version when Heinrich pointed
out that we should return EFI_SECURITY_VIOLATION and that image
execution information table must also be supported.

"Status Codes Returned" by LoadImage API in UEFI specification says,

EFI_ACCESS_DENIED:Image was not loaded because the platform policy prohibits
                  the image from being loaded. NULL is returned in *ImageHandle.
EFI_SECURITY_VIOLATION:Image was loaded and an ImageHandle was created with
                  a valid EFI_LOADED_IMAGE_PROTOCOL. However, the current
                  platform policy specifies that the image should not be
                  started.

Now the code returns EFI_SECURITY_VIOLATION, instead of EFI_ACCESS_DENIED,
when image's signature can not be verified but yet the image itself will be
loaded.
When invoking the binary with StartImage API, it fails and put a record
in image execution information table.
(I have not yet submitted a patch for table support though.)

As UEFI specification doesn't say anything about "policy,"
I don't know which error code should be returned here.

-Takahiro Akashi

> If you keep on parsing a file that obviously (because tested against the
> "db") cannot be allowed to boot anyway, the checks for the headers need
> to be even tighter.
> 
> I'd be unhappy to see U-Boot CVEs come up regarding PE parsing issues.
> 
> Best regards,
> Patrick
> 
> diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c
> index 43a53d3dd1..d134b748be 100644
> --- a/lib/efi_loader/efi_image_loader.c
> +++ b/lib/efi_loader/efi_image_loader.c
> @@ -681,10 +681,11 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
>  	}
>  
>  	/* Authenticate an image */
> -	if (efi_image_authenticate(efi, efi_size))
> -		handle->auth_status = EFI_IMAGE_AUTH_PASSED;
> -	else
> +	if (!efi_image_authenticate(efi, efi_size)) {
>  		handle->auth_status = EFI_IMAGE_AUTH_FAILED;
> +		ret = EFI_SECURITY_VIOLATION;
> +		goto err;
> +	}
>  
>  	/* Calculate upper virtual address boundary */
>  	for (i = num_sections - 1; i >= 0; i--) {
> @@ -736,6 +737,15 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
>  		goto err;
>  	}
>  
> +	if (virt_size < sizeof(*dos) + sizeof(*nt) +
> +	    nt->FileHeader.SizeOfOptionalHeader +
> +	    num_sections * sizeof(IMAGE_SECTION_HEADER)) {
> +		efi_free_pages((uintptr_t) efi_reloc,
> +		       (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT);
> +		ret = EFI_LOAD_ERROR;
> +		goto err;
> +	}
> +
>  	/* Copy PE headers */
>  	memcpy(efi_reloc, efi,
>  	       sizeof(*dos)
> @@ -746,6 +756,13 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
>  	/* Load sections into RAM */
>  	for (i = num_sections - 1; i >= 0; i--) {
>  		IMAGE_SECTION_HEADER *sec = &sections[i];
> +		if (sec->SizeOfRawData > sec->Misc.VirtualSize ||
> +		    sec->PointerToRawData + sec->SizeOfRawData > efi_size) {
> +			efi_free_pages((uintptr_t) efi_reloc,
> +			       (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT);
> +			ret = EFI_LOAD_ERROR;
> +			goto err;
> +		}
>  		memset(efi_reloc + sec->VirtualAddress, 0,
>  		       sec->Misc.VirtualSize);
>  		memcpy(efi_reloc + sec->VirtualAddress,
> @@ -771,10 +788,8 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
>  	loaded_image_info->image_base = efi_reloc;
>  	loaded_image_info->image_size = virt_size;
>  
> -	if (handle->auth_status == EFI_IMAGE_AUTH_PASSED)
> -		return EFI_SUCCESS;
> -	else
> -		return EFI_SECURITY_VIOLATION;
> +	handle->auth_status = EFI_IMAGE_AUTH_PASSED;
> +	return EFI_SUCCESS;
>  
>  err:
>  	return ret;
diff mbox series

Patch

diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c
index 43a53d3dd1..d134b748be 100644
--- a/lib/efi_loader/efi_image_loader.c
+++ b/lib/efi_loader/efi_image_loader.c
@@ -681,10 +681,11 @@  efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
 	}
 
 	/* Authenticate an image */
-	if (efi_image_authenticate(efi, efi_size))
-		handle->auth_status = EFI_IMAGE_AUTH_PASSED;
-	else
+	if (!efi_image_authenticate(efi, efi_size)) {
 		handle->auth_status = EFI_IMAGE_AUTH_FAILED;
+		ret = EFI_SECURITY_VIOLATION;
+		goto err;
+	}
 
 	/* Calculate upper virtual address boundary */
 	for (i = num_sections - 1; i >= 0; i--) {
@@ -736,6 +737,15 @@  efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
 		goto err;
 	}
 
+	if (virt_size < sizeof(*dos) + sizeof(*nt) +
+	    nt->FileHeader.SizeOfOptionalHeader +
+	    num_sections * sizeof(IMAGE_SECTION_HEADER)) {
+		efi_free_pages((uintptr_t) efi_reloc,
+		       (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT);
+		ret = EFI_LOAD_ERROR;
+		goto err;
+	}
+
 	/* Copy PE headers */
 	memcpy(efi_reloc, efi,
 	       sizeof(*dos)
@@ -746,6 +756,13 @@  efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
 	/* Load sections into RAM */
 	for (i = num_sections - 1; i >= 0; i--) {
 		IMAGE_SECTION_HEADER *sec = &sections[i];
+		if (sec->SizeOfRawData > sec->Misc.VirtualSize ||
+		    sec->PointerToRawData + sec->SizeOfRawData > efi_size) {
+			efi_free_pages((uintptr_t) efi_reloc,
+			       (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT);
+			ret = EFI_LOAD_ERROR;
+			goto err;
+		}
 		memset(efi_reloc + sec->VirtualAddress, 0,
 		       sec->Misc.VirtualSize);
 		memcpy(efi_reloc + sec->VirtualAddress,
@@ -771,10 +788,8 @@  efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle,
 	loaded_image_info->image_base = efi_reloc;
 	loaded_image_info->image_size = virt_size;
 
-	if (handle->auth_status == EFI_IMAGE_AUTH_PASSED)
-		return EFI_SUCCESS;
-	else
-		return EFI_SECURITY_VIOLATION;
+	handle->auth_status = EFI_IMAGE_AUTH_PASSED;
+	return EFI_SUCCESS;
 
 err:
 	return ret;