mirror of
				https://git.proxmox.com/git/fwupd
				synced 2025-11-04 10:27:26 +00:00 
			
		
		
		
	To do this mount all ESP partitions and check all the binaries there to see if they match any entries in the new dbx. If we applied the update when a hash matched, we would unintentially 'brick' the users machine, as the grub and shim binaries *have* to be updated first. This functionality does reimplement the PE hashing functionality found in sbsigntools and pesign. This was done for 4 main reasons: * There were some memory safety issues found when fuzzing random binaries * Executing the tools hundreds of times was a lot of overhead * Operating from a blob of immutable mmap'd memory is much faster * We only need a very small amount of functionality from both tools
		
			
				
	
	
		
			333 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: LGPL-2.1+
 | 
						|
 */
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
 | 
						|
#include "fu-common.h"
 | 
						|
#include "fu-efi-image.h"
 | 
						|
 | 
						|
struct _FuEfiImage {
 | 
						|
	GObject		 parent_instance;
 | 
						|
	gchar		*checksum;
 | 
						|
};
 | 
						|
 | 
						|
typedef struct {
 | 
						|
	gsize		 offset;
 | 
						|
	gsize		 size;
 | 
						|
	gchar		*name;
 | 
						|
} FuEfiImageRegion;
 | 
						|
 | 
						|
typedef struct __attribute__((packed)) {
 | 
						|
	guint32		 addr;
 | 
						|
	guint32		 size;
 | 
						|
} FuEfiImageDataDirEntry;
 | 
						|
 | 
						|
G_DEFINE_TYPE (FuEfiImage, fu_efi_image, G_TYPE_OBJECT)
 | 
						|
 | 
						|
#define _DOS_OFFSET_SIGNATURE			0x00
 | 
						|
#define _DOS_OFFSET_TO_PE_HEADER		0x3c
 | 
						|
 | 
						|
#define _PEI_OFFSET_SIGNATURE			0x00
 | 
						|
#define _PEI_OFFSET_MACHINE			0x04
 | 
						|
#define _PEI_OFFSET_NUMBER_OF_SECTIONS		0x06
 | 
						|
#define _PEI_OFFSET_OPTIONAL_HEADER_SIZE	0x14
 | 
						|
#define _PEI_HEADER_SIZE			0x18
 | 
						|
 | 
						|
#define _PE_OFFSET_SIZE_OF_HEADERS		0x54
 | 
						|
#define _PE_OFFSET_CHECKSUM			0x58
 | 
						|
#define _PE_OFFSET_DEBUG_TABLE_OFFSET		0x98
 | 
						|
 | 
						|
#define _PEP_OFFSET_SIZE_OF_HEADERS		0x54
 | 
						|
#define _PEP_OFFSET_CHECKSUM			0x58
 | 
						|
#define _PEP_OFFSET_DEBUG_TABLE_OFFSET		0xa8
 | 
						|
 | 
						|
#define _SECTION_HEADER_OFFSET_NAME		0x0
 | 
						|
#define _SECTION_HEADER_OFFSET_SIZE		0x10
 | 
						|
#define _SECTION_HEADER_OFFSET_PTR		0x14
 | 
						|
#define _SECTION_HEADER_SIZE			0x28
 | 
						|
 | 
						|
#define IMAGE_FILE_MACHINE_AMD64		0x8664
 | 
						|
#define IMAGE_FILE_MACHINE_I386			0x014c
 | 
						|
#define IMAGE_FILE_MACHINE_THUMB		0x01c2
 | 
						|
#define IMAGE_FILE_MACHINE_AARCH64		0xaa64
 | 
						|
 | 
						|
static gint
 | 
						|
