mirror of
				https://git.proxmox.com/git/efi-boot-shim
				synced 2025-11-04 07:52:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1934 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1934 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: BSD-2-Clause-Patent
 | 
						|
 | 
						|
/*
 | 
						|
 * shim - trivial UEFI first-stage bootloader
 | 
						|
 *
 | 
						|
 * Copyright Red Hat, Inc
 | 
						|
 * Author: Matthew Garrett
 | 
						|
 *
 | 
						|
 * Significant portions of this code are derived from Tianocore
 | 
						|
 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
 | 
						|
 * Corporation.
 | 
						|
 */
 | 
						|
 | 
						|
#include "shim.h"
 | 
						|
#include "hexdump.h"
 | 
						|
#if defined(ENABLE_SHIM_CERT)
 | 
						|
#include "shim_cert.h"
 | 
						|
#endif /* defined(ENABLE_SHIM_CERT) */
 | 
						|
 | 
						|
#include <openssl/err.h>
 | 
						|
#include <openssl/bn.h>
 | 
						|
#include <openssl/dh.h>
 | 
						|
#include <openssl/ocsp.h>
 | 
						|
#include <openssl/pkcs12.h>
 | 
						|
#include <openssl/rand.h>
 | 
						|
#include <openssl/crypto.h>
 | 
						|
#include <openssl/ssl.h>
 | 
						|
#include <openssl/x509.h>
 | 
						|
#include <openssl/x509v3.h>
 | 
						|
#include <openssl/rsa.h>
 | 
						|
#include <openssl/dso.h>
 | 
						|
 | 
						|
#include <Library/BaseCryptLib.h>
 | 
						|
 | 
						|
#include <stdint.h>
 | 
						|
 | 
						|
#define OID_EKU_MODSIGN "1.3.6.1.4.1.2312.16.1.2"
 | 
						|
 | 
						|
static EFI_SYSTEM_TABLE *systab;
 | 
						|
static EFI_HANDLE global_image_handle;
 | 
						|
 | 
						|
static CHAR16 *second_stage;
 | 
						|
void *load_options;
 | 
						|
UINT32 load_options_size;
 | 
						|
 | 
						|
/*
 | 
						|
 * The vendor certificate used for validating the second stage loader
 | 
						|
 */
 | 
						|
extern struct {
 | 
						|
	UINT32 vendor_authorized_size;
 | 
						|
	UINT32 vendor_deauthorized_size;
 | 
						|
	UINT32 vendor_authorized_offset;
 | 
						|
	UINT32 vendor_deauthorized_offset;
 | 
						|
} cert_table;
 | 
						|
 | 
						|
UINT32 vendor_authorized_size = 0;
 | 
						|
UINT8 *vendor_authorized = NULL;
 | 
						|
 | 
						|
UINT32 vendor_deauthorized_size = 0;
 | 
						|
UINT8 *vendor_deauthorized = NULL;
 | 
						|
 | 
						|
#if defined(ENABLE_SHIM_CERT)
 | 
						|
UINT32 build_cert_size;
 | 
						|
UINT8 *build_cert;
 | 
						|
#endif /* defined(ENABLE_SHIM_CERT) */
 | 
						|
 | 
						|
/*
 | 
						|
 * indicator of how an image has been verified
 | 
						|
 */
 | 
						|
verification_method_t verification_method;
 | 
						|
int loader_is_participating;
 | 
						|
 | 
						|
#define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }}
 | 
						|
 | 
						|
UINT8 user_insecure_mode;
 | 
						|
UINT8 ignore_db;
 | 
						|
 | 
						|
typedef enum {
 | 
						|
	DATA_FOUND,
 | 
						|
	DATA_NOT_FOUND,
 | 
						|
	VAR_NOT_FOUND
 | 
						|
} CHECK_STATUS;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
	UINT32 MokSize;
 | 
						|
	UINT8 *Mok;
 | 
						|
} MokListNode;
 | 
						|
 | 
						|
static void
 | 
						|
drain_openssl_errors(void)
 | 
						|
{
 | 
						|
	unsigned long err = -1;
 | 
						|
	while (err != 0)
 | 
						|
		err = ERR_get_error();
 | 
						|
}
 | 
						|
 | 
						|
