mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-04-30 19:16:25 +00:00
2007 lines
52 KiB
C
2007 lines
52 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"
|
|
#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 EFI_LOADED_IMAGE *shim_li;
|
|
static EFI_LOADED_IMAGE shim_li_bak;
|
|
|
|
list_t sbat_var;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
#define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }}
|
|
|
|
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, CertList->SignatureSize, Cert);
|
|
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, CertList->SignatureSize, Cert);
|
|
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"MokListRT", 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(MokListRT, sha256hash) != DATA_FOUND\n");
|
|
}
|
|
if (cert && check_db_cert(L"MokListRT", 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(MokListRT, 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) {
|
|
CHAR16 *title = L"Secure boot not enabled";
|
|
CHAR16 *message = L"Press any key to continue";
|
|
console_countdown(title, message, 5);
|
|
}
|
|
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) {
|
|
CHAR16 *title = L"Platform is in setup mode";
|
|
CHAR16 *message = L"Press any key to continue";
|
|
console_countdown(title, message, 5);
|
|
}
|
|
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_authenticode (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 (checked_add(context->SecDir->Size, context->SecDir->VirtualAddress, &offset) ||
|
|
offset > size) {
|
|
perror(L"Certificate Database size is too large\n");
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
offset = 0;
|
|
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;
|
|
|
|
if ((uint64_t)(uintptr_t)&sig[1]
|
|
> (uint64_t)(uintptr_t)data + datasize) {
|
|
perror(L"Certificate size is too large for secruity database");
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Check that the binary is permitted to load by SBAT.
|
|
*/
|
|
EFI_STATUS
|
|
verify_buffer_sbat (char *data, int datasize,
|
|
PE_COFF_LOADER_IMAGE_CONTEXT *context)
|
|
{
|
|
int i;
|
|
EFI_IMAGE_SECTION_HEADER *Section;
|
|
char *SBATBase = NULL;
|
|
size_t SBATSize = 0;
|
|
|
|
Section = context->FirstSection;
|
|
for (i = 0; i < context->NumberOfSections; i++, Section++) {
|
|
if ((uint64_t)(uintptr_t)&Section[1]
|
|
> (uintptr_t)(uintptr_t)data + datasize) {
|
|
perror(L"Section exceeds bounds of image\n");
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
if (CompareMem(Section->Name, ".sbat\0\0\0", 8) != 0)
|
|
continue;
|
|
|
|
if (SBATBase || SBATSize) {
|
|
perror(L"Image has multiple SBAT sections\n");
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
if (Section->NumberOfRelocations != 0 ||
|
|
Section->PointerToRelocations != 0) {
|
|
perror(L"SBAT section has relocations\n");
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
/* The virtual size corresponds to the size of the SBAT
|
|
* metadata and isn't necessarily a multiple of the file
|
|
* alignment. The on-disk size is a multiple of the file
|
|
* alignment and is zero padded. Make sure that the
|
|
* on-disk size is at least as large as virtual size,
|
|
* and ignore the section if it isn't. */
|
|
if (Section->SizeOfRawData &&
|
|
Section->SizeOfRawData >= Section->Misc.VirtualSize) {
|
|
uint64_t boundary;
|
|
SBATBase = ImageAddress(data, datasize,
|
|
Section->PointerToRawData);
|
|
SBATSize = Section->SizeOfRawData;
|
|
dprint(L"sbat section base:0x%lx size:0x%lx\n",
|
|
SBATBase, SBATSize);
|
|
if (checked_add((uint64_t)(uintptr_t)SBATBase, SBATSize, &boundary) ||
|
|
(boundary > (uint64_t)(uintptr_t)data + datasize)) {
|
|
perror(L"Section exceeds bounds of image\n");
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
return verify_sbat_section(SBATBase, SBATSize);
|
|
}
|
|
|
|
/*
|
|
* Check that the signature is valid and matches the binary and that
|
|
* the binary is permitted to load by SBAT.
|
|
*/
|
|
EFI_STATUS
|
|
verify_buffer (char *data, int datasize,
|
|
PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
|
UINT8 *sha256hash, UINT8 *sha1hash)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
|
|
efi_status = verify_buffer_authenticode(data, datasize, context, sha256hash, sha1hash);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
return verify_buffer_sbat(data, datasize, context);
|
|
}
|
|
|
|
static int
|
|
is_removable_media_path(EFI_LOADED_IMAGE *li)
|
|
{
|
|
unsigned int pathlen = 0;
|
|
CHAR16 *bootpath = NULL;
|
|
int ret = 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;
|
|
|
|
ret = 1;
|
|
|
|
error:
|
|
if (bootpath)
|
|
FreePool(bootpath);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
should_use_fallback(EFI_HANDLE image_handle)
|
|
{
|
|
EFI_LOADED_IMAGE *li;
|
|
EFI_FILE_IO_INTERFACE *fio = NULL;
|
|
EFI_FILE *vh = NULL;
|
|
EFI_FILE *fh = NULL;
|
|
EFI_STATUS efi_status;
|
|
int ret = 0;
|
|
|
|
efi_status = BS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID,
|
|
(void **)&li);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Could not get image for boot" EFI_ARCH L".efi: %r\n",
|
|
efi_status);
|
|
return 0;
|
|
}
|
|
|
|
if (!is_removable_media_path(li))
|
|
goto error;
|
|
|
|
efi_status = BS->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);
|
|
|
|
return ret;
|
|
}
|
|
/*
|
|
* 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 = BS->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;
|
|
}
|
|
|
|
VOID
|
|
restore_loaded_image(VOID)
|
|
{
|
|
if (shim_li->FilePath)
|
|
FreePool(shim_li->FilePath);
|
|
|
|
/*
|
|
* Restore our original loaded image values
|
|
*/
|
|
CopyMem(shim_li, &shim_li_bak, sizeof(shim_li_bak));
|
|
}
|
|
|
|
/* If gets used on static data it probably needs boundary checking */
|
|
void
|
|
str16_to_str8(CHAR16 *str16, CHAR8 **str8)
|
|
{
|
|
int i = 0;
|
|
|
|
while (str16[i++] != '\0');
|
|
*str8 = (CHAR8 *)AllocatePool((i + 1) * sizeof (CHAR8));
|
|
|
|
i = 0;
|
|
while (str16[i] != '\0') {
|
|
(*str8)[i] = (CHAR8)str16[i];
|
|
i++;
|
|
}
|
|
(*str8)[i] = '\0';
|
|
}
|
|
|
|
/*
|
|
* Load and run an EFI executable
|
|
*/
|
|
EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath,
|
|
CHAR16 **PathName, void **data, int *datasize)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
void *sourcebuffer = NULL;
|
|
UINT64 sourcesize = 0;
|
|
CHAR8 *netbootname;
|
|
|
|
/*
|
|
* We need to refer to the loaded image protocol on the running
|
|
* binary in order to find our path
|
|
*/
|
|
efi_status = BS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID,
|
|
(void **)&shim_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(shim_li, ImagePath, PathName);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Unable to generate path %s: %r\n", ImagePath,
|
|
efi_status);
|
|
return efi_status;
|
|
}
|
|
|
|
if (findNetboot(shim_li->DeviceHandle)) {
|
|
str16_to_str8(ImagePath, &netbootname);
|
|
efi_status = parseNetbootinfo(image_handle, netbootname);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Netboot parsing failed: %r\n", efi_status);
|
|
return EFI_PROTOCOL_ERROR;
|
|
}
|
|
FreePool(netbootname);
|
|
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(shim_li->DeviceHandle)) {
|
|
str16_to_str8(ImagePath, &netbootname);
|
|
efi_status = httpboot_fetch_buffer (image_handle,
|
|
&sourcebuffer,
|
|
&sourcesize,
|
|
netbootname);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Unable to fetch HTTP image %a: %r\n",
|
|
netbootname, efi_status);
|
|
return efi_status;
|
|
}
|
|
*data = sourcebuffer;
|
|
*datasize = sourcesize;
|
|
} else {
|
|
/*
|
|
* Read the new executable off disk
|
|
*/
|
|
efi_status = load_image(shim_li, data, datasize, *PathName);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to load image %s: %r\n",
|
|
PathName, efi_status);
|
|
PrintErrors();
|
|
ClearErrors();
|
|
return efi_status;
|
|
}
|
|
}
|
|
|
|
if (*datasize < 0)
|
|
efi_status = EFI_INVALID_PARAMETER;
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
/*
|
|
* Load and run an EFI executable
|
|
*/
|
|
EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
EFI_IMAGE_ENTRY_POINT entry_point;
|
|
EFI_PHYSICAL_ADDRESS alloc_address;
|
|
UINTN alloc_pages;
|
|
CHAR16 *PathName = NULL;
|
|
void *data = NULL;
|
|
int datasize = 0;
|
|
|
|
efi_status = read_image(image_handle, ImagePath, &PathName, &data,
|
|
&datasize);
|
|
if (EFI_ERROR(efi_status))
|
|
goto done;
|
|
|
|
/*
|
|
* We need to modify the loaded image protocol entry before running
|
|
* the new binary, so back it up
|
|
*/
|
|
CopyMem(&shim_li_bak, shim_li, sizeof(shim_li_bak));
|
|
|
|
/*
|
|
* Update the loaded image with the second stage loader file path
|
|
*/
|
|
shim_li->FilePath = FileDevicePath(NULL, PathName);
|
|
if (!shim_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, shim_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:
|
|
restore_loaded_image();
|
|
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);
|
|
usleep(2000000);
|
|
return efi_status;
|
|
}
|
|
|
|
efi_status = start_image(image_handle,
|
|
use_fb ? FALLBACK : second_stage);
|
|
}
|
|
|
|
// If the filename is invalid, or the file does not exist,
|
|
// just fallback to the default loader.
|
|
if (!use_fb && (efi_status == EFI_INVALID_PARAMETER ||
|
|
efi_status == EFI_NOT_FOUND)) {
|
|
console_print(
|
|
L"start_image() returned %r, falling back to default loader\n",
|
|
efi_status);
|
|
usleep(2000000);
|
|
load_options = NULL;
|
|
load_options_size = 0;
|
|
efi_status = start_image(image_handle, DEFAULT_LOADER);
|
|
}
|
|
|
|
if (EFI_ERROR(efi_status)) {
|
|
console_print(L"start_image() returned %r\n", efi_status);
|
|
usleep(2000000);
|
|
}
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
second_stage = DEFAULT_LOADER;
|
|
load_options = NULL;
|
|
load_options_size = 0;
|
|
|
|
efi_status = BS->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;
|
|
}
|
|
|
|
#if defined(DISABLE_REMOVABLE_LOAD_OPTIONS)
|
|
/*
|
|
* boot services build very strange load options, and we might misparse them,
|
|
* causing boot failures on removable media.
|
|
*/
|
|
if (is_removable_media_path(li)) {
|
|
dprint("Invoked from removable media path, ignoring boot options");
|
|
return EFI_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
efi_status = parse_load_options(li);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror (L"Failed to get load options: %r\n", efi_status);
|
|
return efi_status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
init_openssl(void)
|
|
{
|
|
OPENSSL_init();
|
|
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 = BS->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
|
|
*/
|
|
BS->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
|
|
}
|
|
|
|
static void
|
|
check_section_helper(char *section_name, int len, void **pointer,
|
|
EFI_IMAGE_SECTION_HEADER *Section, void *data,
|
|
int datasize, size_t minsize)
|
|
{
|
|
if (CompareMem(Section->Name, section_name, len) == 0) {
|
|
*pointer = ImageAddress(data, datasize, Section->PointerToRawData);
|
|
if (Section->SizeOfRawData < minsize) {
|
|
dprint(L"found and rejected %.*a bad size\n", len, section_name);
|
|
dprint(L"minsize: %d\n", minsize);
|
|
dprint(L"rawsize: %d\n", Section->SizeOfRawData);
|
|
return ;
|
|
}
|
|
if (!*pointer) {
|
|
return ;
|
|
}
|
|
dprint(L"found %.*a\n", len, section_name);
|
|
}
|
|
}
|
|
|
|
#define check_section(section_name, pointer, section, data, datasize, minsize) \
|
|
check_section_helper(section_name, sizeof(section_name) - 1, pointer, \
|
|
section, data, datasize, minsize)
|
|
|
|
EFI_STATUS
|
|
load_revocations_file(EFI_HANDLE image_handle, CHAR16 *PathName)
|
|
{
|
|
EFI_STATUS efi_status = EFI_SUCCESS;
|
|
PE_COFF_LOADER_IMAGE_CONTEXT context;
|
|
EFI_IMAGE_SECTION_HEADER *Section;
|
|
int datasize = 0;
|
|
void *data = NULL;
|
|
unsigned int i;
|
|
char *sbat_var_automatic = NULL;
|
|
char *sbat_var_latest = NULL;
|
|
uint8_t *ssps_automatic = NULL;
|
|
uint8_t *sspv_automatic = NULL;
|
|
uint8_t *ssps_latest = NULL;
|
|
uint8_t *sspv_latest = NULL;
|
|
|
|
efi_status = read_image(image_handle, L"revocations.efi", &PathName,
|
|
&data, &datasize);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
efi_status = verify_image(data, datasize, shim_li, &context);
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"revocations failed to verify\n");
|
|
return efi_status;
|
|
}
|
|
dprint(L"verified revocations\n");
|
|
|
|
Section = context.FirstSection;
|
|
for (i = 0; i < context.NumberOfSections; i++, Section++) {
|
|
dprint(L"checking section \"%c%c%c%c%c%c%c%c\"\n", (char *)Section->Name);
|
|
check_section(".sbata\0\0", (void **)&sbat_var_automatic, Section,
|
|
data, datasize, sizeof(SBAT_VAR_ORIGINAL));
|
|
check_section(".sbatl\0\0", (void **)&sbat_var_latest, Section,
|
|
data, datasize, sizeof(SBAT_VAR_ORIGINAL));
|
|
check_section(".sspva\0\0", (void **)&sspv_automatic, Section,
|
|
data, datasize, SSPVER_SIZE);
|
|
check_section(".sspsa\0\0", (void **)&ssps_automatic, Section,
|
|
data, datasize, SSPSIG_SIZE);
|
|
check_section(".sspvl\0\0", (void **)&sspv_latest, Section,
|
|
data, datasize, SSPVER_SIZE);
|
|
check_section(".sspsl\0\0", (void **)&ssps_latest, Section,
|
|
data, datasize, SSPSIG_SIZE);
|
|
}
|
|
|
|
if (sbat_var_latest && sbat_var_automatic) {
|
|
dprint(L"attempting to update SBAT_LEVEL\n");
|
|
efi_status = set_sbat_uefi_variable(sbat_var_automatic,
|
|
sbat_var_latest);
|
|
} else {
|
|
dprint(L"no data for SBAT_LEVEL\n");
|
|
}
|
|
|
|
if ((sspv_automatic && ssps_automatic) || (sspv_latest && ssps_latest)) {
|
|
dprint(L"attempting to update SkuSiPolicy\n");
|
|
efi_status = set_ssp_uefi_variable(sspv_automatic, ssps_automatic,
|
|
sspv_latest, ssps_latest);
|
|
|
|
} else {
|
|
dprint(L"no data for SkuSiPolicy\n");
|
|
}
|
|
|
|
FreePool(data);
|
|
return efi_status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
load_cert_file(EFI_HANDLE image_handle, CHAR16 *filename, CHAR16 *PathName)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
PE_COFF_LOADER_IMAGE_CONTEXT context;
|
|
EFI_IMAGE_SECTION_HEADER *Section;
|
|
EFI_SIGNATURE_LIST *certlist;
|
|
void *pointer;
|
|
UINT32 original;
|
|
int datasize = 0;
|
|
void *data = NULL;
|
|
int i;
|
|
|
|
efi_status = read_image(image_handle, filename, &PathName,
|
|
&data, &datasize);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
efi_status = verify_image(data, datasize, shim_li, &context);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
Section = context.FirstSection;
|
|
for (i = 0; i < context.NumberOfSections; i++, Section++) {
|
|
if (CompareMem(Section->Name, ".db\0\0\0\0\0", 8) == 0) {
|
|
original = user_cert_size;
|
|
if (Section->SizeOfRawData < sizeof(EFI_SIGNATURE_LIST)) {
|
|
continue;
|
|
}
|
|
pointer = ImageAddress(data, datasize,
|
|
Section->PointerToRawData);
|
|
if (!pointer) {
|
|
continue;
|
|
}
|
|
certlist = pointer;
|
|
user_cert_size += certlist->SignatureListSize;;
|
|
user_cert = ReallocatePool(user_cert, original,
|
|
user_cert_size);
|
|
CopyMem(user_cert + original, pointer,
|
|
certlist->SignatureListSize);
|
|
}
|
|
}
|
|
FreePool(data);
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Read additional certificates and SBAT Level requirements from files
|
|
* (after verifying signatures)
|
|
*/
|
|
EFI_STATUS
|
|
load_unbundled_trust(EFI_HANDLE image_handle)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
EFI_LOADED_IMAGE *li = NULL;
|
|
CHAR16 *PathName = NULL;
|
|
EFI_FILE *root, *dir;
|
|
EFI_FILE_INFO *info;
|
|
EFI_HANDLE device;
|
|
EFI_FILE_IO_INTERFACE *drive;
|
|
UINTN buffersize = 0;
|
|
void *buffer = NULL;
|
|
BOOLEAN search_revocations = TRUE;
|
|
|
|
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;
|
|
}
|
|
|
|
efi_status = generate_path_from_image_path(li, L"", &PathName);
|
|
if (EFI_ERROR(efi_status))
|
|
goto done;
|
|
|
|
device = li->DeviceHandle;
|
|
efi_status = gBS->HandleProtocol(device, &EFI_SIMPLE_FILE_SYSTEM_GUID,
|
|
(void **)&drive);
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"Failed to find fs on local drive (netboot?): %r \n",
|
|
efi_status);
|
|
/*
|
|
* Network boot cases do not support reading a directory. Try
|
|
* to read revocations.efi to pull in any unbundled SBATLevel
|
|
* updates unconditionally in those cases. This may produce
|
|
* console noise when the file is not present.
|
|
*/
|
|
load_cert_file(image_handle, REVOCATIONFILE, PathName);
|
|
goto done;
|
|
}
|
|
|
|
efi_status = drive->OpenVolume(drive, &root);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to open fs: %r\n", efi_status);
|
|
goto done;
|
|
}
|
|
|
|
efi_status = root->Open(root, &dir, PathName, EFI_FILE_MODE_READ, 0);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to open %s - %r\n", PathName, efi_status);
|
|
goto done;
|
|
}
|
|
|
|
if (!secure_mode())
|
|
goto done;
|
|
|
|
|
|
while (true) {
|
|
UINTN old = buffersize;
|
|
|
|
efi_status = dir->Read(dir, &buffersize, buffer);
|
|
if (efi_status == EFI_BUFFER_TOO_SMALL) {
|
|
if (buffersize == old) {
|
|
/*
|
|
* Some UEFI drivers or firmwares are not compliant with
|
|
* the EFI_FILE_PROTOCOL.Read() specs and do not return the
|
|
* required buffer size along with EFI_BUFFER_TOO_SMALL.
|
|
* Work around this by progressively increasing the buffer
|
|
* size, up to a certain point, until the call succeeds.
|
|
*/
|
|
perror(L"Error reading directory %s - non-compliant UEFI driver or firmware!\n",
|
|
PathName);
|
|
buffersize = (buffersize < 4) ? 4 : buffersize * 2;
|
|
if (buffersize > 1024)
|
|
goto done;
|
|
}
|
|
buffer = ReallocatePool(buffer, old, buffersize);
|
|
if (buffer == NULL) {
|
|
perror(L"Failed to read directory %s - %r\n",
|
|
PathName, EFI_OUT_OF_RESOURCES);
|
|
goto done;
|
|
}
|
|
continue;
|
|
} else if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to read directory %s - %r\n", PathName,
|
|
efi_status);
|
|
goto done;
|
|
}
|
|
|
|
info = (EFI_FILE_INFO *)buffer;
|
|
if (buffersize == 0 || !info) {
|
|
if (search_revocations) {
|
|
search_revocations = FALSE;
|
|
efi_status = root->Open(root, &dir, PathName,
|
|
EFI_FILE_MODE_READ, 0);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to open %s - %r\n",
|
|
PathName, efi_status);
|
|
goto done;
|
|
}
|
|
continue;
|
|
} else {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In the event that there are unprocessed revocation
|
|
* additions, they could be intended to ban any *new* trust
|
|
* anchors we find here. With that in mind, we always want to
|
|
* do a pass of loading revocations before we try to add
|
|
* anything new to our allowlist. This is done by making two
|
|
* passes over the directory, first to search for the
|
|
* revocations.efi file then to search for shim_certificate.efi
|
|
*/
|
|
if (search_revocations &&
|
|
StrCaseCmp(info->FileName, REVOCATIONFILE) == 0) {
|
|
load_revocations_file(image_handle, PathName);
|
|
search_revocations = FALSE;
|
|
efi_status = root->Open(root, &dir, PathName,
|
|
EFI_FILE_MODE_READ, 0);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to open %s - %r\n",
|
|
PathName, efi_status);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (!search_revocations &&
|
|
StrCaseCmp(info->FileName, L"shim_certificate.efi") == 0) {
|
|
load_cert_file(image_handle, info->FileName, PathName);
|
|
}
|
|
}
|
|
done:
|
|
FreePool(buffer);
|
|
FreePool(PathName);
|
|
return efi_status;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (secure_mode())
|
|
cleanup_sbat_var(&sbat_var);
|
|
|
|
/*
|
|
* Remove our protocols
|
|
*/
|
|
uninstall_shim_protocols();
|
|
|
|
if (secure_mode()) {
|
|
|
|
/*
|
|
* Remove our hooks from system services.
|
|
*/
|
|
unhook_system_services();
|
|
}
|
|
|
|
unhook_exit();
|
|
|
|
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;
|
|
|
|
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
|
|
wait_for_debug();
|
|
}
|
|
x = 1;
|
|
}
|
|
|
|
typedef enum {
|
|
COLD_RESET,
|
|
EXIT_FAILURE,
|
|
EXIT_SUCCESS, // keep this one last
|
|
} devel_egress_action;
|
|
|
|
void
|
|
devel_egress(devel_egress_action action UNUSED)
|
|
{
|
|
#ifdef ENABLE_SHIM_DEVEL
|
|
char *reasons[] = {
|
|
[COLD_RESET] = "reset",
|
|
[EXIT_FAILURE] = "exit",
|
|
};
|
|
if (action == EXIT_SUCCESS)
|
|
return;
|
|
|
|
console_print(L"Waiting to %a...", reasons[action]);
|
|
for (size_t sleepcount = 0; sleepcount < 10; sleepcount++) {
|
|
console_print(L"%d...", 10 - sleepcount);
|
|
usleep(1000000);
|
|
}
|
|
console_print(L"\ndoing %a\n", action);
|
|
|
|
if (action == COLD_RESET)
|
|
RT->ResetSystem(EfiResetCold, EFI_SECURITY_VIOLATION, 0, NULL);
|
|
#endif
|
|
}
|
|
|
|
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",
|
|
L"import of SBAT data failed",
|
|
L"SBAT self-check failed",
|
|
SBAT_VAR_NAME L" UEFI variable setting failed",
|
|
NULL
|
|
};
|
|
enum {
|
|
IMPORT_MOK_STATE,
|
|
SHIM_INIT,
|
|
IMPORT_SBAT,
|
|
SBAT_SELF_CHECK,
|
|
SET_SBAT,
|
|
} msg = IMPORT_MOK_STATE;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* if SHIM_DEBUG is set, wait for a debugger to attach.
|
|
*/
|
|
debug_hook();
|
|
|
|
efi_status = set_sbat_uefi_variable_internal();
|
|
if (EFI_ERROR(efi_status) && secure_mode()) {
|
|
perror(L"%s variable initialization failed\n", SBAT_VAR_NAME);
|
|
msg = SET_SBAT;
|
|
goto die;
|
|
} else if (EFI_ERROR(efi_status)) {
|
|
dprint(L"%s variable initialization failed: %r\n",
|
|
SBAT_VAR_NAME, efi_status);
|
|
}
|
|
efi_status = set_ssp_uefi_variable_internal();
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"%s variable initialization failed: %r\n",
|
|
SSPVER_VAR_NAME, efi_status);
|
|
}
|
|
dprint(L"%s variable initialization done\n", SSPVER_VAR_NAME);
|
|
|
|
if (secure_mode()) {
|
|
char *sbat_start = (char *)&_sbat;
|
|
char *sbat_end = (char *)&_esbat;
|
|
|
|
INIT_LIST_HEAD(&sbat_var);
|
|
efi_status = parse_sbat_var(&sbat_var, NULL);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Parsing %s variable failed: %r\n",
|
|
SBAT_VAR_NAME, efi_status);
|
|
msg = IMPORT_SBAT;
|
|
goto die;
|
|
}
|
|
|
|
efi_status = verify_sbat_section(sbat_start, sbat_end - sbat_start - 1);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Verifiying shim SBAT data failed: %r\n",
|
|
efi_status);
|
|
msg = SBAT_SELF_CHECK;
|
|
goto die;
|
|
}
|
|
dprint(L"SBAT self-check succeeded\n");
|
|
}
|
|
|
|
init_openssl();
|
|
|
|
efi_status = load_unbundled_trust(global_image_handle);
|
|
if (EFI_ERROR(efi_status)) {
|
|
LogError(L"Failed to load addon certificates / sbat level\n");
|
|
}
|
|
|
|
/*
|
|
* 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 ||
|
|
efi_status == EFI_OUT_OF_RESOURCES)) {
|
|
/*
|
|
* Make copy failures fatal only if secure_mode is enabled, or
|
|
* the error was anything else than EFI_INVALID_PARAMETER or
|
|
* EFI_OUT_OF_RESOURCES.
|
|
* 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);
|
|
#if defined(ENABLE_SHIM_DEVEL)
|
|
devel_egress(COLD_RESET);
|
|
#else
|
|
usleep(5000000);
|
|
RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION,
|
|
0, NULL);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* This variable is supposed to be set by second stages, so ensure it is
|
|
* not set when we are starting up.
|
|
*/
|
|
(void) del_variable(SHIM_RETAIN_PROTOCOL_VAR_NAME, SHIM_LOCK_GUID);
|
|
|
|
efi_status = shim_init();
|
|
if (EFI_ERROR(efi_status)) {
|
|
msg = SHIM_INIT;
|
|
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");
|
|
usleep(2000000);
|
|
}
|
|
|
|
/*
|
|
* Hand over control to the second stage bootloader
|
|
*/
|
|
efi_status = init_grub(image_handle);
|
|
|
|
shim_fini();
|
|
devel_egress(EFI_ERROR(efi_status) ? EXIT_FAILURE : EXIT_SUCCESS);
|
|
return efi_status;
|
|
}
|