fu_efi_image_region_sort_cb (gconstpointer a, gconstpointer b)
 | 
						|
{
 | 
						|
	const FuEfiImageRegion *r1 = *((const FuEfiImageRegion **) a);
 | 
						|
	const FuEfiImageRegion *r2 = *((const FuEfiImageRegion **) b);
 | 
						|
	if (r1->offset < r2->offset)
 | 
						|
		return -1;
 | 
						|
	if (r1->offset > r2->offset)
 | 
						|
		return 1;
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static FuEfiImageRegion *
 | 
						|
fu_efi_image_add_region (GPtrArray *checksum_regions,
 | 
						|
			 const gchar *name,
 | 
						|
			 gsize offset_start,
 | 
						|
			 gsize offset_end)
 | 
						|
{
 | 
						|
	FuEfiImageRegion *r = g_new0 (FuEfiImageRegion, 1);
 | 
						|
	r->name = g_strdup (name);
 | 
						|
	r->offset = offset_start;
 | 
						|
	r->size = offset_end - offset_start;
 | 
						|
	g_ptr_array_add (checksum_regions, r);
 | 
						|
	return r;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_efi_image_region_free (FuEfiImageRegion *r)
 | 
						|
{
 | 
						|
	g_free (r->name);
 | 
						|
	g_free (r);
 | 
						|
}
 | 
						|
 | 
						|
FuEfiImage *
 | 
						|
fu_efi_image_new (GBytes *data, GError **error)
 | 
						|
{
 | 
						|
	FuEfiImageRegion *r;
 | 
						|
	const guint8 *buf;
 | 
						|
	gsize bufsz;
 | 
						|
	gsize image_bytes = 0;
 | 
						|
	gsize checksum_offset;
 | 
						|
	gsize data_dir_debug_offset;
 | 
						|
	gsize offset_tmp;
 | 
						|
	guint16 dos_sig = 0;
 | 
						|
	guint16 machine = 0;
 | 
						|
	guint16 opthdrsz;
 | 
						|
	guint16 sections;
 | 
						|
	guint32 baseaddr = 0;
 | 
						|
	guint32 cert_table_size;
 | 
						|
	guint32 header_size;
 | 
						|
	guint32 nt_sig = 0;
 | 
						|
	g_autoptr(FuEfiImage) self = g_object_new (FU_TYPE_EFI_IMAGE, NULL);
 | 
						|
	g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256);
 | 
						|
	g_autoptr(GPtrArray) checksum_regions = NULL;
 | 
						|
 | 
						|
	/* verify this is a DOS file */
 | 
						|
	buf = g_bytes_get_data (data, &bufsz);
 | 
						|
	if (!fu_common_read_uint16_safe (buf, bufsz,
 | 
						|
					 _DOS_OFFSET_SIGNATURE,
 | 
						|
					 &dos_sig, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	if (dos_sig != 0x5a4d) {
 | 
						|
		g_set_error (error,
 | 
						|
			     G_IO_ERROR,
 | 
						|
			     G_IO_ERROR_NOT_SUPPORTED,
 | 
						|
			     "Invalid DOS header magic %04x", dos_sig);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* verify the PE signature */
 | 
						|
	if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
					 _DOS_OFFSET_TO_PE_HEADER,
 | 
						|
					 &baseaddr, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
					 baseaddr + _PEI_OFFSET_SIGNATURE,
 | 
						|
					 &nt_sig, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	if (nt_sig != 0x4550) {
 | 
						|
		g_set_error (error,
 | 
						|
			     G_IO_ERROR,
 | 
						|
			     G_IO_ERROR_NOT_SUPPORTED,
 | 
						|
			     "Invalid PE header signature %08x", nt_sig);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* which machine type are we reading */
 | 
						|
	if (!fu_common_read_uint16_safe (buf, bufsz,
 | 
						|
					 baseaddr + _PEI_OFFSET_MACHINE,
 | 
						|
					 &machine, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	if (machine == IMAGE_FILE_MACHINE_AMD64 ||
 | 
						|
	    machine == IMAGE_FILE_MACHINE_AARCH64) {
 | 
						|
 | 
						|
		/* a.out header directly follows PE header */
 | 
						|
		if (!fu_common_read_uint16_safe (buf, bufsz,
 | 
						|
						 baseaddr + _PEI_HEADER_SIZE,
 | 
						|
						 &machine, G_LITTLE_ENDIAN, error))
 | 
						|
			return NULL;
 | 
						|
		if (machine != 0x020b) {
 | 
						|
			g_set_error (error,
 | 
						|
				     G_IO_ERROR,
 | 
						|
				     G_IO_ERROR_NOT_SUPPORTED,
 | 
						|
				     "Invalid a.out machine type %04x", machine);
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
		if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
						 baseaddr + _PEP_OFFSET_SIZE_OF_HEADERS,
 | 
						|
						 &header_size, G_LITTLE_ENDIAN, error))
 | 
						|
			return NULL;
 | 
						|
 | 
						|
		checksum_offset = baseaddr + _PEP_OFFSET_CHECKSUM;
 | 
						|
 | 
						|
		/* now, this is odd. sbsigntools seems to think that we're
 | 
						|
		 * skipping the CertificateTable -- but we actually seems to be
 | 
						|
		 * ignoring Debug instead */
 | 
						|
		data_dir_debug_offset = baseaddr + _PEP_OFFSET_DEBUG_TABLE_OFFSET;
 | 
						|
 | 
						|
	} else if (machine == IMAGE_FILE_MACHINE_I386 ||
 | 
						|
		   machine == IMAGE_FILE_MACHINE_THUMB) {
 | 
						|
 | 
						|
		/* a.out header directly follows PE header */
 | 
						|
		if (!fu_common_read_uint16_safe (buf, bufsz,
 | 
						|
						 baseaddr + _PEI_HEADER_SIZE,
 | 
						|
						 &machine, G_LITTLE_ENDIAN, error))
 | 
						|
			return NULL;
 | 
						|
		if (machine != 0x010b) {
 | 
						|
			g_set_error (error,
 | 
						|
				     G_IO_ERROR,
 | 
						|
				     G_IO_ERROR_NOT_SUPPORTED,
 | 
						|
				     "Invalid a.out machine type %04x", machine);
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
		if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
						 baseaddr + _PE_OFFSET_SIZE_OF_HEADERS,
 | 
						|
						 &header_size, G_LITTLE_ENDIAN, error))
 | 
						|
			return NULL;
 | 
						|
 | 
						|
		checksum_offset = baseaddr + _PE_OFFSET_CHECKSUM;
 | 
						|
		data_dir_debug_offset = baseaddr + _PE_OFFSET_DEBUG_TABLE_OFFSET;
 | 
						|
 | 
						|
	} else {
 | 
						|
		g_set_error (error,
 | 
						|
			     G_IO_ERROR,
 | 
						|
			     G_IO_ERROR_NOT_SUPPORTED,
 | 
						|
			     "Invalid PE header machine %04x", machine);
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* get sections */
 | 
						|
	if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
					 data_dir_debug_offset + sizeof(guint32),
 | 
						|
					 &cert_table_size, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	if (!fu_common_read_uint16_safe (buf, bufsz,
 | 
						|
					 baseaddr + _PEI_OFFSET_NUMBER_OF_SECTIONS,
 | 
						|
					 §ions, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	g_debug ("number_of_sections: %u", sections);
 | 
						|
 | 
						|
	/* get header size */
 | 
						|
	if (!fu_common_read_uint16_safe (buf, bufsz,
 | 
						|
					 baseaddr + _PEI_OFFSET_OPTIONAL_HEADER_SIZE,
 | 
						|
					 &opthdrsz, G_LITTLE_ENDIAN, error))
 | 
						|
		return NULL;
 | 
						|
	g_debug ("optional_header_size: 0x%x", opthdrsz);
 | 
						|
 | 
						|
	/* first region: beginning to checksum_offset field */
 | 
						|
	checksum_regions = g_ptr_array_new_with_free_func ((GDestroyNotify) fu_efi_image_region_free);
 | 
						|
	r = fu_efi_image_add_region (checksum_regions, "begin->cksum", 0x0, checksum_offset);
 | 
						|
	image_bytes += r->size + sizeof(guint32);
 | 
						|
 | 
						|
	/* second region: end of checksum_offset to certificate table entry */
 | 
						|
	r = fu_efi_image_add_region (checksum_regions, "cksum->datadir[DEBUG]",
 | 
						|
				     checksum_offset + sizeof(guint32),
 | 
						|
				     data_dir_debug_offset);
 | 
						|
	image_bytes += r->size + sizeof(FuEfiImageDataDirEntry);
 | 
						|
 | 
						|
	/* third region: end of checksum_offset to end of headers */
 | 
						|
	r = fu_efi_image_add_region (checksum_regions, "datadir[DEBUG]->headers",
 | 
						|
				     data_dir_debug_offset + sizeof(FuEfiImageDataDirEntry),
 | 
						|
				     header_size);
 | 
						|
	image_bytes += r->size;
 | 
						|
 | 
						|
	/* add COFF sections */
 | 
						|
	offset_tmp = baseaddr + _PEI_HEADER_SIZE + opthdrsz;
 | 
						|
	for (guint i = 0; i < sections; i++) {
 | 
						|
		guint32 file_offset = 0;
 | 
						|
		guint32 file_size = 0;
 | 
						|
		gchar name[9] = { '\0' };
 | 
						|
 | 
						|
		if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
						 offset_tmp + _SECTION_HEADER_OFFSET_PTR,
 | 
						|
						 &file_offset, G_LITTLE_ENDIAN, error))
 | 
						|
			return NULL;
 | 
						|
		if (!fu_common_read_uint32_safe (buf, bufsz,
 | 
						|
						 offset_tmp + _SECTION_HEADER_OFFSET_SIZE,
 | 
						|
						 &file_size, G_LITTLE_ENDIAN, error))
 | 
						|
			return NULL;
 | 
						|
		if (file_size == 0)
 | 
						|
			continue;
 | 
						|
		if (!fu_memcpy_safe ((guint8 *) name, sizeof(name), 0x0,	/* dst */
 | 
						|
				     buf, bufsz,
 | 
						|
				     offset_tmp + _SECTION_HEADER_OFFSET_NAME,	/* src */
 | 
						|
				     sizeof(name) - 1, error))
 | 
						|
			return NULL;
 | 
						|
		r = fu_efi_image_add_region (checksum_regions, name, file_offset, file_offset + file_size);
 | 
						|
		image_bytes += r->size;
 | 
						|
 | 
						|
		if (file_offset + r->size > bufsz) {
 | 
						|
			g_set_error (error,
 | 
						|
				     G_IO_ERROR,
 | 
						|
				     G_IO_ERROR_INVALID_DATA,
 | 
						|
				     "file-aligned section %s extends beyond end of file",
 | 
						|
				     r->name);
 | 
						|
			return NULL;
 | 
						|
		}
 | 
						|
		offset_tmp += _SECTION_HEADER_SIZE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* make sure in order */
 | 
						|
	g_ptr_array_sort (checksum_regions, fu_efi_image_region_sort_cb);
 | 
						|
 | 
						|
	/* for the data at the end of the image */
 | 
						|
	if (image_bytes + cert_table_size < bufsz) {
 | 
						|
		fu_efi_image_add_region (checksum_regions, "endjunk",
 | 
						|
					 image_bytes, bufsz - cert_table_size);
 | 
						|
	} else if (image_bytes + cert_table_size > bufsz) {
 | 
						|
		g_set_error (error,
 | 
						|
			     G_IO_ERROR,
 | 
						|
			     G_IO_ERROR_INVALID_DATA,
 | 
						|
			     "checksum_offset areas outside image size");
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* calculate the checksum we would find in the dbx */
 | 
						|
	for (guint i = 0; i < checksum_regions->len; i++) {
 | 
						|
		r = g_ptr_array_index (checksum_regions, i);
 | 
						|
		g_debug ("region %s: 0x%04x -> 0x%04x [0x%04x]",
 | 
						|
			 r->name,
 | 
						|
			 (guint) r->offset,
 | 
						|
			 (guint) (r->offset + r->size - 1),
 | 
						|
			 (guint) r->size);
 | 
						|
		g_checksum_update (checksum,
 | 
						|
				   (const guchar *) buf + r->offset,
 | 
						|
				   (gssize) r->size);
 | 
						|
	}
 | 
						|
	self->checksum = g_strdup (g_checksum_get_string (checksum));
 | 
						|
	return g_steal_pointer (&self);
 | 
						|
}
 | 
						|
 | 
						|
const gchar *
 | 
						|
fu_efi_image_get_checksum (FuEfiImage *self)
 | 
						|
{
 | 
						|
	return self->checksum;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_efi_image_finalize (GObject *obj)
 | 
						|
{
 | 
						|
	FuEfiImage *self = FU_EFI_IMAGE (obj);
 | 
						|
	g_free (self->checksum);
 | 
						|
	G_OBJECT_CLASS (fu_efi_image_parent_class)->finalize (obj);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_efi_image_class_init (FuEfiImageClass *klass)
 | 
						|
{
 | 
						|
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 | 
						|
	object_class->finalize = fu_efi_image_finalize;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fu_efi_image_init (FuEfiImage *self)
 | 
						|
{
 | 
						|
}
 |