efi-boot-shim/mok.c
Peter Jones 63f7943dbe mirror_one_mok_variable(): round allocation up to a full page
The code currently computes the size of the MoK variable in ram and
rounds up to a full page, but then actually allocates the exact size,
rather than the rounded up version.  This should be completely safe, but
the intent was to round up to at least the page size boundary, and to
always guarantee rounding up /some/, to ensure extra 0-bytes at the end
of the buffer.

Signed-off-by: Peter Jones <pjones@redhat.com>
2020-08-04 12:49:30 -04:00

1018 lines
27 KiB
C

/*
* mok.c
* Copyright 2017 Peter Jones <pjones@redhat.com>
*/
#include "shim.h"
#include <stdint.h>
#include "hexdump.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 = gRT->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_ = gRT->SetVariable(name, guid, attrs, varsz, var); \
dprint_(L"%a:%d:%a() SetVariable(\"%s\", ... varsz=0x%llx) = %r\n",\
__FILE__, __LINE__, __func__, \
name, varsz, efi_status_); \
efi_status_; \
})
/*
* 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")) {
efi_status = start_image(image_handle, MOK_MANAGER);
if (EFI_ERROR(efi_status)) {
perror(L"Failed to start MokManager: %r\n", efi_status);
return efi_status;
}
}
return EFI_SUCCESS;
}
typedef enum {
VENDOR_ADDEND_DB,
VENDOR_ADDEND_X509,
VENDOR_ADDEND_NONE,
} vendor_addend_category_t;
struct mok_state_variable;
typedef vendor_addend_category_t (vendor_addend_categorizer_t)(struct mok_state_variable *);
/*
* MoK variables that need to have their storage validated.
*
* The order here is important, since this is where we measure for the
* tpm as well.
*/
struct mok_state_variable {
CHAR16 *name;
char *name8;
CHAR16 *rtname;
char *rtname8;
EFI_GUID *guid;
UINT8 *data;
UINTN data_size;
/*
* These are indirect pointers just to make initialization saner...
*/
vendor_addend_categorizer_t *categorize_addend;
UINT8 **addend;
UINT32 *addend_size;
UINT8 **build_cert;
UINT32 *build_cert_size;
UINT32 yes_attr;
UINT32 no_attr;
UINT32 flags;
UINTN pcr;
UINT8 *state;
};
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;
}
#define MOK_MIRROR_KEYDB 0x01
#define MOK_MIRROR_DELETE_FIRST 0x02
#define MOK_VARIABLE_MEASURE 0x04
#define MOK_VARIABLE_LOG 0x08
struct mok_state_variable mok_state_variables[] = {
{.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,
#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,
},
{ NULL, }
};
#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;
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;
efi_status = gRT->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,
UINTN *newsz, SIZE_T maxsz)
{
EFI_STATUS efi_status;
SIZE_T howmany, varsz = 0, esdsz;
UINT8 *var, *data;
howmany = min((maxsz - sizeof(*esl)) / esl->SignatureSize,
(esl->SignatureListSize - sizeof(*esl)) / esl->SignatureSize);
if (howmany < 1) {
return EFI_BUFFER_TOO_SMALL;
}
/*
* We always assume esl->SignatureHeaderSize is 0 (and so far,
* that's true as per UEFI 2.8)
*/
esdsz = howmany * esl->SignatureSize;
data = (UINT8 *)esd;
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(data + sizeof(EFI_GUID),
esdsz - sizeof(EFI_GUID),
&esl->SignatureType,
&esd->SignatureOwner,
&var, &varsz);
if (EFI_ERROR(efi_status) || !var || !varsz) {
LogError(L"Couldn't allocate %lu bytes for mok variable \"%s\": %r\n",
varsz, var, 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",
varsz, var, efi_status);
return efi_status;
}
*newsz = esdsz;
return efi_status;
}
static EFI_STATUS
mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs,
UINT8 *FullData, SIZE_T FullDataSize, BOOLEAN only_first)
{
EFI_STATUS efi_status = EFI_SUCCESS;
SIZE_T max_var_sz;
if (only_first) {
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);
return efi_status;
}
if (FullDataSize <= max_var_sz) {
efi_status = SetVariable(name, guid, attrs,
FullDataSize, FullData);
return efi_status;
}
}
CHAR16 *namen;
CHAR8 *namen8;
UINTN namelen, namesz;
namelen = StrLen(name);
namesz = namelen * 2;
if (only_first) {
namen = name;
namen8 = name8;
} else {
namelen += 18;
namesz += 34;
namen = AllocateZeroPool(namesz);
if (!namen) {
LogError(L"Could not allocate %lu bytes", namesz);
return EFI_OUT_OF_RESOURCES;
}
namen8 = AllocateZeroPool(namelen);
if (!namen8) {
FreePool(namen);
LogError(L"Could not allocate %lu bytes", namelen);
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;
/* uggggh */
UINTN j;
for (j = 0; j < namelen; j++)
namen8[j] = (CHAR8)(namen[j] & 0xff);
namen8[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);
FreePool(namen8);
}
return efi_status;
}
SIZE_T howmany;
UINTN adj = 0;
howmany = min((max_var_sz - sizeof(*esl)) / esl->SignatureSize,
(esl->SignatureListSize - sizeof(*esl)) / esl->SignatureSize);
if (!only_first && i == 0 && howmany >= 1) {
adj = howmany * esl->SignatureSize;
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, &adj, max_var_sz);
dprint(L"esd:0x%llx adj:0x%llx\n", esd, adj);
if (EFI_ERROR(efi_status) && efi_status != EFI_BUFFER_TOO_SMALL) {
LogError(L"Could not mirror mok variable \"%s\": %r\n",
namen, efi_status);
break;
}
if (!EFI_ERROR(efi_status)) {
did_one = TRUE;
if (only_first)
break;
dprint(L"pos:0x%llx->0x%llx\n", pos, pos + adj);
pos += adj;
i++;
}
}
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(
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) {
SetVariable(name, guid,
EFI_VARIABLE_BOOTSERVICE_ACCESS
| EFI_VARIABLE_RUNTIME_ACCESS,
varsz, var);
FreePool(var);
}
efi_status = EFI_INVALID_PARAMETER;
} else if (EFI_ERROR(efi_status)) {
perror(L"Failed to set %s: %r\n", name, efi_status);
}
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;
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(*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);
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(*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);
}
}
/*
* 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;
}
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(*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);
return efi_status;
}
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(*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);
return efi_status;
}
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);
}
}
/*
* 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(
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);
return efi_status;
}
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) {
dprint(L"calling mirror_mok_db(\"%s\", datasz=%lu)\n",
v->rtname, FullDataSize);
efi_status = mirror_mok_db(v->rtname, (CHAR8 *)v->rtname8, 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) {
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);
return efi_status;
}
}
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);
return efi_status;
}
}
}
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;
}
/*
* 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;
}
struct mok_variable_config_entry {
CHAR8 name[256];
UINT64 data_size;
UINT8 data[];
};
EFI_STATUS import_one_mok_state(struct mok_state_variable *v,
BOOLEAN only_first)
{
EFI_STATUS ret = EFI_SUCCESS;
EFI_STATUS efi_status;
user_insecure_mode = 0;
ignore_db = 0;
UINT32 attrs = 0;
BOOLEAN delete = FALSE;
dprint(L"importing mok state for \"%s\"\n", v->name);
efi_status = get_variable_attr(v->name,
&v->data, &v->data_size,
*v->guid, &attrs);
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 (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);
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;
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 = gBS->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);
}
}
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));
strncpya(config_template.name, (CHAR8 *)v->rtname8, 255);
config_template.name[255] = '\0';
config_template.data_size = 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 = gBS->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