mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-06-14 14:48:19 +00:00
1369 lines
38 KiB
C
1369 lines
38 KiB
C
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
/*
|
|
* mok.c - MoK variable processing
|
|
* Copyright 2017 Peter Jones <pjones@redhat.com>
|
|
*/
|
|
|
|
#include "shim.h"
|
|
|
|
/*
|
|
* Check if a variable exists
|
|
*/
|
|
static BOOLEAN check_var(CHAR16 *varname)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
UINTN size = sizeof(UINT32);
|
|
UINT32 MokVar;
|
|
UINT32 attributes;
|
|
|
|
efi_status = RT->GetVariable(varname, &SHIM_LOCK_GUID, &attributes,
|
|
&size, (void *)&MokVar);
|
|
if (!EFI_ERROR(efi_status) || efi_status == EFI_BUFFER_TOO_SMALL)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
#define SetVariable(name, guid, attrs, varsz, var) \
|
|
({ \
|
|
EFI_STATUS efi_status_; \
|
|
efi_status_ = RT->SetVariable(name, guid, attrs, varsz, var); \
|
|
dprint_(L"%a:%d:%a() SetVariable(\"%s\", ... varsz=0x%llx) = %r\n", \
|
|
__FILE__, __LINE__ - 5, __func__, name, varsz, \
|
|
efi_status_); \
|
|
efi_status_; \
|
|
})
|
|
|
|
static UINTN
|
|
format_hsi_status(UINT8 *buf, size_t sz,
|
|
struct mok_state_variable *msv UNUSED)
|
|
{
|
|
const char heapx[] = "heap-is-executable: ";
|
|
const char stackx[] = "\nstack-is-executable: ";
|
|
const char row[] = "\nro-sections-are-writable: ";
|
|
const char hasmap[] = "\nhas-memory-attribute-protocol: ";
|
|
const char hasdxeservices[] = "\nhas-dxe-services-table: ";
|
|
const char hasdsgmsd[] = "\nhas-get-memory-space-descriptor: ";
|
|
const char hasdssmsa[] = "\nhas-set-memory-space-attributes: ";
|
|
const char shimhasnx[] = "\nshim-has-nx-compat-set: ";
|
|
const char finale[] = "\n";
|
|
char *pos;
|
|
|
|
/*
|
|
* sizeof includes the trailing NUL which is where our 0 or 1 value
|
|
* fits
|
|
*/
|
|
UINTN ret = sizeof(heapx) + sizeof(stackx) +
|
|
sizeof(row) + sizeof(hasmap) +
|
|
sizeof(hasdxeservices) + sizeof(hasdsgmsd) +
|
|
sizeof(hasdssmsa) + sizeof(shimhasnx) +
|
|
sizeof(finale);
|
|
|
|
if (buf == 0 || sz < ret) {
|
|
return ret;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
pos = (char *)buf;
|
|
pos = stpcpy(pos, heapx);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HEAPX) ? "1" : "0");
|
|
pos = stpcpy(pos, stackx);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_STACKX) ? "1" : "0");
|
|
pos = stpcpy(pos, row);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_ROW) ? "1" : "0");
|
|
pos = stpcpy(pos, hasmap);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASMAP) ? "1" : "0");
|
|
pos = stpcpy(pos, hasdxeservices);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASDST) ? "1" : "0");
|
|
pos = stpcpy(pos, hasdsgmsd);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASDSTGMSD) ? "1" : "0");
|
|
pos = stpcpy(pos, hasdssmsa);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASDSTSMSA) ? "1" : "0");
|
|
pos = stpcpy(pos, shimhasnx);
|
|
pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_NX) ? "1" : "0");
|
|
stpcpy(pos, finale);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* If the OS has set any of these variables we need to drop into MOK and
|
|
* handle them appropriately
|
|
*/
|
|
static EFI_STATUS check_mok_request(EFI_HANDLE image_handle)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
|
|
if (check_var(L"MokNew") || check_var(L"MokSB") ||
|
|
check_var(L"MokPW") || check_var(L"MokAuth") ||
|
|
check_var(L"MokDel") || check_var(L"MokDB") ||
|
|
check_var(L"MokXNew") || check_var(L"MokXDel") ||
|
|
check_var(L"MokXAuth") || check_var(L"MokListTrustedNew")) {
|
|
efi_status = start_image(image_handle, MOK_MANAGER);
|
|
|
|
if (EFI_ERROR(efi_status)) {
|
|
/*
|
|
* We don't do this in the unit tests because we
|
|
* don't have simulation for console_countdown()
|
|
* and similar.
|
|
*/
|
|
#ifndef SHIM_UNIT_TEST
|
|
EFI_STATUS efi_status_2;
|
|
EFI_LOADED_IMAGE *li;
|
|
efi_status_2 = BS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID,
|
|
(void **)&li);
|
|
if (EFI_ERROR(efi_status_2))
|
|
perror (L"Failed to get image: %r\n", efi_status_2);
|
|
else if (is_removable_media_path(li) &&
|
|
efi_status == EFI_NOT_FOUND) {
|
|
CHAR16 *title = L"Could not find MokManager";
|
|
CHAR16 *message = L"MokManager is missing on removable media.";
|
|
/*
|
|
* This occurs when system is booting on
|
|
* hard disk's EFI/BOOT/BOOTxxx.EFI entry
|
|
* while it should have booted on
|
|
* EFI/<os>/shimxxx.efi entry
|
|
*/
|
|
console_countdown(title, message, 10);
|
|
RT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL);
|
|
}
|
|
#endif
|
|
perror(L"Failed to start MokManager: %r\n", efi_status);
|
|
return efi_status;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static vendor_addend_category_t
|
|
categorize_authorized(struct mok_state_variable *v)
|
|
{
|
|
if (!(v->addend && v->addend_size &&
|
|
*v->addend && *v->addend_size)) {
|
|
return VENDOR_ADDEND_NONE;
|
|
}
|
|
|
|
return vendor_authorized_category;
|
|
}
|
|
|
|
static vendor_addend_category_t
|
|
categorize_deauthorized(struct mok_state_variable *v)
|
|
{
|
|
if (!(v->addend && v->addend_size &&
|
|
*v->addend && *v->addend_size)) {
|
|
return VENDOR_ADDEND_NONE;
|
|
}
|
|
|
|
return VENDOR_ADDEND_DB;
|
|
}
|
|
|
|
struct mok_state_variable mok_state_variable_data[] = {
|
|
{.name = L"MokList",
|
|
.name8 = "MokList",
|
|
.rtname = L"MokListRT",
|
|
.rtname8 = "MokListRT",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
.categorize_addend = categorize_authorized,
|
|
.addend = &vendor_authorized,
|
|
.addend_size = &vendor_authorized_size,
|
|
.user_cert = &user_cert,
|
|
.user_cert_size = &user_cert_size,
|
|
#if defined(ENABLE_SHIM_CERT)
|
|
.build_cert = &build_cert,
|
|
.build_cert_size = &build_cert_size,
|
|
#endif /* defined(ENABLE_SHIM_CERT) */
|
|
.flags = MOK_MIRROR_KEYDB |
|
|
MOK_MIRROR_DELETE_FIRST |
|
|
MOK_VARIABLE_LOG,
|
|
.pcr = 14,
|
|
},
|
|
{.name = L"MokListX",
|
|
.name8 = "MokListX",
|
|
.rtname = L"MokListXRT",
|
|
.rtname8 = "MokListXRT",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
.categorize_addend = categorize_deauthorized,
|
|
.addend = &vendor_deauthorized,
|
|
.addend_size = &vendor_deauthorized_size,
|
|
.flags = MOK_MIRROR_KEYDB |
|
|
MOK_MIRROR_DELETE_FIRST |
|
|
MOK_VARIABLE_LOG,
|
|
.pcr = 14,
|
|
},
|
|
{.name = L"MokSBState",
|
|
.name8 = "MokSBState",
|
|
.rtname = L"MokSBStateRT",
|
|
.rtname8 = "MokSBStateRT",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
.flags = MOK_MIRROR_DELETE_FIRST |
|
|
MOK_VARIABLE_MEASURE |
|
|
MOK_VARIABLE_LOG,
|
|
.pcr = 14,
|
|
.state = &user_insecure_mode,
|
|
},
|
|
{.name = L"MokDBState",
|
|
.name8 = "MokDBState",
|
|
.rtname = L"MokIgnoreDB",
|
|
.rtname8 = "MokIgnoreDB",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
.state = &ignore_db,
|
|
},
|
|
{.name = SBAT_VAR_NAME,
|
|
.name8 = SBAT_VAR_NAME8,
|
|
.rtname = SBAT_RT_VAR_NAME,
|
|
.rtname8 = SBAT_RT_VAR_NAME8,
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
/*
|
|
* we're enforcing that SBAT can't have an RT flag here because
|
|
* there's no way to tell whether it's an authenticated variable.
|
|
*/
|
|
#if !defined(ENABLE_SHIM_DEVEL)
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
#else
|
|
.no_attr = 0,
|
|
#endif
|
|
.flags = MOK_MIRROR_DELETE_FIRST |
|
|
MOK_VARIABLE_MEASURE,
|
|
.pcr = 7,
|
|
},
|
|
{.name = L"MokListTrusted",
|
|
.name8 = "MokListTrusted",
|
|
.rtname = L"MokListTrustedRT",
|
|
.rtname8 = "MokListTrustedRT",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
.flags = MOK_MIRROR_DELETE_FIRST |
|
|
MOK_VARIABLE_INVERSE |
|
|
MOK_VARIABLE_LOG,
|
|
.pcr = 14,
|
|
.state = &trust_mok_list,
|
|
},
|
|
{.name = L"MokPolicy",
|
|
.name8 = "MokPolicy",
|
|
.rtname = L"MokPolicyRT",
|
|
.rtname8 = "MokPolicyRT",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.yes_attr = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_NON_VOLATILE,
|
|
.no_attr = EFI_VARIABLE_RUNTIME_ACCESS,
|
|
.flags = MOK_MIRROR_DELETE_FIRST |
|
|
MOK_VARIABLE_LOG,
|
|
.pcr = 14,
|
|
.state = &mok_policy,
|
|
},
|
|
{.name = L"HSIStatus",
|
|
.name8 = "HSIStatus",
|
|
.rtname = L"HSIStatus",
|
|
.rtname8 = "HSIStatus",
|
|
.guid = &SHIM_LOCK_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
.format = format_hsi_status,
|
|
},
|
|
{.name = L"AuditMode",
|
|
.name8 = "AuditMode",
|
|
.rtname = L"AuditMode",
|
|
.rtname8 = "AuditMode",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"BootOrder",
|
|
.name8 = "BootOrder",
|
|
.rtname = L"BootOrder",
|
|
.rtname8 = "BootOrder",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"BootCurrent",
|
|
.name8 = "BootCurrent",
|
|
.rtname = L"BootCurrent",
|
|
.rtname8 = "BootCurrent",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"BootNext",
|
|
.name8 = "BootNext",
|
|
.rtname = L"BootNext",
|
|
.rtname8 = "BootNext",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0000",
|
|
.name8 = "Boot0000",
|
|
.rtname = L"Boot0000",
|
|
.rtname8 = "Boot0000",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0001",
|
|
.name8 = "Boot0001",
|
|
.rtname = L"Boot0001",
|
|
.rtname8 = "Boot0001",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0002",
|
|
.name8 = "Boot0002",
|
|
.rtname = L"Boot0002",
|
|
.rtname8 = "Boot0002",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0003",
|
|
.name8 = "Boot0003",
|
|
.rtname = L"Boot0003",
|
|
.rtname8 = "Boot0003",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0004",
|
|
.name8 = "Boot0004",
|
|
.rtname = L"Boot0004",
|
|
.rtname8 = "Boot0004",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0005",
|
|
.name8 = "Boot0005",
|
|
.rtname = L"Boot0005",
|
|
.rtname8 = "Boot0005",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Boot0006",
|
|
.name8 = "Boot0006",
|
|
.rtname = L"Boot0006",
|
|
.rtname8 = "Boot0006",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"DeployedMode",
|
|
.name8 = "DeployedMode",
|
|
.rtname = L"DeployedMode",
|
|
.rtname8 = "DeployedMode",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"SecureBoot",
|
|
.name8 = "SecureBoot",
|
|
.rtname = L"SecureBoot",
|
|
.rtname8 = "SecureBoot",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"SetupMode",
|
|
.name8 = "SetupMode",
|
|
.rtname = L"SetupMode",
|
|
.rtname8 = "SetupMode",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"SignatureSupport",
|
|
.name8 = "SignatureSupport",
|
|
.rtname = L"SignatureSupport",
|
|
.rtname8 = "SignatureSupport",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Timeout",
|
|
.name8 = "Timeout",
|
|
.rtname = L"Timeout",
|
|
.rtname8 = "Timeout",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"PK",
|
|
.name8 = "PK",
|
|
.rtname = L"PK",
|
|
.rtname8 = "PK",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"KEK",
|
|
.name8 = "KEK",
|
|
.rtname = L"KEK",
|
|
.rtname8 = "KEK",
|
|
.guid = &GV_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"db",
|
|
.name8 = "db",
|
|
.rtname = L"db",
|
|
.rtname8 = "db",
|
|
.guid = &SIG_DB,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"dbx",
|
|
.name8 = "dbx",
|
|
.rtname = L"dbx",
|
|
.rtname8 = "dbx",
|
|
.guid = &SIG_DB,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{.name = L"Kernel_SkuSiStatus",
|
|
.name8 = "Kernel_SkuSiStatus",
|
|
.rtname = L"Kernel_SkuSiStatus",
|
|
.rtname8 = "Kernel_SkuSiStatus",
|
|
.guid = &SECUREBOOT_EFI_NAMESPACE_GUID,
|
|
.flags = MOK_VARIABLE_CONFIG_ONLY,
|
|
},
|
|
{ NULL, }
|
|
};
|
|
size_t n_mok_state_variables = sizeof(mok_state_variable_data) / sizeof(mok_state_variable_data[0]);
|
|
struct mok_state_variable *mok_state_variables = &mok_state_variable_data[0];
|
|
|
|
#define should_mirror_addend(v) (((v)->categorize_addend) && ((v)->categorize_addend(v) != VENDOR_ADDEND_NONE))
|
|
|
|
static inline BOOLEAN NONNULL(1)
|
|
should_mirror_build_cert(struct mok_state_variable *v)
|
|
{
|
|
return (v->build_cert && v->build_cert_size &&
|
|
*v->build_cert && *v->build_cert_size) ? TRUE : FALSE;
|
|
}
|
|
|
|
static const uint8_t null_sha256[32] = { 0, };
|
|
|
|
typedef UINTN SIZE_T;
|
|
|
|
#define EFI_MAJOR_VERSION(tablep) ((UINT16)((((tablep)->Hdr.Revision) >> 16) & 0xfffful))
|
|
#define EFI_MINOR_VERSION(tablep) ((UINT16)(((tablep)->Hdr.Revision) & 0xfffful))
|
|
|
|
static BOOLEAN is_apple_firmware_vendor(void)
|
|
{
|
|
CHAR16 vendorbuf[6] = L"";
|
|
CHAR16 *vendor = ST->FirmwareVendor;
|
|
if (!vendor)
|
|
return FALSE;
|
|
|
|
ZeroMem(vendorbuf, sizeof(vendorbuf));
|
|
|
|
/*
|
|
* We've had a problem where ST->FirmwareVendor is only as big as
|
|
* it needs to be (or at least less than the 200 bytes we formerly
|
|
* defined vendorbuf as) and it's up against a page that's not
|
|
* mapped readable, so we take a fault and reset when copying from
|
|
* it.
|
|
*
|
|
* We modeled this after kernel, which has the 200 byte CHAR16
|
|
* array and copies 198 bytes into it, so that there's a NUL
|
|
* terminator. They solve this issue by mapping the whole 200
|
|
* bytes unconditionally and then unmapping it after the copy, but
|
|
* we can't take that approach because we don't necessarily have
|
|
* page permission primitives at all.
|
|
*
|
|
* The 200 bytes (CHAR16 [100]) is an arbitrary number anyway, but
|
|
* it's likely larger than any sane vendor name, and we still want
|
|
* to do the copy into an array larger than our copied data because
|
|
* that's how we guard against failure to terminate with a NUL.
|
|
*
|
|
* So right now we're only copying ten bytes, because Apple is the
|
|
* only vendor we're testing against.
|
|
*/
|
|
CopyMem(vendorbuf, vendor, 10);
|
|
|
|
dprint(L"FirmwareVendor: \"%s\"\n", vendor);
|
|
|
|
if (StrnCmp(vendor, L"Apple", 5) == 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
get_max_var_sz(UINT32 attrs, SIZE_T *max_var_szp)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
uint64_t max_storage_sz = 0;
|
|
uint64_t remaining_sz = 0;
|
|
uint64_t max_var_sz = 0;
|
|
|
|
*max_var_szp = 0;
|
|
if (EFI_MAJOR_VERSION(RT) < 2 || is_apple_firmware_vendor()) {
|
|
dprint(L"EFI %d.%d; no RT->QueryVariableInfo(). Using 1024!\n",
|
|
EFI_MAJOR_VERSION(RT), EFI_MINOR_VERSION(RT));
|
|
max_var_sz = remaining_sz = max_storage_sz = 1024;
|
|
efi_status = EFI_SUCCESS;
|
|
} else {
|
|
dprint(L"calling RT->QueryVariableInfo() at 0x%lx\n",
|
|
RT->QueryVariableInfo);
|
|
efi_status = RT->QueryVariableInfo(attrs, &max_storage_sz,
|
|
&remaining_sz, &max_var_sz);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Could not get variable storage info: %r\n",
|
|
efi_status);
|
|
return efi_status;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* I just don't trust implementations to not be showing static data
|
|
* for max_var_sz
|
|
*/
|
|
*max_var_szp = (max_var_sz < remaining_sz) ? max_var_sz : remaining_sz;
|
|
dprint("max_var_sz:%lx remaining_sz:%lx max_storage_sz:%lx\n",
|
|
max_var_sz, remaining_sz, max_storage_sz);
|
|
return efi_status;
|
|
}
|
|
|
|
/*
|
|
* If any entries fit in < maxsz, and nothing goes wrong, create a variable
|
|
* of the given name and guid with as many esd entries as possible in it,
|
|
* and updates *esdp with what would be the next entry (even if makes *esdp
|
|
* > esl+esl->SignatureListSize), and returns whatever SetVariable()
|
|
* returns
|
|
*
|
|
* If no entries fit (i.e. sizeof(esl) + esl->SignatureSize > maxsz),
|
|
* returns EFI_BUFFER_TOO_SMALL;
|
|
*/
|
|
static EFI_STATUS
|
|
mirror_one_esl(CHAR16 *name, EFI_GUID *guid, UINT32 attrs,
|
|
EFI_SIGNATURE_LIST *esl, EFI_SIGNATURE_DATA *esd,
|
|
SIZE_T howmany)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
SIZE_T varsz = 0;
|
|
UINT8 *var;
|
|
|
|
/*
|
|
* We always assume esl->SignatureHeaderSize is 0 (and so far,
|
|
* that's true as per UEFI 2.8)
|
|
*/
|
|
dprint(L"Trying to add %lx signatures to \"%s\" of size %lx\n",
|
|
howmany, name, esl->SignatureSize);
|
|
|
|
/*
|
|
* Because of the semantics of variable_create_esl(), the first
|
|
* owner guid from the data is not part of esdsz, or the data.
|
|
*
|
|
* Compensate here.
|
|
*/
|
|
efi_status = variable_create_esl(esd, howmany,
|
|
&esl->SignatureType,
|
|
esl->SignatureSize,
|
|
&var, &varsz);
|
|
if (EFI_ERROR(efi_status) || !var || !varsz) {
|
|
LogError(L"Couldn't allocate %lu bytes for mok variable \"%s\": %r\n",
|
|
varsz, name, efi_status);
|
|
return efi_status;
|
|
}
|
|
|
|
dprint(L"new esl:\n");
|
|
dhexdumpat(var, varsz, 0);
|
|
|
|
efi_status = SetVariable(name, guid, attrs, varsz, var);
|
|
FreePool(var);
|
|
if (EFI_ERROR(efi_status)) {
|
|
LogError(L"Couldn't create mok variable \"%s\": %r\n",
|
|
name, efi_status);
|
|
return efi_status;
|
|
}
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
mirror_mok_db(CHAR16 *name, EFI_GUID *guid, UINT32 attrs,
|
|
UINT8 *FullData, SIZE_T FullDataSize, BOOLEAN only_first)
|
|
{
|
|
EFI_STATUS efi_status = EFI_SUCCESS;
|
|
SIZE_T max_var_sz;
|
|
|
|
efi_status = get_max_var_sz(attrs, &max_var_sz);
|
|
if (EFI_ERROR(efi_status) && efi_status != EFI_UNSUPPORTED) {
|
|
LogError(L"Could not get maximum variable size: %r",
|
|
efi_status);
|
|
return efi_status;
|
|
}
|
|
|
|
/* Some UEFI environment such as u-boot doesn't implement
|
|
* QueryVariableInfo() and we will only get EFI_UNSUPPORTED when
|
|
* querying the available space. In this case, we just mirror
|
|
* the variable directly. */
|
|
if (FullDataSize <= max_var_sz || efi_status == EFI_UNSUPPORTED) {
|
|
efi_status = EFI_SUCCESS;
|
|
if (only_first)
|
|
efi_status = SetVariable(name, guid, attrs,
|
|
FullDataSize, FullData);
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
CHAR16 *namen = NULL;
|
|
UINTN namelen, namesz;
|
|
|
|
namelen = StrLen(name);
|
|
namesz = namelen * 2;
|
|
if (only_first) {
|
|
namen = name;
|
|
} else {
|
|
namelen += 18;
|
|
namesz += 34;
|
|
namen = AllocateZeroPool(namesz);
|
|
if (!namen) {
|
|
LogError(L"Could not allocate %lu bytes", namesz);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
|
|
UINTN pos, i;
|
|
const SIZE_T minsz = sizeof(EFI_SIGNATURE_LIST)
|
|
+ sizeof(EFI_SIGNATURE_DATA)
|
|
+ SHA1_DIGEST_SIZE;
|
|
BOOLEAN did_one = FALSE;
|
|
|
|
/*
|
|
* Create any entries that can fit.
|
|
*/
|
|
if (!only_first) {
|
|
dprint(L"full data for \"%s\":\n", name);
|
|
dhexdumpat(FullData, FullDataSize, 0);
|
|
}
|
|
EFI_SIGNATURE_LIST *esl = NULL;
|
|
UINTN esl_end_pos = 0;
|
|
for (i = 0, pos = 0; FullDataSize - pos >= minsz && FullData; ) {
|
|
EFI_SIGNATURE_DATA *esd = NULL;
|
|
|
|
dprint(L"pos:0x%llx FullDataSize:0x%llx\n", pos, FullDataSize);
|
|
if (esl == NULL || pos >= esl_end_pos) {
|
|
UINT8 *nesl = FullData + pos;
|
|
dprint(L"esl:0x%llx->0x%llx\n", esl, nesl);
|
|
esl = (EFI_SIGNATURE_LIST *)nesl;
|
|
esl_end_pos = pos + esl->SignatureListSize;
|
|
dprint(L"pos:0x%llx->0x%llx\n", pos, pos + sizeof(*esl));
|
|
pos += sizeof(*esl);
|
|
}
|
|
esd = (EFI_SIGNATURE_DATA *)(FullData + pos);
|
|
if (pos >= FullDataSize)
|
|
break;
|
|
if (esl->SignatureListSize == 0 || esl->SignatureSize == 0)
|
|
break;
|
|
|
|
dprint(L"esl[%lu] 0x%llx = {sls=0x%lx, ss=0x%lx} esd:0x%llx\n",
|
|
i, esl, esl->SignatureListSize, esl->SignatureSize, esd);
|
|
|
|
if (!only_first) {
|
|
SPrint(namen, namelen, L"%s%lu", name, i);
|
|
namen[namelen-1] = 0;
|
|
}
|
|
|
|
/*
|
|
* In case max_var_sz is computed dynamically, refresh the
|
|
* value here.
|
|
*/
|
|
efi_status = get_max_var_sz(attrs, &max_var_sz);
|
|
if (EFI_ERROR(efi_status)) {
|
|
LogError(L"Could not get maximum variable size: %r",
|
|
efi_status);
|
|
if (!only_first) {
|
|
FreePool(namen);
|
|
}
|
|
return efi_status;
|
|
}
|
|
|
|
/* The name counts towards the size of the variable */
|
|
SIZE_T namen_sz = (StrLen(namen) + 1) * 2;
|
|
if (max_var_sz > namen_sz)
|
|
max_var_sz -= namen_sz;
|
|
else
|
|
max_var_sz = 0;
|
|
dprint(L"max_var_sz - name: %lx\n", max_var_sz);
|
|
|
|
SIZE_T howmany;
|
|
if (max_var_sz > sizeof(*esl))
|
|
howmany = MIN((max_var_sz - sizeof(*esl)) / esl->SignatureSize,
|
|
(esl_end_pos - pos) / esl->SignatureSize);
|
|
else
|
|
howmany = 0;
|
|
|
|
if (howmany == 0) {
|
|
/* No signatures from this ESL can be mirrored in to a
|
|
* single variable, so skip it.
|
|
*/
|
|
dprint(L"skipping esl, pos:0x%llx->0x%llx\n", pos, esl_end_pos);
|
|
pos = esl_end_pos;
|
|
continue;
|
|
}
|
|
|
|
UINTN adj = howmany * esl->SignatureSize;
|
|
|
|
if (!only_first && i == 0) {
|
|
dprint(L"pos:0x%llx->0x%llx\n", pos, pos + adj);
|
|
pos += adj;
|
|
i++;
|
|
continue;
|
|
|
|
}
|
|
|
|
efi_status = mirror_one_esl(namen, guid, attrs,
|
|
esl, esd, howmany);
|
|
dprint(L"esd:0x%llx adj:0x%llx\n", esd, adj);
|
|
if (EFI_ERROR(efi_status)) {
|
|
LogError(L"Could not mirror mok variable \"%s\": %r\n",
|
|
namen, efi_status);
|
|
break;
|
|
}
|
|
|
|
dprint(L"pos:0x%llx->0x%llx\n", pos, pos + adj);
|
|
pos += adj;
|
|
did_one = TRUE;
|
|
if (only_first)
|
|
break;
|
|
i++;
|
|
}
|
|
if (namen && namen != name) {
|
|
FreePool(namen);
|
|
}
|
|
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to set %s: %r\n", name, efi_status);
|
|
} else if (only_first && !did_one) {
|
|
/*
|
|
* In this case we're going to try to create a
|
|
* dummy variable so that there's one there. It
|
|
* may or may not work, because on some firmware
|
|
* builds when the SetVariable call above fails it
|
|
* does actually set the variable(!), so aside from
|
|
* not using the allocation if it doesn't work, we
|
|
* don't care about failures here.
|
|
*/
|
|
UINT8 *var;
|
|
UINTN varsz;
|
|
|
|
efi_status = variable_create_esl_with_one_signature(
|
|
null_sha256, sizeof(null_sha256),
|
|
&EFI_CERT_SHA256_GUID, &SHIM_LOCK_GUID,
|
|
&var, &varsz);
|
|
/*
|
|
* from here we don't really care if it works or
|
|
* doesn't.
|
|
*/
|
|
if (!EFI_ERROR(efi_status) && var && varsz) {
|
|
efi_status = SetVariable(name, guid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS
|
|
| EFI_VARIABLE_RUNTIME_ACCESS,
|
|
varsz, var);
|
|
FreePool(var);
|
|
}
|
|
}
|
|
return efi_status;
|
|
}
|
|
|
|
|
|
static EFI_STATUS NONNULL(1)
|
|
mirror_one_mok_variable(struct mok_state_variable *v,
|
|
BOOLEAN only_first)
|
|
{
|
|
EFI_STATUS efi_status = EFI_SUCCESS;
|
|
uint8_t *FullData = NULL;
|
|
size_t FullDataSize = 0;
|
|
bool allocated_full_data = false;
|
|
vendor_addend_category_t addend_category = VENDOR_ADDEND_NONE;
|
|
uint8_t *p = NULL;
|
|
uint32_t attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
|
EFI_VARIABLE_RUNTIME_ACCESS;
|
|
BOOLEAN measure = v->flags & MOK_VARIABLE_MEASURE;
|
|
BOOLEAN log = v->flags & MOK_VARIABLE_LOG;
|
|
size_t build_cert_esl_sz = 0, addend_esl_sz = 0;
|
|
bool reuse = FALSE;
|
|
|
|
if (v->categorize_addend)
|
|
addend_category = v->categorize_addend(v);
|
|
|
|
/*
|
|
* if it is, there's more data
|
|
*/
|
|
if (v->flags & MOK_MIRROR_KEYDB) {
|
|
|
|
/*
|
|
* We're mirroring (into) an efi security database, aka an
|
|
* array of EFI_SIGNATURE_LIST. Its layout goes like:
|
|
*
|
|
* existing_variable_data
|
|
* existing_variable_data_size
|
|
* if flags & MOK_MIRROR_KEYDB
|
|
* if build_cert
|
|
* build_cert_esl
|
|
* build_cert_header (always sz=0)
|
|
* build_cert_esd[0] { owner, data }
|
|
* if addend==vendor_db
|
|
* for n=[1..N]
|
|
* vendor_db_esl_n
|
|
* vendor_db_header_n (always sz=0)
|
|
* vendor_db_esd_n[m] {{ owner, data }, ... }
|
|
* elif addend==vendor_cert
|
|
* vendor_cert_esl
|
|
* vendor_cert_header (always sz=0)
|
|
* vendor_cert_esd[1] { owner, data }
|
|
*
|
|
* first we determine the size of the variable, then alloc
|
|
* and add the data.
|
|
*/
|
|
|
|
/*
|
|
* *first* vendor_db or vendor_cert
|
|
*/
|
|
switch (addend_category) {
|
|
case VENDOR_ADDEND_DB:
|
|
/*
|
|
* if it's an ESL already, we use it wholesale
|
|
*/
|
|
FullDataSize += *v->addend_size;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx\n",
|
|
FullDataSize, FullData);
|
|
break;
|
|
case VENDOR_ADDEND_X509:
|
|
efi_status = fill_esl_with_one_signature(*v->addend,
|
|
*v->addend_size,
|
|
&EFI_CERT_TYPE_X509_GUID,
|
|
&SHIM_LOCK_GUID,
|
|
NULL,
|
|
&addend_esl_sz);
|
|
if (efi_status != EFI_BUFFER_TOO_SMALL) {
|
|
perror(L"Could not add built-in cert to %s: %r\n",
|
|
v->name, efi_status);
|
|
goto err;
|
|
return efi_status;
|
|
}
|
|
FullDataSize += addend_esl_sz;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx\n",
|
|
FullDataSize, FullData);
|
|
break;
|
|
default:
|
|
case VENDOR_ADDEND_NONE:
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx\n",
|
|
FullDataSize, FullData);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* then the build cert if it's there
|
|
*/
|
|
if (should_mirror_build_cert(v)) {
|
|
efi_status = fill_esl_with_one_signature(*v->build_cert,
|
|
*v->build_cert_size,
|
|
&EFI_CERT_TYPE_X509_GUID,
|
|
&SHIM_LOCK_GUID,
|
|
NULL, &build_cert_esl_sz);
|
|
if (efi_status != EFI_BUFFER_TOO_SMALL) {
|
|
perror(L"Could not add built-in cert to %s: %r\n",
|
|
v->name, efi_status);
|
|
return efi_status;
|
|
}
|
|
FullDataSize += build_cert_esl_sz;
|
|
dprint(L"FullDataSize:0x%lx FullData:0x%llx\n",
|
|
FullDataSize, FullData);
|
|
}
|
|
if (v->user_cert_size)
|
|
FullDataSize += *v->user_cert_size;
|
|
}
|
|
|
|
/*
|
|
* we're always mirroring the original data, whether this is an efi
|
|
* security database or not
|
|
*/
|
|
dprint(L"v->name:\"%s\" v->rtname:\"%s\"\n", v->name, v->rtname);
|
|
dprint(L"v->data_size:%lu v->data:0x%llx\n", v->data_size, v->data);
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx\n", FullDataSize, FullData);
|
|
if (v->data_size) {
|
|
FullDataSize += v->data_size;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx\n",
|
|
FullDataSize, FullData);
|
|
}
|
|
if (v->data_size == FullDataSize)
|
|
reuse = TRUE;
|
|
|
|
/*
|
|
* Now we have the full size
|
|
*/
|
|
if (FullDataSize) {
|
|
/*
|
|
* allocate the buffer, or use the old one if it's just the
|
|
* existing data.
|
|
*/
|
|
if (FullDataSize == v->data_size) {
|
|
FullData = v->data;
|
|
FullDataSize = v->data_size;
|
|
p = FullData + FullDataSize;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
v->data = NULL;
|
|
v->data_size = 0;
|
|
} else {
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx allocating FullData\n",
|
|
FullDataSize, FullData);
|
|
/*
|
|
* make sure we've got some zeroes at the end, just
|
|
* in case.
|
|
*/
|
|
UINTN new, allocsz;
|
|
|
|
allocsz = FullDataSize + sizeof(EFI_SIGNATURE_LIST);
|
|
new = ALIGN_VALUE(allocsz, 4096);
|
|
allocsz = new == allocsz ? new + 4096 : new;
|
|
FullData = AllocateZeroPool(allocsz);
|
|
if (!FullData) {
|
|
perror(L"Failed to allocate %lu bytes for %s\n",
|
|
FullDataSize, v->name);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
allocated_full_data = true;
|
|
p = FullData;
|
|
}
|
|
}
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
|
|
/*
|
|
* Now fill it.
|
|
*/
|
|
if (v->flags & MOK_MIRROR_KEYDB) {
|
|
/*
|
|
* first vendor_cert or vendor_db
|
|
*/
|
|
switch (addend_category) {
|
|
case VENDOR_ADDEND_DB:
|
|
CopyMem(p, *v->addend, *v->addend_size);
|
|
p += *v->addend_size;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
break;
|
|
case VENDOR_ADDEND_X509:
|
|
efi_status = fill_esl_with_one_signature(*v->addend,
|
|
*v->addend_size,
|
|
&EFI_CERT_TYPE_X509_GUID,
|
|
&SHIM_LOCK_GUID,
|
|
p, &addend_esl_sz);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Could not add built-in cert to %s: %r\n",
|
|
v->name, efi_status);
|
|
goto err;
|
|
}
|
|
p += addend_esl_sz;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
break;
|
|
default:
|
|
case VENDOR_ADDEND_NONE:
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* then is the build cert
|
|
*/
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
if (should_mirror_build_cert(v)) {
|
|
efi_status = fill_esl_with_one_signature(*v->build_cert,
|
|
*v->build_cert_size,
|
|
&EFI_CERT_TYPE_X509_GUID,
|
|
&SHIM_LOCK_GUID,
|
|
p, &build_cert_esl_sz);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Could not add built-in cert to %s: %r\n",
|
|
v->name, efi_status);
|
|
goto err;
|
|
}
|
|
p += build_cert_esl_sz;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
}
|
|
if (v->user_cert_size) {
|
|
CopyMem(p, *v->user_cert, *v->user_cert_size);
|
|
p += *v->user_cert_size;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* last bit is existing data, unless it's the only thing,
|
|
* in which case it's already there.
|
|
*/
|
|
if (!reuse) {
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
if (v->data && v->data_size) {
|
|
CopyMem(p, v->data, v->data_size);
|
|
p += v->data_size;
|
|
}
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
}
|
|
|
|
/*
|
|
* We always want to create our key databases, so in this case we
|
|
* need a dummy entry
|
|
*/
|
|
if ((v->flags & MOK_MIRROR_KEYDB) && FullDataSize == 0) {
|
|
efi_status = variable_create_esl_with_one_signature(
|
|
null_sha256, sizeof(null_sha256),
|
|
&EFI_CERT_SHA256_GUID, &SHIM_LOCK_GUID,
|
|
&FullData, &FullDataSize);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to allocate %lu bytes for %s\n",
|
|
FullDataSize, v->name);
|
|
goto err;
|
|
}
|
|
p = FullData + FullDataSize;
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
}
|
|
|
|
dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n",
|
|
FullDataSize, FullData, p, p-(uintptr_t)FullData);
|
|
if (FullDataSize && v->flags & MOK_MIRROR_KEYDB &&
|
|
!(v->flags & MOK_VARIABLE_CONFIG_ONLY)) {
|
|
dprint(L"calling mirror_mok_db(\"%s\", datasz=%lu)\n",
|
|
v->rtname, FullDataSize);
|
|
efi_status = mirror_mok_db(v->rtname, v->guid,
|
|
attrs, FullData, FullDataSize,
|
|
only_first);
|
|
dprint(L"mirror_mok_db(\"%s\", datasz=%lu) returned %r\n",
|
|
v->rtname, FullDataSize, efi_status);
|
|
} else if (FullDataSize && only_first &&
|
|
!(v->flags & MOK_VARIABLE_CONFIG_ONLY)) {
|
|
efi_status = SetVariable(v->rtname, v->guid, attrs,
|
|
FullDataSize, FullData);
|
|
}
|
|
if (FullDataSize && only_first) {
|
|
if (measure) {
|
|
/*
|
|
* Measure this into PCR 7 in the Microsoft format
|
|
*/
|
|
efi_status = tpm_measure_variable(v->name, *v->guid,
|
|
FullDataSize, FullData);
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"tpm_measure_variable(\"%s\",%lu,0x%llx)->%r\n",
|
|
v->name, FullDataSize, FullData, efi_status);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (log) {
|
|
/*
|
|
* Log this variable into whichever PCR the table
|
|
* says.
|
|
*/
|
|
EFI_PHYSICAL_ADDRESS datap =
|
|
(EFI_PHYSICAL_ADDRESS)(UINTN)FullData,
|
|
efi_status = tpm_log_event(datap, FullDataSize,
|
|
v->pcr, (CHAR8 *)v->name8);
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"tpm_log_event(0x%llx, %lu, %lu, \"%s\")->%r\n",
|
|
FullData, FullDataSize, v->pcr, v->name,
|
|
efi_status);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
}
|
|
if (v->data && v->data_size && v->data != FullData) {
|
|
FreePool(v->data);
|
|
v->data = NULL;
|
|
v->data_size = 0;
|
|
}
|
|
v->data = FullData;
|
|
v->data_size = FullDataSize;
|
|
dprint(L"returning %r\n", efi_status);
|
|
return efi_status;
|
|
err:
|
|
if (FullData && allocated_full_data)
|
|
FreePool(FullData);
|
|
return efi_status;
|
|
}
|
|
|
|
/*
|
|
* Mirror a variable if it has an rtname, and preserve any
|
|
* EFI_SECURITY_VIOLATION status at the same time.
|
|
*/
|
|
static EFI_STATUS NONNULL(1)
|
|
maybe_mirror_one_mok_variable(struct mok_state_variable *v,
|
|
EFI_STATUS ret, BOOLEAN only_first)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
BOOLEAN present = FALSE;
|
|
|
|
if (v->rtname) {
|
|
if (only_first && (v->flags & MOK_MIRROR_DELETE_FIRST)) {
|
|
dprint(L"deleting \"%s\"\n", v->rtname);
|
|
efi_status = LibDeleteVariable(v->rtname, v->guid);
|
|
dprint(L"LibDeleteVariable(\"%s\",...) => %r\n", v->rtname, efi_status);
|
|
}
|
|
|
|
efi_status = mirror_one_mok_variable(v, only_first);
|
|
if (EFI_ERROR(efi_status)) {
|
|
if (ret != EFI_SECURITY_VIOLATION)
|
|
ret = efi_status;
|
|
perror(L"Could not create %s: %r\n", v->rtname,
|
|
efi_status);
|
|
}
|
|
}
|
|
|
|
present = (v->data && v->data_size) ? TRUE : FALSE;
|
|
if (!present)
|
|
return ret;
|
|
|
|
if (v->data_size == sizeof(UINT8) && v->state) {
|
|
*v->state = v->data[0];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
EFI_STATUS import_one_mok_state(struct mok_state_variable *v,
|
|
BOOLEAN only_first)
|
|
{
|
|
EFI_STATUS ret = EFI_SUCCESS;
|
|
EFI_STATUS efi_status;
|
|
|
|
UINT32 attrs = 0;
|
|
BOOLEAN delete = FALSE;
|
|
|
|
dprint(L"importing mok state for \"%s\"\n", v->name);
|
|
|
|
if (!v->data && !v->data_size &&
|
|
!(v->flags & MOK_VARIABLE_CONFIG_ONLY)) {
|
|
efi_status = get_variable_attr(v->name,
|
|
&v->data, &v->data_size,
|
|
*v->guid, &attrs);
|
|
if (efi_status == EFI_NOT_FOUND &&
|
|
v->flags & MOK_VARIABLE_INVERSE) {
|
|
v->data = AllocateZeroPool(4);
|
|
if (!v->data) {
|
|
perror(L"Out of memory\n");
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
v->data[0] = 0x01;
|
|
v->data_size = 1;
|
|
} else if (efi_status == EFI_NOT_FOUND) {
|
|
v->data = NULL;
|
|
v->data_size = 0;
|
|
} else if (EFI_ERROR(efi_status)) {
|
|
perror(L"Could not verify %s: %r\n", v->name,
|
|
efi_status);
|
|
delete = TRUE;
|
|
} else {
|
|
if (!(attrs & v->yes_attr)) {
|
|
perror(L"Variable %s is missing attributes:\n",
|
|
v->name);
|
|
perror(L" 0x%08x should have 0x%08x set.\n",
|
|
attrs, v->yes_attr);
|
|
delete = TRUE;
|
|
}
|
|
if (attrs & v->no_attr) {
|
|
perror(L"Variable %s has incorrect attribute:\n",
|
|
v->name);
|
|
perror(L" 0x%08x should not have 0x%08x set.\n",
|
|
attrs, v->no_attr);
|
|
delete = TRUE;
|
|
}
|
|
if (v->flags & MOK_VARIABLE_INVERSE) {
|
|
FreePool(v->data);
|
|
v->data = NULL;
|
|
v->data_size = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (v->format) {
|
|
v->data_size = v->format(NULL, 0, v);
|
|
if (v->data_size > 0) {
|
|
v->data = AllocatePool(v->data_size);
|
|
if (!v->data) {
|
|
perror(L"Could not allocate %lu bytes for %s\n",
|
|
v->data_size, v->name);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
v->format(v->data, v->data_size, v);
|
|
}
|
|
|
|
if (!v->data && !v->data_size &&
|
|
(v->flags & MOK_VARIABLE_CONFIG_ONLY) &&
|
|
!v->format) {
|
|
efi_status = get_variable_attr(v->name,
|
|
&v->data, &v->data_size,
|
|
*v->guid, &attrs);
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"Couldn't get variable \"%s\" for mirroring: %r\n",
|
|
v->name, efi_status);
|
|
if (efi_status != EFI_NOT_FOUND)
|
|
return efi_status;
|
|
v->data = NULL;
|
|
v->data_size = 0;
|
|
}
|
|
}
|
|
|
|
if (delete == TRUE) {
|
|
perror(L"Deleting bad variable %s\n", v->name);
|
|
efi_status = LibDeleteVariable(v->name, v->guid);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to erase %s\n", v->name);
|
|
ret = EFI_SECURITY_VIOLATION;
|
|
}
|
|
FreePool(v->data);
|
|
v->data = NULL;
|
|
v->data_size = 0;
|
|
}
|
|
|
|
dprint(L"maybe mirroring \"%s\". original data:\n", v->name);
|
|
if (v->data && v->data_size) {
|
|
dhexdumpat(v->data, v->data_size, 0);
|
|
}
|
|
|
|
ret = maybe_mirror_one_mok_variable(v, ret, only_first);
|
|
dprint(L"returning %r\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Verify our non-volatile MoK state. This checks the variables above
|
|
* accessable and have valid attributes. If they don't, it removes
|
|
* them. If any of them can't be removed, our ability to do this is
|
|
* comprimized, so return EFI_SECURITY_VIOLATION.
|
|
*
|
|
* Any variable that isn't deleted and has ->measure == TRUE is then
|
|
* measured into the tpm.
|
|
*
|
|
* Any variable with a ->rtname element is then mirrored to a
|
|
* runtime-accessable version. The new ones won't be marked NV, so the OS
|
|
* can't modify them.
|
|
*/
|
|
EFI_STATUS import_mok_state(EFI_HANDLE image_handle)
|
|
{
|
|
UINTN i;
|
|
EFI_STATUS ret = EFI_SUCCESS;
|
|
EFI_STATUS efi_status;
|
|
|
|
user_insecure_mode = 0;
|
|
ignore_db = 0;
|
|
trust_mok_list = 0;
|
|
|
|
UINT64 config_sz = 0;
|
|
UINT8 *config_table = NULL;
|
|
size_t npages = 0;
|
|
struct mok_variable_config_entry config_template;
|
|
|
|
dprint(L"importing minimal mok state variables\n");
|
|
for (i = 0; mok_state_variables[i].name != NULL; i++) {
|
|
struct mok_state_variable *v = &mok_state_variables[i];
|
|
|
|
efi_status = import_one_mok_state(v, TRUE);
|
|
if (EFI_ERROR(efi_status)) {
|
|
dprint(L"import_one_mok_state(ih, \"%s\", TRUE): %r\n",
|
|
v->rtname);
|
|
/*
|
|
* don't clobber EFI_SECURITY_VIOLATION from some
|
|
* other variable in the list.
|
|
*/
|
|
if (ret != EFI_SECURITY_VIOLATION)
|
|
ret = efi_status;
|
|
}
|
|
|
|
if (v->data && v->data_size) {
|
|
config_sz += v->data_size;
|
|
config_sz += sizeof(config_template);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Alright, so we're going to copy these to a config table. The
|
|
* table is a packed array of N+1 struct mok_variable_config_entry
|
|
* items, with the last item having all zero's in name and
|
|
* data_size.
|
|
*/
|
|
if (config_sz) {
|
|
config_sz += sizeof(config_template);
|
|
npages = ALIGN_VALUE(config_sz, PAGE_SIZE) >> EFI_PAGE_SHIFT;
|
|
config_table = NULL;
|
|
efi_status = BS->AllocatePages(
|
|
AllocateAnyPages, EfiRuntimeServicesData, npages,
|
|
(EFI_PHYSICAL_ADDRESS *)&config_table);
|
|
if (EFI_ERROR(efi_status) || !config_table) {
|
|
console_print(L"Allocating %lu pages for mok config table failed: %r\n",
|
|
npages, efi_status);
|
|
config_table = NULL;
|
|
} else {
|
|
ZeroMem(config_table, npages << EFI_PAGE_SHIFT);
|
|
mok_config_table = (EFI_PHYSICAL_ADDRESS)(uintptr_t)config_table;
|
|
mok_config_table_pages = npages;
|
|
}
|
|
}
|
|
|
|
UINT8 *p = (UINT8 *)config_table;
|
|
for (i = 0; p && mok_state_variables[i].name != NULL; i++) {
|
|
struct mok_state_variable *v = &mok_state_variables[i];
|
|
|
|
ZeroMem(&config_template, sizeof(config_template));
|
|
strncpy(config_template.name, (CHAR8 *)v->rtname8, 255);
|
|
config_template.name[255] = '\0';
|
|
|
|
config_template.data_size = v->data_size;
|
|
|
|
if (v->data && v->data_size) {
|
|
CopyMem(p, &config_template, sizeof(config_template));
|
|
p += sizeof(config_template);
|
|
CopyMem(p, v->data, v->data_size);
|
|
p += v->data_size;
|
|
}
|
|
}
|
|
if (p) {
|
|
ZeroMem(&config_template, sizeof(config_template));
|
|
CopyMem(p, &config_template, sizeof(config_template));
|
|
|
|
efi_status = BS->InstallConfigurationTable(&MOK_VARIABLE_STORE,
|
|
config_table);
|
|
if (EFI_ERROR(efi_status)) {
|
|
console_print(L"Couldn't install MoK configuration table\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is really just to make it easy for userland.
|
|
*/
|
|
dprint(L"importing full mok state variables\n");
|
|
for (i = 0; mok_state_variables[i].name != NULL; i++) {
|
|
struct mok_state_variable *v = &mok_state_variables[i];
|
|
|
|
import_one_mok_state(v, FALSE);
|
|
}
|
|
|
|
/*
|
|
* Enter MokManager if necessary. Any actual *changes* here will
|
|
* cause MokManager to demand a machine reboot, so this is safe to
|
|
* have after the entire loop.
|
|
*/
|
|
dprint(L"checking mok request\n");
|
|
efi_status = check_mok_request(image_handle);
|
|
dprint(L"mok returned %r\n", efi_status);
|
|
if (EFI_ERROR(efi_status)) {
|
|
/*
|
|
* don't clobber EFI_SECURITY_VIOLATION
|
|
*/
|
|
if (ret != EFI_SECURITY_VIOLATION)
|
|
ret = efi_status;
|
|
return ret;
|
|
}
|
|
|
|
dprint(L"returning %r\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
// vim:fenc=utf-8:tw=75:noet
|