From 8c408cbc39878f9bf9ebcc9920a245c33a2defd0 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Mon, 3 Feb 2020 13:25:40 -0500 Subject: [PATCH 85/86] Add security types/guids and signature database iterators Signed-off-by: Peter Jones --- src/Makefile | 2 +- src/include/efivar/efisec-secdb.h | 61 ++++++ src/include/efivar/efisec.h | 1 + src/libefisec.map.in | 4 + src/secdb.c | 329 ++++++++++++++++++++++++++++++ 5 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 src/include/efivar/efisec-secdb.h create mode 100644 src/secdb.c diff --git a/src/Makefile b/src/Makefile index 883e058facf..a73f8f34ce9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -13,7 +13,7 @@ PCTARGETS=efivar.pc efiboot.pc efisec.pc TARGETS=$(LIBTARGETS) $(BINTARGETS) $(PCTARGETS) STATICTARGETS=$(STATICLIBTARGETS) $(STATICBINTARGETS) -LIBEFISEC_SOURCES = sec.c +LIBEFISEC_SOURCES = sec.c secdb.c LIBEFISEC_OBJECTS = $(patsubst %.c,%.o,$(LIBEFISEC_SOURCES)) LIBEFIBOOT_SOURCES = crc32.c creator.c disk.c gpt.c loadopt.c path-helpers.c \ linux.c $(sort $(wildcard linux-*.c)) diff --git a/src/include/efivar/efisec-secdb.h b/src/include/efivar/efisec-secdb.h new file mode 100644 index 00000000000..0b7103a38d7 --- /dev/null +++ b/src/include/efivar/efisec-secdb.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright 2014-2020 Red Hat, Inc. + * Copyright 2014-2020 Peter M. Jones + * + * Author(s): Peter Jones + */ +#ifndef EFISEC_SECDB_H_ +#define EFISEC_SECDB_H_ 1 + +#include +#include + +typedef struct efi_secdb_iter efi_secdb_iter; + +/* + * efi_secdb_iter_new - create a new iterator over a efi security database + * iter: pointer to a NULL efi_secdb_iter pointer. + * buf: security database from the file + * len: size of the file + * + * returns 0 on success, negative on error, sets errno. + */ +extern int efi_secdb_iter_new(efi_secdb_iter **iter, uint8_t *buf, size_t len) + __attribute__((__nonnull__(1, 2))); + +/* + * efi_secdb_iter_end - destroy the iterator created by efi_secdb_iter_new() + * iter: the iterator being destroyed + * + * returns 0 on success, negative on error, sets errno. + */ +extern int efi_secdb_iter_end(efi_secdb_iter *iter) + __attribute__((__nonnull__(1))); + +/* + * efi_secdb_iter_next - get the next item in the list + * iter: the iterator + * type: the type of the entry + * owner: the owner of the entry + * data: the identifying data + * len: the size of the data + * + * returns negative and sets errno on error, + * 0 if there weren't any entries (type/owner/data/len are not populated) + * 1 if an entry was returned. + */ +extern int efi_secdb_iter_next(efi_secdb_iter *iter, efi_guid_t *type, + efi_guid_t *owner, uint8_t **data, size_t *len) + __attribute__((__nonnull__(1, 2, 3, 4, 5))); + +/* + * efi_secdb_iter_get_line - tell how many entries have been returned + * iter: the iterator + * + * return value: -1 on error, with errno set, >=0 in all other cases + */ +extern int efi_secdb_iter_get_line(efi_secdb_iter *iter) + __attribute__((__nonnull__(1))); + +#endif /* EFISEC_SECDB_H_ */ diff --git a/src/include/efivar/efisec.h b/src/include/efivar/efisec.h index f62bcedbf6f..2072e5c9149 100644 --- a/src/include/efivar/efisec.h +++ b/src/include/efivar/efisec.h @@ -10,6 +10,7 @@ #include #include +#include extern uint32_t efi_get_libefisec_version(void) __attribute__((__visibility__("default"))); diff --git a/src/libefisec.map.in b/src/libefisec.map.in index 2e732cf1d9b..50ae27df44a 100644 --- a/src/libefisec.map.in +++ b/src/libefisec.map.in @@ -4,4 +4,8 @@ libefisec.so.0 { LIBEFISEC_1.38 { global: efi_get_libefisec_version; + efi_secdb_iter_new; + efi_secdb_iter_end; + efi_secdb_iter_next; + efi_secdb_iter_get_line; } libefisec.so.0; diff --git a/src/secdb.c b/src/secdb.c new file mode 100644 index 00000000000..e8ea0180cfd --- /dev/null +++ b/src/secdb.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright 2014-2020 Red Hat, Inc. + * Copyright 2014-2020 Peter M. Jones + * + * Author(s): Peter Jones + */ + +#include "fix_coverity.h" + +#include +#include +#include + +#include "efisec.h" + +typedef struct efi_secdb_list_iter efi_secdb_list_iter; +extern int efi_secdb_list_iter_new(efi_secdb_list_iter **iter, uint8_t *buf, size_t len); +extern int efi_secdb_list_iter_end(efi_secdb_list_iter *iter); +extern int efi_secdb_list_iter_next(efi_secdb_list_iter *iter, efi_guid_t *type, + efi_signature_data_t **data, size_t *len); +extern int efi_secdb_list_list_size(efi_secdb_list_iter *iter, size_t *sls); +extern int efi_secdb_list_header_size(efi_secdb_list_iter *iter, size_t *slh); +extern int efi_secdb_list_sig_size(efi_secdb_list_iter *iter, size_t *ss); +extern int efi_secdb_list_get_type(efi_secdb_list_iter *iter, efi_guid_t *type); + +struct efi_secdb_iter { + efi_secdb_list_iter *iter; + int line; + + efi_signature_data_t *esd; + size_t len; + + size_t nmemb; + unsigned int i; +}; + +int NONNULL(1, 2) PUBLIC +efi_secdb_iter_new(efi_secdb_iter **iter, uint8_t *buf, size_t len) +{ + int rc; + + if (len < sizeof (efi_signature_list_t) + sizeof (efi_signature_data_t)) { + errno = EINVAL; + return -1; + } + + *iter = calloc(1, sizeof (efi_secdb_iter)); + if (!*iter) + return -1; + + rc = efi_secdb_list_iter_new(&(*iter)->iter, buf, len); + if (rc < 0) { + int error = errno; + free(*iter); + errno = error; + return -1; + } + + (*iter)->i = -1; + + return 0; +} + +int NONNULL(1) PUBLIC +efi_secdb_iter_end(efi_secdb_iter *iter) +{ + if (!iter) { + errno = EINVAL; + return -1; + } + if (iter->iter) + efi_secdb_list_iter_end(iter->iter); + free(iter); + return 0; +} + +int NONNULL(1, 2, 3, 4, 5) PUBLIC +efi_secdb_iter_next(efi_secdb_iter *iter, efi_guid_t *type, + efi_guid_t *owner, uint8_t **data, size_t *len) +{ + int rc; + size_t ss; + + if (!iter) + return -EINVAL; + + if (iter->iter == NULL) + return -EINVAL; + + iter->line += 1; + + iter->i += 1; + if (iter->i == iter->nmemb) { + debug("Getting next efi_signature_data_t\n"); + iter->i = 0; + rc = efi_secdb_list_iter_next(iter->iter, type, &iter->esd, &iter->len); + if (rc < 1) + return rc; + + if (!efi_guid_cmp(type, &efi_guid_x509_cert)) { + int32_t asn1size; + + asn1size = get_asn1_seq_size(iter->esd->signature_data, + iter->len - sizeof (iter->esd->signature_owner)); + + if (asn1size < 0) { + debug("iterator data claims to be an X.509 Cert but is not valid ASN.1 DER"); + } else if ((uint32_t)asn1size != iter->len - + sizeof (iter->esd->signature_owner)) { + debug("X.509 Cert ASN.1 size does not match signature_List Size (%d vs %zu)", + asn1size, iter->len - + sizeof (iter->esd->signature_owner)); + } + } + + size_t sls, slh; + rc = efi_secdb_list_list_size(iter->iter, &sls); + if (rc < 0) + return rc; + + rc = efi_secdb_list_header_size(iter->iter, &slh); + if (rc < 0) + return rc; + + rc = efi_secdb_list_sig_size(iter->iter, &ss); + if (rc < 0) + return rc; + + /* if we'd have leftover data, then this ESD is garbage. */ + if ((sls - sizeof (efi_signature_list_t) - slh) % ss != 0) + return -EINVAL; + + iter->nmemb = (sls - sizeof (efi_signature_list_t) - slh) / ss; + } else { + debug("Getting next esd element\n"); + rc = efi_secdb_list_sig_size(iter->iter, &ss); + if (rc < 0) + return rc; + + iter->esd = (efi_signature_data_t *)((intptr_t)iter->esd + ss); + } + + rc = efi_secdb_list_get_type(iter->iter, type); + if (rc < 0) + return rc; + + *owner = iter->esd->signature_owner; + *data = iter->esd->signature_data; + *len = ss - sizeof (iter->esd->signature_owner); + return 1; +} + +int NONNULL(1) PUBLIC +efi_secdb_iter_get_line(efi_secdb_iter *iter) +{ + if (!iter) { + errno = EINVAL; + return -1; + } + + return iter->line; +} + +struct efi_secdb_list_iter { + uint8_t *buf; + size_t len; + + off_t offset; + + efi_signature_list_t *esl; +}; + +int NONNULL(1, 2) +efi_secdb_list_iter_new(efi_secdb_list_iter **iter, uint8_t *buf, size_t len) +{ + if (len < sizeof (efi_signature_list_t) + sizeof (efi_signature_data_t)) { + errno = EINVAL; + return -1; + } + + *iter = calloc(1, sizeof (efi_secdb_list_iter)); + if (!*iter) + return -1; + + (*iter)->buf = buf; + (*iter)->len = len; + + return 0; +} + +int NONNULL(1) +efi_secdb_list_iter_end(efi_secdb_list_iter *iter) +{ + if (!iter) { + errno = EINVAL; + return -1; + } + free(iter); + return 0; +} + +int NONNULL(1, 2, 3, 4) +efi_secdb_list_iter_next(efi_secdb_list_iter *iter, efi_guid_t *type, + efi_signature_data_t **data, size_t *len) +{ + if (!iter) + return -EINVAL; + if (iter->offset < 0) + return -EINVAL; + if ((uint32_t)iter->offset >= iter->len) + return -EINVAL; + + if (!iter->esl) { + debug("Getting next ESL buffer\n"); + iter->esl = (efi_signature_list_t *)iter->buf; + } else { + debug("Getting next efi_signature_list_t\n"); + efi_guid_t type; + efi_secdb_list_get_type(iter, &type); + if (iter->len - iter->offset < iter->esl->signature_list_size) { + debug("EFI signature_ List is malformed"); + debug("list has %lu bytes left, element is %"PRIu32" bytes", + iter->len - iter->offset, + iter->esl->signature_list_size); + return -1; + } + if (!efi_guid_cmp(&type, &efi_guid_x509_cert)) { + int32_t asn1size; + + asn1size = get_asn1_seq_size( + ((uint8_t *)*data) + sizeof (efi_guid_t), + *len - sizeof (efi_guid_t)); + if (asn1size < 0) { + debug("iterator data claims to be an X.509 Cert but is not valid ASN.1 DER"); + } else if ((uint32_t)asn1size != iter->esl->signature_size + - sizeof (efi_guid_t)) { + debug("X.509 Cert ASN.1 size does not match signature_List Size (%d vs %zu)", + asn1size, iter->esl->signature_size - + sizeof (efi_guid_t)); + } + + } + + iter->offset += iter->esl->signature_list_size; + if ((uint32_t)iter->offset >= iter->len) + return 0; + iter->esl = (efi_signature_list_t *)((intptr_t)iter->buf + + iter->offset); + } + + efi_signature_list_t esl; + memset(&esl, '\0', sizeof (esl)); + /* if somehow we've gotten a buffer that's bigger than our + * real list, this will be zeros, so we've hit the end. */ + if (!memcmp(&esl, iter->esl, sizeof (esl))) + return 0; + + /* if this list size is too big for our data, then it's malformed + * data and we're done. */ + if (iter->esl->signature_list_size > iter->len - iter->offset) + return -EINVAL; + + *type = iter->esl->signature_type; + *data = (efi_signature_data_t *)((intptr_t)iter->esl + + sizeof (efi_signature_list_t) + + iter->esl->signature_header_size); + *len = iter->esl->signature_list_size - sizeof (efi_signature_list_t); + + return 1; +} + +int NONNULL(1, 2) +efi_secdb_list_list_size(efi_secdb_list_iter *iter, size_t *sls) +{ + if (!iter || !iter->esl) { + errno = EINVAL; + return -1; + } + /* this has to be at least as large as its header to be valid */ + if (iter->esl->signature_list_size < sizeof (efi_signature_list_t)) { + errno = EINVAL; + return -1; + } + + *sls = iter->esl->signature_list_size; + return 0; +} + +int NONNULL(1, 2) +efi_secdb_list_header_size(efi_secdb_list_iter *iter, size_t *slh) +{ + if (!iter || !iter->esl) { + errno = EINVAL; + return -1; + } + + *slh = iter->esl->signature_header_size; + return 0; +} + +int NONNULL(1, 2) +efi_secdb_list_sig_size(efi_secdb_list_iter *iter, size_t *ss) +{ + if (!iter || !iter->esl) { + errno = EINVAL; + return -1; + } + /* If signature size isn't positive, there's invalid data. */ + if (iter->esl->signature_size < 1) { + errno = EINVAL; + return -1; + } + + *ss = iter->esl->signature_size; + return 0; +} + +int NONNULL(1, 2) +efi_secdb_list_get_type(efi_secdb_list_iter *iter, efi_guid_t *type) +{ + if (!iter || !iter->esl) { + errno = EINVAL; + return -1; + } + + memcpy(type, &iter->esl->signature_type, sizeof (*type)); + return 0; +} -- 2.24.1