static BOOLEAN verify_x509(UINT8 *Cert, UINTN CertSize)
 | 
						|
{
 | 
						|
	UINTN length;
 | 
						|
 | 
						|
	if (!Cert || CertSize < 4)
 | 
						|
		return FALSE;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * A DER encoding x509 certificate starts with SEQUENCE(0x30),
 | 
						|
	 * the number of length bytes, and the number of value bytes.
 | 
						|
	 * The size of a x509 certificate is usually between 127 bytes
 | 
						|
	 * and 64KB. For convenience, assume the number of value bytes
 | 
						|
	 * is 2, i.e. the second byte is 0x82.
 | 
						|
	 */
 | 
						|
	if (Cert[0] != 0x30 || Cert[1] != 0x82) {
 | 
						|
		dprint(L"cert[0:1] is [%02x%02x], should be [%02x%02x]\n",
 | 
						|
		       Cert[0], Cert[1], 0x30, 0x82);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	length = Cert[2]<<8 | Cert[3];
 | 
						|
	if (length != (CertSize - 4)) {
 | 
						|
		dprint(L"Cert length is %ld, expecting %ld\n",
 | 
						|
		       length, CertSize);
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static BOOLEAN verify_eku(UINT8 *Cert, UINTN CertSize)
 | 
						|
{
 | 
						|
	X509 *x509;
 | 
						|
	CONST UINT8 *Temp = Cert;
 | 
						|
	EXTENDED_KEY_USAGE *eku;
 | 
						|
	ASN1_OBJECT *module_signing;
 | 
						|
 | 
						|
        module_signing = OBJ_nid2obj(OBJ_create(OID_EKU_MODSIGN,
 | 
						|
                                                "modsign-eku",
 | 
						|
                                                "modsign-eku"));
 | 
						|
 | 
						|
	x509 = d2i_X509 (NULL, &Temp, (long) CertSize);
 | 
						|
	if (x509 != NULL) {
 | 
						|
		eku = X509_get_ext_d2i(x509, NID_ext_key_usage, NULL, NULL);
 | 
						|
 | 
						|
		if (eku) {
 | 
						|
			int i = 0;
 | 
						|
			for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
 | 
						|
				ASN1_OBJECT *key_usage = sk_ASN1_OBJECT_value(eku, i);
 | 
						|
 | 
						|
				if (OBJ_cmp(module_signing, key_usage) == 0)
 | 
						|
					return FALSE;
 | 
						|
			}
 | 
						|
			EXTENDED_KEY_USAGE_free(eku);
 | 
						|
		}
 | 
						|
 | 
						|
		X509_free(x509);
 | 
						|
	}
 | 
						|
 | 
						|
	OBJ_cleanup();
 | 
						|
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static CHECK_STATUS check_db_cert_in_ram(EFI_SIGNATURE_LIST *CertList,
 | 
						|
					 UINTN dbsize,
 | 
						|
					 WIN_CERTIFICATE_EFI_PKCS *data,
 | 
						|
					 UINT8 *hash, CHAR16 *dbname,
 | 
						|
					 EFI_GUID guid)
 | 
						|
{
 | 
						|
	EFI_SIGNATURE_DATA *Cert;
 | 
						|
	UINTN CertSize;
 | 
						|
	BOOLEAN IsFound = FALSE;
 | 
						|
	int i = 0;
 | 
						|
 | 
						|
	while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) {
 | 
						|
		if (CompareGuid (&CertList->SignatureType, &EFI_CERT_TYPE_X509_GUID) == 0) {
 | 
						|
			Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize);
 | 
						|
			CertSize = CertList->SignatureSize - sizeof(EFI_GUID);
 | 
						|
			dprint(L"trying to verify cert %d (%s)\n", i++, dbname);
 | 
						|
			if (verify_x509(Cert->SignatureData, CertSize)) {
 | 
						|
				if (verify_eku(Cert->SignatureData, CertSize)) {
 | 
						|
					drain_openssl_errors();
 | 
						|
					IsFound = AuthenticodeVerify (data->CertData,
 | 
						|
								      data->Hdr.dwLength - sizeof(data->Hdr),
 | 
						|
								      Cert->SignatureData,
 | 
						|
								      CertSize,
 | 
						|
								      hash, SHA256_DIGEST_SIZE);
 | 
						|
					if (IsFound) {
 | 
						|
						dprint(L"AuthenticodeVerify() succeeded: %d\n", IsFound);
 | 
						|
						tpm_measure_variable(dbname, guid, CertSize, Cert->SignatureData);
 | 
						|
						drain_openssl_errors();
 | 
						|
						return DATA_FOUND;
 | 
						|
					} else {
 | 
						|
						LogError(L"AuthenticodeVerify(): %d\n", IsFound);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else if (verbose) {
 | 
						|
				console_print(L"Not a DER encoded x.509 Certificate");
 | 
						|
				dprint(L"cert:\n");
 | 
						|
				dhexdumpat(Cert->SignatureData, CertSize, 0);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		dbsize -= CertList->SignatureListSize;
 | 
						|
		CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize);
 | 
						|
	}
 | 
						|
 | 
						|
	return DATA_NOT_FOUND;
 | 
						|
}
 | 
						|
 | 
						|
static CHECK_STATUS check_db_cert(CHAR16 *dbname, EFI_GUID guid,
 | 
						|
				  WIN_CERTIFICATE_EFI_PKCS *data, UINT8 *hash)
 | 
						|
{
 | 
						|
	CHECK_STATUS rc;
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	EFI_SIGNATURE_LIST *CertList;
 | 
						|
	UINTN dbsize = 0;
 | 
						|
	UINT8 *db;
 | 
						|
 | 
						|
	efi_status = get_variable(dbname, &db, &dbsize, guid);
 | 
						|
	if (EFI_ERROR(efi_status))
 | 
						|
		return VAR_NOT_FOUND;
 | 
						|
 | 
						|
	CertList = (EFI_SIGNATURE_LIST *)db;
 | 
						|
 | 
						|
	rc = check_db_cert_in_ram(CertList, dbsize, data, hash, dbname, guid);
 | 
						|
 | 
						|
	FreePool(db);
 | 
						|
 | 
						|
	return rc;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check a hash against an EFI_SIGNATURE_LIST in a buffer
 | 
						|
 */
 | 
						|
static CHECK_STATUS check_db_hash_in_ram(EFI_SIGNATURE_LIST *CertList,
 | 
						|
					 UINTN dbsize, UINT8 *data,
 | 
						|
					 int SignatureSize, EFI_GUID CertType,
 | 
						|
					 CHAR16 *dbname, EFI_GUID guid)
 | 
						|
{
 | 
						|
	EFI_SIGNATURE_DATA *Cert;
 | 
						|
	UINTN CertCount, Index;
 | 
						|
	BOOLEAN IsFound = FALSE;
 | 
						|
 | 
						|
	while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) {
 | 
						|
		CertCount = (CertList->SignatureListSize -sizeof (EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize;
 | 
						|
		Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize);
 | 
						|
		if (CompareGuid(&CertList->SignatureType, &CertType) == 0) {
 | 
						|
			for (Index = 0; Index < CertCount; Index++) {
 | 
						|
				if (CompareMem (Cert->SignatureData, data, SignatureSize) == 0) {
 | 
						|
					//
 | 
						|
					// Find the signature in database.
 | 
						|
					//
 | 
						|
					IsFound = TRUE;
 | 
						|
					tpm_measure_variable(dbname, guid, SignatureSize, data);
 | 
						|
					break;
 | 
						|
				}
 | 
						|
 | 
						|
				Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize);
 | 
						|
			}
 | 
						|
			if (IsFound) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		dbsize -= CertList->SignatureListSize;
 | 
						|
		CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize);
 | 
						|
	}
 | 
						|
 | 
						|
	if (IsFound)
 | 
						|
		return DATA_FOUND;
 | 
						|
 | 
						|
	return DATA_NOT_FOUND;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check a hash against an EFI_SIGNATURE_LIST in a UEFI variable
 | 
						|
 */
 | 
						|
static CHECK_STATUS check_db_hash(CHAR16 *dbname, EFI_GUID guid, UINT8 *data,
 | 
						|
				  int SignatureSize, EFI_GUID CertType)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	EFI_SIGNATURE_LIST *CertList;
 | 
						|
	UINTN dbsize = 0;
 | 
						|
	UINT8 *db;
 | 
						|
 | 
						|
	efi_status = get_variable(dbname, &db, &dbsize, guid);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		return VAR_NOT_FOUND;
 | 
						|
	}
 | 
						|
 | 
						|
	CertList = (EFI_SIGNATURE_LIST *)db;
 | 
						|
 | 
						|
	CHECK_STATUS rc = check_db_hash_in_ram(CertList, dbsize, data,
 | 
						|
					       SignatureSize, CertType,
 | 
						|
					       dbname, guid);
 | 
						|
	FreePool(db);
 | 
						|
	return rc;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check whether the binary signature or hash are present in dbx or the
 | 
						|
 * built-in denylist
 | 
						|
 */
 | 
						|
static EFI_STATUS check_denylist (WIN_CERTIFICATE_EFI_PKCS *cert,
 | 
						|
				  UINT8 *sha256hash, UINT8 *sha1hash)
 | 
						|
{
 | 
						|
	EFI_SIGNATURE_LIST *dbx = (EFI_SIGNATURE_LIST *)vendor_deauthorized;
 | 
						|
 | 
						|
	if (check_db_hash_in_ram(dbx, vendor_deauthorized_size, sha256hash,
 | 
						|
			SHA256_DIGEST_SIZE, EFI_CERT_SHA256_GUID, L"dbx",
 | 
						|
			EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) {
 | 
						|
		LogError(L"binary sha256hash found in vendor dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (check_db_hash_in_ram(dbx, vendor_deauthorized_size, sha1hash,
 | 
						|
				 SHA1_DIGEST_SIZE, EFI_CERT_SHA1_GUID, L"dbx",
 | 
						|
				 EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) {
 | 
						|
		LogError(L"binary sha1hash found in vendor dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (cert &&
 | 
						|
	    check_db_cert_in_ram(dbx, vendor_deauthorized_size, cert, sha256hash, L"dbx",
 | 
						|
				 EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) {
 | 
						|
		LogError(L"cert sha256hash found in vendor dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (check_db_hash(L"dbx", EFI_SECURE_BOOT_DB_GUID, sha256hash,
 | 
						|
			  SHA256_DIGEST_SIZE, EFI_CERT_SHA256_GUID) == DATA_FOUND) {
 | 
						|
		LogError(L"binary sha256hash found in system dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (check_db_hash(L"dbx", EFI_SECURE_BOOT_DB_GUID, sha1hash,
 | 
						|
			  SHA1_DIGEST_SIZE, EFI_CERT_SHA1_GUID) == DATA_FOUND) {
 | 
						|
		LogError(L"binary sha1hash found in system dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (cert &&
 | 
						|
	    check_db_cert(L"dbx", EFI_SECURE_BOOT_DB_GUID,
 | 
						|
			  cert, sha256hash) == DATA_FOUND) {
 | 
						|
		LogError(L"cert sha256hash found in system dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (check_db_hash(L"MokListX", SHIM_LOCK_GUID, sha256hash,
 | 
						|
			  SHA256_DIGEST_SIZE, EFI_CERT_SHA256_GUID) == DATA_FOUND) {
 | 
						|
		LogError(L"binary sha256hash found in Mok dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	if (cert &&
 | 
						|
	    check_db_cert(L"MokListX", SHIM_LOCK_GUID,
 | 
						|
			  cert, sha256hash) == DATA_FOUND) {
 | 
						|
		LogError(L"cert sha256hash found in Mok dbx\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
 | 
						|
	drain_openssl_errors();
 | 
						|
	return EFI_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static void update_verification_method(verification_method_t method)
 | 
						|
{
 | 
						|
	if (verification_method == VERIFIED_BY_NOTHING)
 | 
						|
		verification_method = method;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check whether the binary signature or hash are present in db or MokList
 | 
						|
 */
 | 
						|
static EFI_STATUS check_allowlist (WIN_CERTIFICATE_EFI_PKCS *cert,
 | 
						|
				   UINT8 *sha256hash, UINT8 *sha1hash)
 | 
						|
{
 | 
						|
	if (!ignore_db) {
 | 
						|
		if (check_db_hash(L"db", EFI_SECURE_BOOT_DB_GUID, sha256hash, SHA256_DIGEST_SIZE,
 | 
						|
					EFI_CERT_SHA256_GUID) == DATA_FOUND) {
 | 
						|
			update_verification_method(VERIFIED_BY_HASH);
 | 
						|
			return EFI_SUCCESS;
 | 
						|
		} else {
 | 
						|
			LogError(L"check_db_hash(db, sha256hash) != DATA_FOUND\n");
 | 
						|
		}
 | 
						|
		if (check_db_hash(L"db", EFI_SECURE_BOOT_DB_GUID, sha1hash, SHA1_DIGEST_SIZE,
 | 
						|
					EFI_CERT_SHA1_GUID) == DATA_FOUND) {
 | 
						|
			verification_method = VERIFIED_BY_HASH;
 | 
						|
			update_verification_method(VERIFIED_BY_HASH);
 | 
						|
			return EFI_SUCCESS;
 | 
						|
		} else {
 | 
						|
			LogError(L"check_db_hash(db, sha1hash) != DATA_FOUND\n");
 | 
						|
		}
 | 
						|
		if (cert && check_db_cert(L"db", EFI_SECURE_BOOT_DB_GUID, cert, sha256hash)
 | 
						|
					== DATA_FOUND) {
 | 
						|
			verification_method = VERIFIED_BY_CERT;
 | 
						|
			update_verification_method(VERIFIED_BY_CERT);
 | 
						|
			return EFI_SUCCESS;
 | 
						|
		} else if (cert) {
 | 
						|
			LogError(L"check_db_cert(db, sha256hash) != DATA_FOUND\n");
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
#if defined(VENDOR_DB_FILE)
 | 
						|
	EFI_SIGNATURE_LIST *db = (EFI_SIGNATURE_LIST *)vendor_db;
 | 
						|
 | 
						|
	if (check_db_hash_in_ram(db, vendor_db_size,
 | 
						|
				 sha256hash, SHA256_DIGEST_SIZE,
 | 
						|
				 EFI_CERT_SHA256_GUID, L"vendor_db",
 | 
						|
				 EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) {
 | 
						|
		verification_method = VERIFIED_BY_HASH;
 | 
						|
		update_verification_method(VERIFIED_BY_HASH);
 | 
						|
		return EFI_SUCCESS;
 | 
						|
	} else {
 | 
						|
		LogError(L"check_db_hash(vendor_db, sha256hash) != DATA_FOUND\n");
 | 
						|
	}
 | 
						|
	if (cert &&
 | 
						|
	    check_db_cert_in_ram(db, vendor_db_size,
 | 
						|
				 cert, sha256hash, L"vendor_db",
 | 
						|
				 EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) {
 | 
						|
		verification_method = VERIFIED_BY_CERT;
 | 
						|
		update_verification_method(VERIFIED_BY_CERT);
 | 
						|
		return EFI_SUCCESS;
 | 
						|
	} else if (cert) {
 | 
						|
		LogError(L"check_db_cert(vendor_db, sha256hash) != DATA_FOUND\n");
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
	if (check_db_hash(L"MokList", SHIM_LOCK_GUID, sha256hash,
 | 
						|
			  SHA256_DIGEST_SIZE, EFI_CERT_SHA256_GUID)
 | 
						|
				== DATA_FOUND) {
 | 
						|
		verification_method = VERIFIED_BY_HASH;
 | 
						|
		update_verification_method(VERIFIED_BY_HASH);
 | 
						|
		return EFI_SUCCESS;
 | 
						|
	} else {
 | 
						|
		LogError(L"check_db_hash(MokList, sha256hash) != DATA_FOUND\n");
 | 
						|
	}
 | 
						|
	if (cert && check_db_cert(L"MokList", SHIM_LOCK_GUID, cert, sha256hash)
 | 
						|
			== DATA_FOUND) {
 | 
						|
		verification_method = VERIFIED_BY_CERT;
 | 
						|
		update_verification_method(VERIFIED_BY_CERT);
 | 
						|
		return EFI_SUCCESS;
 | 
						|
	} else if (cert) {
 | 
						|
		LogError(L"check_db_cert(MokList, sha256hash) != DATA_FOUND\n");
 | 
						|
	}
 | 
						|
 | 
						|
	update_verification_method(VERIFIED_BY_NOTHING);
 | 
						|
	return EFI_NOT_FOUND;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check whether we're in Secure Boot and user mode
 | 
						|
 */
 | 
						|
BOOLEAN secure_mode (void)
 | 
						|
{
 | 
						|
	static int first = 1;
 | 
						|
	if (user_insecure_mode)
 | 
						|
		return FALSE;
 | 
						|
 | 
						|
	if (variable_is_secureboot() != 1) {
 | 
						|
		if (verbose && !in_protocol && first)
 | 
						|
			console_notify(L"Secure boot not enabled");
 | 
						|
		first = 0;
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If we /do/ have "SecureBoot", but /don't/ have "SetupMode",
 | 
						|
	 * then the implementation is bad, but we assume that secure boot is
 | 
						|
	 * enabled according to the status of "SecureBoot".  If we have both
 | 
						|
	 * of them, then "SetupMode" may tell us additional data, and we need
 | 
						|
	 * to consider it.
 | 
						|
	 */
 | 
						|
	if (variable_is_setupmode(0) == 1) {
 | 
						|
		if (verbose && !in_protocol && first)
 | 
						|
			console_notify(L"Platform is in setup mode");
 | 
						|
		first = 0;
 | 
						|
		return FALSE;
 | 
						|
	}
 | 
						|
 | 
						|
	first = 0;
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static EFI_STATUS
 | 
						|
verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig,
 | 
						|
		     UINT8 *sha256hash, UINT8 *sha1hash)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Ensure that the binary isn't forbidden
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
	efi_status = check_denylist(sig, sha256hash, sha1hash);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Binary is forbidden: %r\n", efi_status);
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		crypterr(efi_status);
 | 
						|
		return efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check whether the binary is authorized in any of the firmware
 | 
						|
	 * databases
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
	efi_status = check_allowlist(sig, sha256hash, sha1hash);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		if (efi_status != EFI_NOT_FOUND) {
 | 
						|
			dprint(L"check_allowlist(): %r\n", efi_status);
 | 
						|
			PrintErrors();
 | 
						|
			ClearErrors();
 | 
						|
			crypterr(efi_status);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		drain_openssl_errors();
 | 
						|
		return efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	efi_status = EFI_NOT_FOUND;
 | 
						|
#if defined(ENABLE_SHIM_CERT)
 | 
						|
	/*
 | 
						|
	 * Check against the shim build key
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
	if (build_cert && build_cert_size) {
 | 
						|
		dprint("verifying against shim cert\n");
 | 
						|
	}
 | 
						|
	if (build_cert && build_cert_size &&
 | 
						|
	    AuthenticodeVerify(sig->CertData,
 | 
						|
		       sig->Hdr.dwLength - sizeof(sig->Hdr),
 | 
						|
		       build_cert, build_cert_size, sha256hash,
 | 
						|
		       SHA256_DIGEST_SIZE)) {
 | 
						|
		dprint(L"AuthenticodeVerify(shim_cert) succeeded\n");
 | 
						|
		update_verification_method(VERIFIED_BY_CERT);
 | 
						|
		tpm_measure_variable(L"Shim", SHIM_LOCK_GUID,
 | 
						|
				     build_cert_size, build_cert);
 | 
						|
		efi_status = EFI_SUCCESS;
 | 
						|
		drain_openssl_errors();
 | 
						|
		return efi_status;
 | 
						|
	} else {
 | 
						|
		dprint(L"AuthenticodeVerify(shim_cert) failed\n");
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		crypterr(EFI_NOT_FOUND);
 | 
						|
	}
 | 
						|
#endif /* defined(ENABLE_SHIM_CERT) */
 | 
						|
 | 
						|
#if defined(VENDOR_CERT_FILE)
 | 
						|
	/*
 | 
						|
	 * And finally, check against shim's built-in key
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
	if (vendor_cert_size) {
 | 
						|
		dprint("verifying against vendor_cert\n");
 | 
						|
	}
 | 
						|
	if (vendor_cert_size &&
 | 
						|
	    AuthenticodeVerify(sig->CertData,
 | 
						|
			       sig->Hdr.dwLength - sizeof(sig->Hdr),
 | 
						|
			       vendor_cert, vendor_cert_size,
 | 
						|
			       sha256hash, SHA256_DIGEST_SIZE)) {
 | 
						|
		dprint(L"AuthenticodeVerify(vendor_cert) succeeded\n");
 | 
						|
		update_verification_method(VERIFIED_BY_CERT);
 | 
						|
		tpm_measure_variable(L"Shim", SHIM_LOCK_GUID,
 | 
						|
				     vendor_cert_size, vendor_cert);
 | 
						|
		efi_status = EFI_SUCCESS;
 | 
						|
		drain_openssl_errors();
 | 
						|
		return efi_status;
 | 
						|
	} else {
 | 
						|
		dprint(L"AuthenticodeVerify(vendor_cert) failed\n");
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		crypterr(EFI_NOT_FOUND);
 | 
						|
	}
 | 
						|
#endif /* defined(VENDOR_CERT_FILE) */
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check that the signature is valid and matches the binary
 | 
						|
 */
 | 
						|
EFI_STATUS
 | 
						|
verify_buffer (char *data, int datasize,
 | 
						|
	       PE_COFF_LOADER_IMAGE_CONTEXT *context,
 | 
						|
	       UINT8 *sha256hash, UINT8 *sha1hash)
 | 
						|
{
 | 
						|
	EFI_STATUS ret_efi_status;
 | 
						|
	size_t size = datasize;
 | 
						|
	size_t offset = 0;
 | 
						|
	unsigned int i = 0;
 | 
						|
 | 
						|
	if (datasize < 0)
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Clear OpenSSL's error log, because we get some DSO unimplemented
 | 
						|
	 * errors during its intialization, and we don't want those to look
 | 
						|
	 * like they're the reason for validation failures.
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
 | 
						|
	ret_efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash);
 | 
						|
	if (EFI_ERROR(ret_efi_status)) {
 | 
						|
		dprint(L"generate_hash: %r\n", ret_efi_status);
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		crypterr(ret_efi_status);
 | 
						|
		return ret_efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Ensure that the binary isn't forbidden by hash
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
	ret_efi_status = check_denylist(NULL, sha256hash, sha1hash);
 | 
						|
	if (EFI_ERROR(ret_efi_status)) {
 | 
						|
//		perror(L"Binary is forbidden\n");
 | 
						|
//		dprint(L"Binary is forbidden: %r\n", ret_efi_status);
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		crypterr(ret_efi_status);
 | 
						|
		return ret_efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check whether the binary is authorized by hash in any of the
 | 
						|
	 * firmware databases
 | 
						|
	 */
 | 
						|
	drain_openssl_errors();
 | 
						|
	ret_efi_status = check_allowlist(NULL, sha256hash, sha1hash);
 | 
						|
	if (EFI_ERROR(ret_efi_status)) {
 | 
						|
		LogError(L"check_allowlist(): %r\n", ret_efi_status);
 | 
						|
		dprint(L"check_allowlist: %r\n", ret_efi_status);
 | 
						|
		if (ret_efi_status != EFI_NOT_FOUND) {
 | 
						|
			dprint(L"check_allowlist(): %r\n", ret_efi_status);
 | 
						|
			PrintErrors();
 | 
						|
			ClearErrors();
 | 
						|
			crypterr(ret_efi_status);
 | 
						|
			return ret_efi_status;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		drain_openssl_errors();
 | 
						|
		return ret_efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	if (context->SecDir->Size == 0) {
 | 
						|
		dprint(L"No signatures found\n");
 | 
						|
		return EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
 | 
						|
	if (context->SecDir->Size >= size) {
 | 
						|
		perror(L"Certificate Database size is too large\n");
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
	}
 | 
						|
 | 
						|
	ret_efi_status = EFI_NOT_FOUND;
 | 
						|
	do {
 | 
						|
		WIN_CERTIFICATE_EFI_PKCS *sig = NULL;
 | 
						|
		size_t sz;
 | 
						|
 | 
						|
		sig = ImageAddress(data, size,
 | 
						|
				   context->SecDir->VirtualAddress + offset);
 | 
						|
		if (!sig)
 | 
						|
			break;
 | 
						|
 | 
						|
		sz = offset + offsetof(WIN_CERTIFICATE_EFI_PKCS, Hdr.dwLength)
 | 
						|
		     + sizeof(sig->Hdr.dwLength);
 | 
						|
		if (sz > context->SecDir->Size) {
 | 
						|
			perror(L"Certificate size is too large for secruity database");
 | 
						|
			return EFI_INVALID_PARAMETER;
 | 
						|
		}
 | 
						|
 | 
						|
		sz = sig->Hdr.dwLength;
 | 
						|
		if (sz > context->SecDir->Size - offset) {
 | 
						|
			perror(L"Certificate size is too large for secruity database");
 | 
						|
			return EFI_INVALID_PARAMETER;
 | 
						|
		}
 | 
						|
 | 
						|
		if (sz < sizeof(sig->Hdr)) {
 | 
						|
			perror(L"Certificate size is too small for certificate data");
 | 
						|
			return EFI_INVALID_PARAMETER;
 | 
						|
		}
 | 
						|
 | 
						|
		if (sig->Hdr.wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA) {
 | 
						|
			EFI_STATUS efi_status;
 | 
						|
 | 
						|
			dprint(L"Attempting to verify signature %d:\n", i++);
 | 
						|
 | 
						|
			efi_status = verify_one_signature(sig, sha256hash, sha1hash);
 | 
						|
 | 
						|
			/*
 | 
						|
			 * If we didn't get EFI_SECURITY_VIOLATION from
 | 
						|
			 * checking the hashes above, then any dbx entries are
 | 
						|
			 * for a certificate, not this individual binary.
 | 
						|
			 *
 | 
						|
			 * So don't clobber successes with security violation
 | 
						|
			 * here; that just means it isn't a success.
 | 
						|
			 */
 | 
						|
			if (ret_efi_status != EFI_SUCCESS)
 | 
						|
				ret_efi_status = efi_status;
 | 
						|
		} else {
 | 
						|
			perror(L"Unsupported certificate type %x\n",
 | 
						|
				sig->Hdr.wCertificateType);
 | 
						|
		}
 | 
						|
		offset = ALIGN_VALUE(offset + sz, 8);
 | 
						|
	} while (offset < context->SecDir->Size);
 | 
						|
 | 
						|
	if (ret_efi_status != EFI_SUCCESS) {
 | 
						|
		dprint(L"Binary is not authorized\n");
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		crypterr(EFI_SECURITY_VIOLATION);
 | 
						|
		ret_efi_status = EFI_SECURITY_VIOLATION;
 | 
						|
	}
 | 
						|
	drain_openssl_errors();
 | 
						|
	return ret_efi_status;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
should_use_fallback(EFI_HANDLE image_handle)
 | 
						|
{
 | 
						|
	EFI_LOADED_IMAGE *li;
 | 
						|
	unsigned int pathlen = 0;
 | 
						|
	CHAR16 *bootpath = NULL;
 | 
						|
	EFI_FILE_IO_INTERFACE *fio = NULL;
 | 
						|
	EFI_FILE *vh = NULL;
 | 
						|
	EFI_FILE *fh = NULL;
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
	efi_status = gBS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID,
 | 
						|
					 (void **)&li);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Could not get image for bootx64.efi: %r\n",
 | 
						|
		       efi_status);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	bootpath = DevicePathToStr(li->FilePath);
 | 
						|
 | 
						|
	/* Check the beginning of the string and the end, to avoid
 | 
						|
	 * caring about which arch this is. */
 | 
						|
	/* I really don't know why, but sometimes bootpath gives us
 | 
						|
	 * L"\\EFI\\BOOT\\/BOOTX64.EFI".  So just handle that here...
 | 
						|
	 */
 | 
						|
	if (StrnCaseCmp(bootpath, L"\\EFI\\BOOT\\BOOT", 14) &&
 | 
						|
			StrnCaseCmp(bootpath, L"\\EFI\\BOOT\\/BOOT", 15) &&
 | 
						|
			StrnCaseCmp(bootpath, L"EFI\\BOOT\\BOOT", 13) &&
 | 
						|
			StrnCaseCmp(bootpath, L"EFI\\BOOT\\/BOOT", 14))
 | 
						|
		goto error;
 | 
						|
 | 
						|
	pathlen = StrLen(bootpath);
 | 
						|
	if (pathlen < 5 || StrCaseCmp(bootpath + pathlen - 4, L".EFI"))
 | 
						|
		goto error;
 | 
						|
 | 
						|
	efi_status = gBS->HandleProtocol(li->DeviceHandle, &FileSystemProtocol,
 | 
						|
					 (void **) &fio);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Could not get fio for li->DeviceHandle: %r\n",
 | 
						|
		       efi_status);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	efi_status = fio->OpenVolume(fio, &vh);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Could not open fio volume: %r\n", efi_status);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	efi_status = vh->Open(vh, &fh, L"\\EFI\\BOOT" FALLBACK,
 | 
						|
			      EFI_FILE_MODE_READ, 0);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		/* Do not print the error here - this is an acceptable case
 | 
						|
		 * for removable media, where we genuinely don't want
 | 
						|
		 * fallback.efi to exist.
 | 
						|
		 * Print(L"Could not open \"\\EFI\\BOOT%s\": %r\n", FALLBACK,
 | 
						|
		 *       efi_status);
 | 
						|
		 */
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = 1;
 | 
						|
error:
 | 
						|
	if (fh)
 | 
						|
		fh->Close(fh);
 | 
						|
	if (vh)
 | 
						|
		vh->Close(vh);
 | 
						|
	if (bootpath)
 | 
						|
		FreePool(bootpath);
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Generate the path of an executable given shim's path and the name
 | 
						|
 * of the executable
 | 
						|
 */
 | 
						|
static EFI_STATUS generate_path_from_image_path(EFI_LOADED_IMAGE *li,
 | 
						|
						CHAR16 *ImagePath,
 | 
						|
						CHAR16 **PathName)
 | 
						|
{
 | 
						|
	EFI_DEVICE_PATH *devpath;
 | 
						|
	unsigned int i;
 | 
						|
	int j, last = -1;
 | 
						|
	unsigned int pathlen = 0;
 | 
						|
	EFI_STATUS efi_status = EFI_SUCCESS;
 | 
						|
	CHAR16 *bootpath;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Suuuuper lazy technique here, but check and see if this is a full
 | 
						|
	 * path to something on the ESP.  Backwards compatibility demands
 | 
						|
	 * that we don't just use \\, because we (not particularly brightly)
 | 
						|
	 * used to require that the relative file path started with that.
 | 
						|
	 *
 | 
						|
	 * If it is a full path, don't try to merge it with the directory
 | 
						|
	 * from our Loaded Image handle.
 | 
						|
	 */
 | 
						|
	if (StrSize(ImagePath) > 5 && StrnCmp(ImagePath, L"\\EFI\\", 5) == 0) {
 | 
						|
		*PathName = StrDuplicate(ImagePath);
 | 
						|
		if (!*PathName) {
 | 
						|
			perror(L"Failed to allocate path buffer\n");
 | 
						|
			return EFI_OUT_OF_RESOURCES;
 | 
						|
		}
 | 
						|
		return EFI_SUCCESS;
 | 
						|
	}
 | 
						|
 | 
						|
	devpath = li->FilePath;
 | 
						|
 | 
						|
	bootpath = DevicePathToStr(devpath);
 | 
						|
 | 
						|
	pathlen = StrLen(bootpath);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * DevicePathToStr() concatenates two nodes with '/'.
 | 
						|
	 * Convert '/' to '\\'.
 | 
						|
	 */
 | 
						|
	for (i = 0; i < pathlen; i++) {
 | 
						|
		if (bootpath[i] == '/')
 | 
						|
			bootpath[i] = '\\';
 | 
						|
	}
 | 
						|
 | 
						|
	for (i=pathlen; i>0; i--) {
 | 
						|
		if (bootpath[i] == '\\' && bootpath[i-1] == '\\')
 | 
						|
			bootpath[i] = '/';
 | 
						|
		else if (last == -1 && bootpath[i] == '\\')
 | 
						|
			last = i;
 | 
						|
	}
 | 
						|
 | 
						|
	if (last == -1 && bootpath[0] == '\\')
 | 
						|
		last = 0;
 | 
						|
	bootpath[last+1] = '\0';
 | 
						|
 | 
						|
	if (last > 0) {
 | 
						|
		for (i = 0, j = 0; bootpath[i] != '\0'; i++) {
 | 
						|
			if (bootpath[i] != '/') {
 | 
						|
				bootpath[j] = bootpath[i];
 | 
						|
				j++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		bootpath[j] = '\0';
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0, last = 0; i < StrLen(ImagePath); i++)
 | 
						|
		if (ImagePath[i] == '\\')
 | 
						|
			last = i + 1;
 | 
						|
 | 
						|
	ImagePath = ImagePath + last;
 | 
						|
	*PathName = AllocatePool(StrSize(bootpath) + StrSize(ImagePath));
 | 
						|
 | 
						|
	if (!*PathName) {
 | 
						|
		perror(L"Failed to allocate path buffer\n");
 | 
						|
		efi_status = EFI_OUT_OF_RESOURCES;
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	*PathName[0] = '\0';
 | 
						|
	if (StrnCaseCmp(bootpath, ImagePath, StrLen(bootpath)))
 | 
						|
		StrCat(*PathName, bootpath);
 | 
						|
	StrCat(*PathName, ImagePath);
 | 
						|
 | 
						|
error:
 | 
						|
	FreePool(bootpath);
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Open the second stage bootloader and read it into a buffer
 | 
						|
 */
 | 
						|
static EFI_STATUS load_image (EFI_LOADED_IMAGE *li, void **data,
 | 
						|
			      int *datasize, CHAR16 *PathName)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	EFI_HANDLE device;
 | 
						|
	EFI_FILE_INFO *fileinfo = NULL;
 | 
						|
	EFI_FILE_IO_INTERFACE *drive;
 | 
						|
	EFI_FILE *root, *grub;
 | 
						|
	UINTN buffersize = sizeof(EFI_FILE_INFO);
 | 
						|
 | 
						|
	device = li->DeviceHandle;
 | 
						|
 | 
						|
	dprint(L"attempting to load %s\n", PathName);
 | 
						|
	/*
 | 
						|
	 * Open the device
 | 
						|
	 */
 | 
						|
	efi_status = gBS->HandleProtocol(device, &EFI_SIMPLE_FILE_SYSTEM_GUID,
 | 
						|
					 (void **) &drive);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Failed to find fs: %r\n", efi_status);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	efi_status = drive->OpenVolume(drive, &root);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Failed to open fs: %r\n", efi_status);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * And then open the file
 | 
						|
	 */
 | 
						|
	efi_status = root->Open(root, &grub, PathName, EFI_FILE_MODE_READ, 0);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Failed to open %s - %r\n", PathName, efi_status);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	fileinfo = AllocatePool(buffersize);
 | 
						|
 | 
						|
	if (!fileinfo) {
 | 
						|
		perror(L"Unable to allocate file info buffer\n");
 | 
						|
		efi_status = EFI_OUT_OF_RESOURCES;
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Find out how big the file is in order to allocate the storage
 | 
						|
	 * buffer
 | 
						|
	 */
 | 
						|
	efi_status = grub->GetInfo(grub, &EFI_FILE_INFO_GUID, &buffersize,
 | 
						|
				   fileinfo);
 | 
						|
	if (efi_status == EFI_BUFFER_TOO_SMALL) {
 | 
						|
		FreePool(fileinfo);
 | 
						|
		fileinfo = AllocatePool(buffersize);
 | 
						|
		if (!fileinfo) {
 | 
						|
			perror(L"Unable to allocate file info buffer\n");
 | 
						|
			efi_status = EFI_OUT_OF_RESOURCES;
 | 
						|
			goto error;
 | 
						|
		}
 | 
						|
		efi_status = grub->GetInfo(grub, &EFI_FILE_INFO_GUID,
 | 
						|
					   &buffersize, fileinfo);
 | 
						|
	}
 | 
						|
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Unable to get file info: %r\n", efi_status);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	buffersize = fileinfo->FileSize;
 | 
						|
	*data = AllocatePool(buffersize);
 | 
						|
	if (!*data) {
 | 
						|
		perror(L"Unable to allocate file buffer\n");
 | 
						|
		efi_status = EFI_OUT_OF_RESOURCES;
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Perform the actual read
 | 
						|
	 */
 | 
						|
	efi_status = grub->Read(grub, &buffersize, *data);
 | 
						|
	if (efi_status == EFI_BUFFER_TOO_SMALL) {
 | 
						|
		FreePool(*data);
 | 
						|
		*data = AllocatePool(buffersize);
 | 
						|
		efi_status = grub->Read(grub, &buffersize, *data);
 | 
						|
	}
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Unexpected return from initial read: %r, buffersize %x\n",
 | 
						|
		       efi_status, buffersize);
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	*datasize = buffersize;
 | 
						|
 | 
						|
	FreePool(fileinfo);
 | 
						|
 | 
						|
	return EFI_SUCCESS;
 | 
						|
error:
 | 
						|
	if (*data) {
 | 
						|
		FreePool(*data);
 | 
						|
		*data = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (fileinfo)
 | 
						|
		FreePool(fileinfo);
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Protocol entry point. If secure boot is enabled, verify that the provided
 | 
						|
 * buffer is signed with a trusted key.
 | 
						|
 */
 | 
						|
EFI_STATUS shim_verify (void *buffer, UINT32 size)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status = EFI_SUCCESS;
 | 
						|
	PE_COFF_LOADER_IMAGE_CONTEXT context;
 | 
						|
	UINT8 sha1hash[SHA1_DIGEST_SIZE];
 | 
						|
	UINT8 sha256hash[SHA256_DIGEST_SIZE];
 | 
						|
 | 
						|
	if ((INT32)size < 0)
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	loader_is_participating = 1;
 | 
						|
	in_protocol = 1;
 | 
						|
 | 
						|
	efi_status = read_header(buffer, size, &context);
 | 
						|
	if (EFI_ERROR(efi_status))
 | 
						|
		goto done;
 | 
						|
 | 
						|
	efi_status = generate_hash(buffer, size, &context,
 | 
						|
				   sha256hash, sha1hash);
 | 
						|
	if (EFI_ERROR(efi_status))
 | 
						|
		goto done;
 | 
						|
 | 
						|
	/* Measure the binary into the TPM */
 | 
						|
#ifdef REQUIRE_TPM
 | 
						|
	efi_status =
 | 
						|
#endif
 | 
						|
	tpm_log_pe((EFI_PHYSICAL_ADDRESS)(UINTN)buffer, size, 0, NULL,
 | 
						|
		   sha1hash, 4);
 | 
						|
#ifdef REQUIRE_TPM
 | 
						|
	if (EFI_ERROR(efi_status))
 | 
						|
		goto done;
 | 
						|
#endif
 | 
						|
 | 
						|
	if (!secure_mode()) {
 | 
						|
		efi_status = EFI_SUCCESS;
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	efi_status = verify_buffer(buffer, size,
 | 
						|
				   &context, sha256hash, sha1hash);
 | 
						|
done:
 | 
						|
	in_protocol = 0;
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
static EFI_STATUS shim_hash (char *data, int datasize,
 | 
						|
			     PE_COFF_LOADER_IMAGE_CONTEXT *context,
 | 
						|
			     UINT8 *sha256hash, UINT8 *sha1hash)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
 | 
						|
	if (datasize < 0)
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	in_protocol = 1;
 | 
						|
	efi_status = generate_hash(data, datasize, context,
 | 
						|
				   sha256hash, sha1hash);
 | 
						|
	in_protocol = 0;
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
static EFI_STATUS shim_read_header(void *data, unsigned int datasize,
 | 
						|
				   PE_COFF_LOADER_IMAGE_CONTEXT *context)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
 | 
						|
	in_protocol = 1;
 | 
						|
	efi_status = read_header(data, datasize, context);
 | 
						|
	in_protocol = 0;
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Load and run an EFI executable
 | 
						|
 */
 | 
						|
EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	EFI_LOADED_IMAGE *li, li_bak;
 | 
						|
	EFI_IMAGE_ENTRY_POINT entry_point;
 | 
						|
	EFI_PHYSICAL_ADDRESS alloc_address;
 | 
						|
	UINTN alloc_pages;
 | 
						|
	CHAR16 *PathName = NULL;
 | 
						|
	void *sourcebuffer = NULL;
 | 
						|
	UINT64 sourcesize = 0;
 | 
						|
	void *data = NULL;
 | 
						|
	int datasize;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We need to refer to the loaded image protocol on the running
 | 
						|
	 * binary in order to find our path
 | 
						|
	 */
 | 
						|
	efi_status = gBS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID,
 | 
						|
					 (void **)&li);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Unable to init protocol\n");
 | 
						|
		return efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Build a new path from the existing one plus the executable name
 | 
						|
	 */
 | 
						|
	efi_status = generate_path_from_image_path(li, ImagePath, &PathName);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Unable to generate path %s: %r\n", ImagePath,
 | 
						|
		       efi_status);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	if (findNetboot(li->DeviceHandle)) {
 | 
						|
		efi_status = parseNetbootinfo(image_handle);
 | 
						|
		if (EFI_ERROR(efi_status)) {
 | 
						|
			perror(L"Netboot parsing failed: %r\n", efi_status);
 | 
						|
			return EFI_PROTOCOL_ERROR;
 | 
						|
		}
 | 
						|
		efi_status = FetchNetbootimage(image_handle, &sourcebuffer,
 | 
						|
					       &sourcesize);
 | 
						|
		if (EFI_ERROR(efi_status)) {
 | 
						|
			perror(L"Unable to fetch TFTP image: %r\n",
 | 
						|
			       efi_status);
 | 
						|
			return efi_status;
 | 
						|
		}
 | 
						|
		data = sourcebuffer;
 | 
						|
		datasize = sourcesize;
 | 
						|
	} else if (find_httpboot(li->DeviceHandle)) {
 | 
						|
		efi_status = httpboot_fetch_buffer (image_handle,
 | 
						|
						    &sourcebuffer,
 | 
						|
						    &sourcesize);
 | 
						|
		if (EFI_ERROR(efi_status)) {
 | 
						|
			perror(L"Unable to fetch HTTP image: %r\n",
 | 
						|
			       efi_status);
 | 
						|
			return efi_status;
 | 
						|
		}
 | 
						|
		data = sourcebuffer;
 | 
						|
		datasize = sourcesize;
 | 
						|
	} else {
 | 
						|
		/*
 | 
						|
		 * Read the new executable off disk
 | 
						|
		 */
 | 
						|
		efi_status = load_image(li, &data, &datasize, PathName);
 | 
						|
		if (EFI_ERROR(efi_status)) {
 | 
						|
			perror(L"Failed to load image %s: %r\n",
 | 
						|
			       PathName, efi_status);
 | 
						|
			PrintErrors();
 | 
						|
			ClearErrors();
 | 
						|
			goto done;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (datasize < 0) {
 | 
						|
		efi_status = EFI_INVALID_PARAMETER;
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * We need to modify the loaded image protocol entry before running
 | 
						|
	 * the new binary, so back it up
 | 
						|
	 */
 | 
						|
	CopyMem(&li_bak, li, sizeof(li_bak));
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Update the loaded image with the second stage loader file path
 | 
						|
	 */
 | 
						|
	li->FilePath = FileDevicePath(NULL, PathName);
 | 
						|
	if (!li->FilePath) {
 | 
						|
		perror(L"Unable to update loaded image file path\n");
 | 
						|
		efi_status = EFI_OUT_OF_RESOURCES;
 | 
						|
		goto restore;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Verify and, if appropriate, relocate and execute the executable
 | 
						|
	 */
 | 
						|
	efi_status = handle_image(data, datasize, li, &entry_point,
 | 
						|
				  &alloc_address, &alloc_pages);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Failed to load image: %r\n", efi_status);
 | 
						|
		PrintErrors();
 | 
						|
		ClearErrors();
 | 
						|
		goto restore;
 | 
						|
	}
 | 
						|
 | 
						|
	loader_is_participating = 0;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * The binary is trusted and relocated. Run it
 | 
						|
	 */
 | 
						|
	efi_status = entry_point(image_handle, systab);
 | 
						|
 | 
						|
restore:
 | 
						|
	if (li->FilePath)
 | 
						|
		FreePool(li->FilePath);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Restore our original loaded image values
 | 
						|
	 */
 | 
						|
	CopyMem(li, &li_bak, sizeof(li_bak));
 | 
						|
done:
 | 
						|
	if (PathName)
 | 
						|
		FreePool(PathName);
 | 
						|
 | 
						|
	if (data)
 | 
						|
		FreePool(data);
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Load and run grub. If that fails because grub isn't trusted, load and
 | 
						|
 * run MokManager.
 | 
						|
 */
 | 
						|
EFI_STATUS init_grub(EFI_HANDLE image_handle)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	int use_fb = should_use_fallback(image_handle);
 | 
						|
 | 
						|
	efi_status = start_image(image_handle, use_fb ? FALLBACK :second_stage);
 | 
						|
	if (efi_status == EFI_SECURITY_VIOLATION ||
 | 
						|
	    efi_status == EFI_ACCESS_DENIED) {
 | 
						|
		efi_status = start_image(image_handle, MOK_MANAGER);
 | 
						|
		if (EFI_ERROR(efi_status)) {
 | 
						|
			console_print(L"start_image() returned %r\n", efi_status);
 | 
						|
			msleep(2000000);
 | 
						|
			return efi_status;
 | 
						|
		}
 | 
						|
 | 
						|
		efi_status = start_image(image_handle,
 | 
						|
					 use_fb ? FALLBACK : second_stage);
 | 
						|
	}
 | 
						|
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		console_print(L"start_image() returned %r\n", efi_status);
 | 
						|
		msleep(2000000);
 | 
						|
	}
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
static inline EFI_STATUS
 | 
						|
get_load_option_optional_data(UINT8 *data, UINTN data_size,
 | 
						|
			      UINT8 **od, UINTN *ods)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * If it's not at least Attributes + FilePathListLength +
 | 
						|
	 * Description=L"" + 0x7fff0400 (EndEntrireDevicePath), it can't
 | 
						|
	 * be valid.
 | 
						|
	 */
 | 
						|
	if (data_size < (sizeof(UINT32) + sizeof(UINT16) + 2 + 4))
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	UINT8 *cur = data + sizeof(UINT32);
 | 
						|
	UINT16 fplistlen = *(UINT16 *)cur;
 | 
						|
	/*
 | 
						|
	 * If there's not enough space for the file path list and the
 | 
						|
	 * smallest possible description (L""), it's not valid.
 | 
						|
	 */
 | 
						|
	if (fplistlen > data_size - (sizeof(UINT32) + 2 + 4))
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	cur += sizeof(UINT16);
 | 
						|
	UINTN limit = data_size - (cur - data) - fplistlen;
 | 
						|
	UINTN i;
 | 
						|
	for (i = 0; i < limit ; i++) {
 | 
						|
		/* If the description isn't valid UCS2-LE, it's not valid. */
 | 
						|
		if (i % 2 != 0) {
 | 
						|
			if (cur[i] != 0)
 | 
						|
				return EFI_INVALID_PARAMETER;
 | 
						|
		} else if (cur[i] == 0) {
 | 
						|
			/* we've found the end */
 | 
						|
			i++;
 | 
						|
			if (i >= limit || cur[i] != 0)
 | 
						|
				return EFI_INVALID_PARAMETER;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	i++;
 | 
						|
	if (i > limit)
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If i is limit, we know the rest of this is the FilePathList and
 | 
						|
	 * there's no optional data.  So just bail now.
 | 
						|
	 */
 | 
						|
	if (i == limit) {
 | 
						|
		*od = NULL;
 | 
						|
		*ods = 0;
 | 
						|
		return EFI_SUCCESS;
 | 
						|
	}
 | 
						|
 | 
						|
	cur += i;
 | 
						|
	limit -= i;
 | 
						|
	limit += fplistlen;
 | 
						|
	i = 0;
 | 
						|
	while (limit - i >= 4) {
 | 
						|
		struct {
 | 
						|
			UINT8 type;
 | 
						|
			UINT8 subtype;
 | 
						|
			UINT16 len;
 | 
						|
		} dp = {
 | 
						|
			.type = cur[i],
 | 
						|
			.subtype = cur[i+1],
 | 
						|
			/*
 | 
						|
			 * it's a little endian UINT16, but we're not
 | 
						|
			 * guaranteed alignment is sane, so we can't just
 | 
						|
			 * typecast it directly.
 | 
						|
			 */
 | 
						|
			.len = (cur[i+3] << 8) | cur[i+2],
 | 
						|
		};
 | 
						|
 | 
						|
		/*
 | 
						|
		 * We haven't found an EndEntire, so this has to be a valid
 | 
						|
		 * EFI_DEVICE_PATH in order for the data to be valid.  That
 | 
						|
		 * means it has to fit, and it can't be smaller than 4 bytes.
 | 
						|
		 */
 | 
						|
		if (dp.len < 4 || dp.len > limit)
 | 
						|
			return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * see if this is an EndEntire node...
 | 
						|
		 */
 | 
						|
		if (dp.type == 0x7f && dp.subtype == 0xff) {
 | 
						|
			/*
 | 
						|
			 * if we've found the EndEntire node, it must be 4
 | 
						|
			 * bytes
 | 
						|
			 */
 | 
						|
			if (dp.len != 4)
 | 
						|
				return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
			i += dp.len;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * It's just some random DP node; skip it.
 | 
						|
		 */
 | 
						|
		i += dp.len;
 | 
						|
	}
 | 
						|
	if (i != fplistlen)
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * if there's any space left, it's "optional data"
 | 
						|
	 */
 | 
						|
	*od = cur + i;
 | 
						|
	*ods = limit - i;
 | 
						|
	return EFI_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int is_our_path(EFI_LOADED_IMAGE *li, CHAR16 *path)
 | 
						|
{
 | 
						|
	CHAR16 *dppath = NULL;
 | 
						|
	CHAR16 *PathName = NULL;
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	int ret = 1;
 | 
						|
 | 
						|
	dppath = DevicePathToStr(li->FilePath);
 | 
						|
	if (!dppath)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	efi_status = generate_path_from_image_path(li, path, &PathName);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"Unable to generate path %s: %r\n", path,
 | 
						|
		       efi_status);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	dprint(L"dppath: %s\n", dppath);
 | 
						|
	dprint(L"path:   %s\n", path);
 | 
						|
	if (StrnCaseCmp(dppath, PathName, StrLen(dppath)))
 | 
						|
		ret = 0;
 | 
						|
 | 
						|
done:
 | 
						|
	FreePool(dppath);
 | 
						|
	FreePool(PathName);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check the load options to specify the second stage loader
 | 
						|
 */
 | 
						|
EFI_STATUS set_second_stage (EFI_HANDLE image_handle)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	EFI_LOADED_IMAGE *li = NULL;
 | 
						|
	CHAR16 *start = NULL;
 | 
						|
	int remaining_size = 0;
 | 
						|
	CHAR16 *loader_str = NULL;
 | 
						|
	UINTN loader_len = 0;
 | 
						|
	unsigned int i;
 | 
						|
	UINTN second_stage_len;
 | 
						|
 | 
						|
	second_stage_len = (StrLen(DEFAULT_LOADER) + 1) * sizeof(CHAR16);
 | 
						|
	second_stage = AllocatePool(second_stage_len);
 | 
						|
	if (!second_stage) {
 | 
						|
		perror(L"Could not allocate %lu bytes\n", second_stage_len);
 | 
						|
		return EFI_OUT_OF_RESOURCES;
 | 
						|
	}
 | 
						|
	StrCpy(second_stage, DEFAULT_LOADER);
 | 
						|
	load_options = NULL;
 | 
						|
	load_options_size = 0;
 | 
						|
 | 
						|
	efi_status = gBS->HandleProtocol(image_handle, &LoadedImageProtocol,
 | 
						|
					 (void **) &li);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror (L"Failed to get load options: %r\n", efi_status);
 | 
						|
		return efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	/* So, load options are a giant pain in the ass.  If we're invoked
 | 
						|
	 * from the EFI shell, we get something like this:
 | 
						|
 | 
						|
00000000  5c 00 45 00 36 00 49 00  5c 00 66 00 65 00 64 00  |\.E.F.I.\.f.e.d.|
 | 
						|
00000010  6f 00 72 00 61 00 5c 00  73 00 68 00 69 00 6d 00  |o.r.a.\.s.h.i.m.|
 | 
						|
00000020  78 00 36 00 34 00 2e 00  64 00 66 00 69 00 20 00  |x.6.4...e.f.i. .|
 | 
						|
00000030  5c 00 45 00 46 00 49 00  5c 00 66 00 65 00 64 00  |\.E.F.I.\.f.e.d.|
 | 
						|
00000040  6f 00 72 00 61 00 5c 00  66 00 77 00 75 00 70 00  |o.r.a.\.f.w.u.p.|
 | 
						|
00000050  64 00 61 00 74 00 65 00  2e 00 65 00 66 00 20 00  |d.a.t.e.e.f.i. .|
 | 
						|
00000060  00 00 66 00 73 00 30 00  3a 00 5c 00 00 00        |..f.s.0.:.\...|
 | 
						|
 | 
						|
	*
 | 
						|
	* which is just some paths rammed together separated by a UCS-2 NUL.
 | 
						|
	* But if we're invoked from BDS, we get something more like:
 | 
						|
	*
 | 
						|
 | 
						|
00000000  01 00 00 00 62 00 4c 00  69 00 6e 00 75 00 78 00  |....b.L.i.n.u.x.|
 | 
						|
00000010  20 00 46 00 69 00 72 00  6d 00 77 00 61 00 72 00  | .F.i.r.m.w.a.r.|
 | 
						|
00000020  65 00 20 00 55 00 70 00  64 00 61 00 74 00 65 00  |e. .U.p.d.a.t.e.|
 | 
						|
00000030  72 00 00 00 40 01 2a 00  01 00 00 00 00 08 00 00  |r.....*.........|
 | 
						|
00000040  00 00 00 00 00 40 06 00  00 00 00 00 1a 9e 55 bf  |.....@........U.|
 | 
						|
00000050  04 57 f2 4f b4 4a ed 26  4a 40 6a 94 02 02 04 04  |.W.O.:.&J@j.....|
 | 
						|
00000060  34 00 5c 00 45 00 46 00  49 00 5c 00 66 00 65 00  |4.\.E.F.I.f.e.d.|
 | 
						|
00000070  64 00 6f 00 72 00 61 00  5c 00 73 00 68 00 69 00  |o.r.a.\.s.h.i.m.|
 | 
						|
00000080  6d 00 78 00 36 00 34 00  2e 00 65 00 66 00 69 00  |x.6.4...e.f.i...|
 | 
						|
00000090  00 00 7f ff 40 00 20 00  5c 00 66 00 77 00 75 00  |...... .\.f.w.u.|
 | 
						|
000000a0  70 00 78 00 36 00 34 00  2e 00 65 00 66 00 69 00  |p.x.6.4...e.f.i.|
 | 
						|
000000b0  00 00                                             |..|
 | 
						|
 | 
						|
	*
 | 
						|
	* which is clearly an EFI_LOAD_OPTION filled in halfway reasonably.
 | 
						|
	* In short, the UEFI shell is still a useless piece of junk.
 | 
						|
	*
 | 
						|
	* But then on some versions of BDS, we get:
 | 
						|
 | 
						|
00000000  5c 00 66 00 77 00 75 00  70 00 78 00 36 00 34 00  |\.f.w.u.p.x.6.4.|
 | 
						|
00000010  2e 00 65 00 66 00 69 00  00 00                    |..e.f.i...|
 | 
						|
0000001a
 | 
						|
 | 
						|
	* which as you can see is one perfectly normal UCS2-EL string
 | 
						|
	* containing the load option from the Boot#### variable.
 | 
						|
	*
 | 
						|
	* We also sometimes find a guid or partial guid at the end, because
 | 
						|
	* BDS will add that, but we ignore that here.
 | 
						|
	*/
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Maybe there just aren't any options...
 | 
						|
	 */
 | 
						|
	if (li->LoadOptionsSize == 0)
 | 
						|
		return EFI_SUCCESS;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * In either case, we've got to have at least a UCS2 NUL...
 | 
						|
	 */
 | 
						|
	if (li->LoadOptionsSize < 2)
 | 
						|
		return EFI_BAD_BUFFER_SIZE;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Some awesome versions of BDS will add entries for Linux.  On top
 | 
						|
	 * of that, some versions of BDS will "tag" any Boot#### entries they
 | 
						|
	 * create by putting a GUID at the very end of the optional data in
 | 
						|
	 * the EFI_LOAD_OPTIONS, thus screwing things up for everybody who
 | 
						|
	 * tries to actually *use* the optional data for anything.  Why they
 | 
						|
	 * did this instead of adding a flag to the spec to /say/ it's
 | 
						|
	 * created by BDS, I do not know.  For shame.
 | 
						|
	 *
 | 
						|
	 * Anyway, just nerf that out from the start.  It's always just
 | 
						|
	 * garbage at the end.
 | 
						|
	 */
 | 
						|
	if (li->LoadOptionsSize > 16) {
 | 
						|
		if (CompareGuid((EFI_GUID *)(li->LoadOptions
 | 
						|
					     + (li->LoadOptionsSize - 16)),
 | 
						|
				&BDS_GUID) == 0)
 | 
						|
			li->LoadOptionsSize -= 16;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Apparently sometimes we get L"\0\0"?  Which isn't useful at all.
 | 
						|
	 */
 | 
						|
	if (is_all_nuls(li->LoadOptions, li->LoadOptionsSize))
 | 
						|
		return EFI_SUCCESS;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Check and see if this is just a list of strings.  If it's an
 | 
						|
	 * EFI_LOAD_OPTION, it'll be 0, since we know EndEntire device path
 | 
						|
	 * won't pass muster as UCS2-LE.
 | 
						|
	 *
 | 
						|
	 * If there are 3 strings, we're launched from the shell most likely,
 | 
						|
	 * But we actually only care about the second one.
 | 
						|
	 */
 | 
						|
	UINTN strings = count_ucs2_strings(li->LoadOptions,
 | 
						|
					   li->LoadOptionsSize);
 | 
						|
	/*
 | 
						|
	 * If it's not string data, try it as an EFI_LOAD_OPTION.
 | 
						|
	 */
 | 
						|
	if (strings == 0) {
 | 
						|
		/*
 | 
						|
		 * We at least didn't find /enough/ strings.  See if it works
 | 
						|
		 * as an EFI_LOAD_OPTION.
 | 
						|
		 */
 | 
						|
		efi_status = get_load_option_optional_data(li->LoadOptions,
 | 
						|
							   li->LoadOptionsSize,
 | 
						|
							   (UINT8 **)&start,
 | 
						|
							   &loader_len);
 | 
						|
		if (EFI_ERROR(efi_status))
 | 
						|
			return EFI_SUCCESS;
 | 
						|
 | 
						|
		remaining_size = 0;
 | 
						|
	} else if (strings >= 2) {
 | 
						|
		/*
 | 
						|
		 * UEFI shell copies the whole line of the command into
 | 
						|
		 * LoadOptions.  We ignore the string before the first L' ',
 | 
						|
		 * i.e. the name of this program.
 | 
						|
		 * Counting by two bytes is safe, because we know the size is
 | 
						|
		 * compatible with a UCS2-LE string.
 | 
						|
		 */
 | 
						|
		UINT8 *cur = li->LoadOptions;
 | 
						|
		for (i = 0; i < li->LoadOptionsSize - 2; i += 2) {
 | 
						|
			CHAR16 c = (cur[i+1] << 8) | cur[i];
 | 
						|
			if (c == L' ') {
 | 
						|
				start = (CHAR16 *)&cur[i+2];
 | 
						|
				remaining_size = li->LoadOptionsSize - i - 2;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (!start || remaining_size <= 0 || start[0] == L'\0')
 | 
						|
			return EFI_SUCCESS;
 | 
						|
 | 
						|
		for (i = 0; start[i] != '\0'; i++) {
 | 
						|
			if (start[i] == L' ')
 | 
						|
				start[i] = L'\0';
 | 
						|
			if (start[i] == L'\0') {
 | 
						|
				loader_len = 2 * i + 2;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (loader_len)
 | 
						|
			remaining_size -= loader_len;
 | 
						|
	} else {
 | 
						|
		/* only find one string */
 | 
						|
		start = li->LoadOptions;
 | 
						|
		loader_len = li->LoadOptionsSize;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Just to be sure all that math is right...
 | 
						|
	 */
 | 
						|
	if (loader_len % 2 != 0)
 | 
						|
		return EFI_INVALID_PARAMETER;
 | 
						|
 | 
						|
	strings = count_ucs2_strings((UINT8 *)start, loader_len);
 | 
						|
	if (strings < 1)
 | 
						|
		return EFI_SUCCESS;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * And then I found a version of BDS that gives us our own path in
 | 
						|
	 * LoadOptions:
 | 
						|
 | 
						|
77162C58                           5c 00 45 00 46 00 49 00          |\.E.F.I.|
 | 
						|
77162C60  5c 00 42 00 4f 00 4f 00  54 00 5c 00 42 00 4f 00  |\.B.O.O.T.\.B.O.|
 | 
						|
77162C70  4f 00 54 00 58 00 36 00  34 00 2e 00 45 00 46 00  |O.T.X.6.4...E.F.|
 | 
						|
77162C80  49 00 00 00                                       |I...|
 | 
						|
 | 
						|
	 * which is just cruel... So yeah, just don't use it.
 | 
						|
	 */
 | 
						|
	if (strings == 1 && is_our_path(li, start))
 | 
						|
		return EFI_SUCCESS;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Set up the name of the alternative loader and the LoadOptions for
 | 
						|
	 * the loader
 | 
						|
	 */
 | 
						|
	if (loader_len > 0) {
 | 
						|
		loader_str = AllocatePool(loader_len);
 | 
						|
		if (!loader_str) {
 | 
						|
			perror(L"Failed to allocate loader string\n");
 | 
						|
			return EFI_OUT_OF_RESOURCES;
 | 
						|
		}
 | 
						|
 | 
						|
		for (i = 0; i < loader_len / 2; i++)
 | 
						|
			loader_str[i] = start[i];
 | 
						|
		loader_str[loader_len/2-1] = L'\0';
 | 
						|
 | 
						|
		second_stage = loader_str;
 | 
						|
		load_options = remaining_size ? start + (loader_len/2) : NULL;
 | 
						|
		load_options_size = remaining_size;
 | 
						|
	}
 | 
						|
 | 
						|
	return EFI_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static void *
 | 
						|
ossl_malloc(size_t num)
 | 
						|
{
 | 
						|
	return AllocatePool(num);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
ossl_free(void *addr)
 | 
						|
{
 | 
						|
	FreePool(addr);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
init_openssl(void)
 | 
						|
{
 | 
						|
	CRYPTO_set_mem_functions(ossl_malloc, NULL, ossl_free);
 | 
						|
	OPENSSL_init();
 | 
						|
	CRYPTO_set_mem_functions(ossl_malloc, NULL, ossl_free);
 | 
						|
	ERR_load_ERR_strings();
 | 
						|
	ERR_load_BN_strings();
 | 
						|
	ERR_load_RSA_strings();
 | 
						|
	ERR_load_DH_strings();
 | 
						|
	ERR_load_EVP_strings();
 | 
						|
	ERR_load_BUF_strings();
 | 
						|
	ERR_load_OBJ_strings();
 | 
						|
	ERR_load_PEM_strings();
 | 
						|
	ERR_load_X509_strings();
 | 
						|
	ERR_load_ASN1_strings();
 | 
						|
	ERR_load_CONF_strings();
 | 
						|
	ERR_load_CRYPTO_strings();
 | 
						|
	ERR_load_COMP_strings();
 | 
						|
	ERR_load_BIO_strings();
 | 
						|
	ERR_load_PKCS7_strings();
 | 
						|
	ERR_load_X509V3_strings();
 | 
						|
	ERR_load_PKCS12_strings();
 | 
						|
	ERR_load_RAND_strings();
 | 
						|
	ERR_load_DSO_strings();
 | 
						|
	ERR_load_OCSP_strings();
 | 
						|
}
 | 
						|
 | 
						|
static SHIM_LOCK shim_lock_interface;
 | 
						|
static EFI_HANDLE shim_lock_handle;
 | 
						|
 | 
						|
EFI_STATUS
 | 
						|
install_shim_protocols(void)
 | 
						|
{
 | 
						|
	SHIM_LOCK *shim_lock;
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Did another instance of shim earlier already install the
 | 
						|
	 * protocol? If so, get rid of it.
 | 
						|
	 *
 | 
						|
	 * We have to uninstall shim's protocol here, because if we're
 | 
						|
	 * On the fallback.efi path, then our call pathway is:
 | 
						|
	 *
 | 
						|
	 * shim->fallback->shim->grub
 | 
						|
	 * ^               ^      ^
 | 
						|
	 * |               |      \- gets protocol #0
 | 
						|
	 * |               \- installs its protocol (#1)
 | 
						|
	 * \- installs its protocol (#0)
 | 
						|
	 * and if we haven't removed this, then grub will get the *first*
 | 
						|
	 * shim's protocol, but it'll get the second shim's systab
 | 
						|
	 * replacements.  So even though it will participate and verify
 | 
						|
	 * the kernel, the systab never finds out.
 | 
						|
	 */
 | 
						|
	efi_status = LibLocateProtocol(&SHIM_LOCK_GUID, (VOID **)&shim_lock);
 | 
						|
	if (!EFI_ERROR(efi_status))
 | 
						|
		uninstall_shim_protocols();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Install the protocol
 | 
						|
	 */
 | 
						|
	efi_status = gBS->InstallProtocolInterface(&shim_lock_handle,
 | 
						|
						   &SHIM_LOCK_GUID,
 | 
						|
						   EFI_NATIVE_INTERFACE,
 | 
						|
						   &shim_lock_interface);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		console_error(L"Could not install security protocol",
 | 
						|
			      efi_status);
 | 
						|
		return efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!secure_mode())
 | 
						|
		return EFI_SUCCESS;
 | 
						|
 | 
						|
#if defined(OVERRIDE_SECURITY_POLICY)
 | 
						|
	/*
 | 
						|
	 * Install the security protocol hook
 | 
						|
	 */
 | 
						|
	security_policy_install(shim_verify);
 | 
						|
#endif
 | 
						|
 | 
						|
	return EFI_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
uninstall_shim_protocols(void)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * If we're back here then clean everything up before exiting
 | 
						|
	 */
 | 
						|
	gBS->UninstallProtocolInterface(shim_lock_handle, &SHIM_LOCK_GUID,
 | 
						|
					&shim_lock_interface);
 | 
						|
 | 
						|
	if (!secure_mode())
 | 
						|
		return;
 | 
						|
 | 
						|
#if defined(OVERRIDE_SECURITY_POLICY)
 | 
						|
	/*
 | 
						|
	 * Clean up the security protocol hook
 | 
						|
	 */
 | 
						|
	security_policy_uninstall();
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
EFI_STATUS
 | 
						|
shim_init(void)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
 | 
						|
	dprint(L"%a", shim_version);
 | 
						|
 | 
						|
	/* Set the second stage loader */
 | 
						|
	efi_status = set_second_stage(global_image_handle);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		perror(L"set_second_stage() failed: %r\n", efi_status);
 | 
						|
		return efi_status;
 | 
						|
	}
 | 
						|
 | 
						|
	if (secure_mode()) {
 | 
						|
		if (vendor_authorized_size || vendor_deauthorized_size) {
 | 
						|
			/*
 | 
						|
			 * If shim includes its own certificates then ensure
 | 
						|
			 * that anything it boots has performed some
 | 
						|
			 * validation of the next image.
 | 
						|
			 */
 | 
						|
			hook_system_services(systab);
 | 
						|
			loader_is_participating = 0;
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	hook_exit(systab);
 | 
						|
 | 
						|
	efi_status = install_shim_protocols();
 | 
						|
	if (EFI_ERROR(efi_status))
 | 
						|
		perror(L"install_shim_protocols() failed: %r\n", efi_status);
 | 
						|
 | 
						|
	return efi_status;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
shim_fini(void)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * Remove our protocols
 | 
						|
	 */
 | 
						|
	uninstall_shim_protocols();
 | 
						|
 | 
						|
	if (secure_mode()) {
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Remove our hooks from system services.
 | 
						|
		 */
 | 
						|
		unhook_system_services();
 | 
						|
	}
 | 
						|
 | 
						|
	unhook_exit();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Free the space allocated for the alternative 2nd stage loader
 | 
						|
	 */
 | 
						|
	if (load_options_size > 0 && second_stage)
 | 
						|
		FreePool(second_stage);
 | 
						|
 | 
						|
	console_fini();
 | 
						|
}
 | 
						|
 | 
						|
extern EFI_STATUS
 | 
						|
efi_main(EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab);
 | 
						|
 | 
						|
static void
 | 
						|
__attribute__((__optimize__("0")))
 | 
						|
debug_hook(void)
 | 
						|
{
 | 
						|
	UINT8 *data = NULL;
 | 
						|
	UINTN dataSize = 0;
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	register volatile UINTN x = 0;
 | 
						|
	extern char _text, _data;
 | 
						|
 | 
						|
	const CHAR16 * const debug_var_name =
 | 
						|
#ifdef ENABLE_SHIM_DEVEL
 | 
						|
		L"SHIM_DEVEL_DEBUG";
 | 
						|
#else
 | 
						|
		L"SHIM_DEBUG";
 | 
						|
#endif
 | 
						|
 | 
						|
	if (x)
 | 
						|
		return;
 | 
						|
 | 
						|
	efi_status = get_variable(debug_var_name, &data, &dataSize,
 | 
						|
				  SHIM_LOCK_GUID);
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	FreePool(data);
 | 
						|
 | 
						|
	console_print(L"add-symbol-file "DEBUGDIR
 | 
						|
		      L"shim" EFI_ARCH L".efi.debug 0x%08x -s .data 0x%08x\n",
 | 
						|
		      &_text, &_data);
 | 
						|
 | 
						|
	console_print(L"Pausing for debugger attachment.\n");
 | 
						|
	console_print(L"To disable this, remove the EFI variable %s-%g .\n",
 | 
						|
		      debug_var_name, &SHIM_LOCK_GUID);
 | 
						|
	x = 1;
 | 
						|
	while (x++) {
 | 
						|
		/* Make this so it can't /totally/ DoS us. */
 | 
						|
#if defined(__x86_64__) || defined(__i386__) || defined(__i686__)
 | 
						|
		if (x > 4294967294ULL)
 | 
						|
			break;
 | 
						|
#elif defined(__aarch64__)
 | 
						|
		if (x > 1000)
 | 
						|
			break;
 | 
						|
#else
 | 
						|
		if (x > 12000)
 | 
						|
			break;
 | 
						|
#endif
 | 
						|
		pause();
 | 
						|
	}
 | 
						|
	x = 1;
 | 
						|
}
 | 
						|
 | 
						|
EFI_STATUS
 | 
						|
efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab)
 | 
						|
{
 | 
						|
	EFI_STATUS efi_status;
 | 
						|
	EFI_HANDLE image_handle;
 | 
						|
 | 
						|
	verification_method = VERIFIED_BY_NOTHING;
 | 
						|
 | 
						|
	vendor_authorized_size = cert_table.vendor_authorized_size;
 | 
						|
	vendor_authorized = (UINT8 *)&cert_table + cert_table.vendor_authorized_offset;
 | 
						|
 | 
						|
	vendor_deauthorized_size = cert_table.vendor_deauthorized_size;
 | 
						|
	vendor_deauthorized = (UINT8 *)&cert_table + cert_table.vendor_deauthorized_offset;
 | 
						|
 | 
						|
#if defined(ENABLE_SHIM_CERT)
 | 
						|
	build_cert_size = sizeof(shim_cert);
 | 
						|
	build_cert = shim_cert;
 | 
						|
#endif /* defined(ENABLE_SHIM_CERT) */
 | 
						|
 | 
						|
	CHAR16 *msgs[] = {
 | 
						|
		L"import_mok_state() failed",
 | 
						|
		L"shim_init() failed",
 | 
						|
		NULL
 | 
						|
	};
 | 
						|
	int msg = 0;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Set up the shim lock protocol so that grub and MokManager can
 | 
						|
	 * call back in and use shim functions
 | 
						|
	 */
 | 
						|
	shim_lock_interface.Verify = shim_verify;
 | 
						|
	shim_lock_interface.Hash = shim_hash;
 | 
						|
	shim_lock_interface.Context = shim_read_header;
 | 
						|
 | 
						|
	systab = passed_systab;
 | 
						|
	image_handle = global_image_handle = passed_image_handle;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Ensure that gnu-efi functions are available
 | 
						|
	 */
 | 
						|
	InitializeLib(image_handle, systab);
 | 
						|
	setup_verbosity();
 | 
						|
 | 
						|
	dprint(L"vendor_authorized:0x%08lx vendor_authorized_size:%lu\n",
 | 
						|
	       vendor_authorized, vendor_authorized_size);
 | 
						|
	dprint(L"vendor_deauthorized:0x%08lx vendor_deauthorized_size:%lu\n",
 | 
						|
	       vendor_deauthorized, vendor_deauthorized_size);
 | 
						|
	init_openssl();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * if SHIM_DEBUG is set, wait for a debugger to attach.
 | 
						|
	 */
 | 
						|
	debug_hook();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Before we do anything else, validate our non-volatile,
 | 
						|
	 * boot-services-only state variables are what we think they are.
 | 
						|
	 */
 | 
						|
	efi_status = import_mok_state(image_handle);
 | 
						|
	if (!secure_mode() && efi_status == EFI_INVALID_PARAMETER) {
 | 
						|
		/*
 | 
						|
		 * Make copy failures fatal only if secure_mode is enabled, or
 | 
						|
		 * the error was anything else than EFI_INVALID_PARAMETER.
 | 
						|
		 * There are non-secureboot firmware implementations that don't
 | 
						|
		 * reserve enough EFI variable memory to fit the variable.
 | 
						|
		 */
 | 
						|
		console_print(L"Importing MOK states has failed: %s: %r\n",
 | 
						|
			      msgs[msg], efi_status);
 | 
						|
		console_print(L"Continuing boot since secure mode is disabled");
 | 
						|
	} else if (EFI_ERROR(efi_status)) {
 | 
						|
die:
 | 
						|
		console_print(L"Something has gone seriously wrong: %s: %r\n",
 | 
						|
			      msgs[msg], efi_status);
 | 
						|
		msleep(5000000);
 | 
						|
		gRT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION,
 | 
						|
				 0, NULL);
 | 
						|
	}
 | 
						|
 | 
						|
	efi_status = shim_init();
 | 
						|
	if (EFI_ERROR(efi_status)) {
 | 
						|
		msg = 1;
 | 
						|
		goto die;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Tell the user that we're in insecure mode if necessary
 | 
						|
	 */
 | 
						|
	if (user_insecure_mode) {
 | 
						|
		console_print(L"Booting in insecure mode\n");
 | 
						|
		msleep(2000000);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Hand over control to the second stage bootloader
 | 
						|
	 */
 | 
						|
	efi_status = init_grub(image_handle);
 | 
						|
 | 
						|
	shim_fini();
 | 
						|
	return efi_status;
 | 
						|
}
 |