From bdf329e4a3ea1209a50d4cbe267ada41384eb750 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Wed, 1 Apr 2020 11:15:01 +0100 Subject: efi_loader: enable RNG if DM_RNG is enabled Enable EFI_RNG_PROTOCOL by default if DM_RNG is set. Signed-off-by: Peter Robinson Acked-by: Sughosh Ganu Remove superfluous 'if DM_RNG' Reviewed-by: Heinrich Schuchardt --- lib/efi_loader/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/efi_loader') diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index 9890144d416..c89ce85c4a8 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -126,6 +126,7 @@ config EFI_GRUB_ARM32_WORKAROUND config EFI_RNG_PROTOCOL bool "EFI_RNG_PROTOCOL support" depends on DM_RNG + default y help Provide a EFI_RNG_PROTOCOL implementation using the hardware random number generator of the platform. -- cgit v1.3.1 From 76956556fc56c8aaa782f131f4e4fa6fbaaf640f Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 10 Apr 2020 17:39:23 +0200 Subject: efi_loader: function descriptions efi_unicode_collation.c Correct function descriptions in efi_unicode_collation.c Add the Unicode collation protocol to the generated HTML documentation. Signed-off-by: Heinrich Schuchardt --- doc/api/efi.rst | 6 ++++++ lib/efi_loader/efi_unicode_collation.c | 6 ++---- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/efi_loader') diff --git a/doc/api/efi.rst b/doc/api/efi.rst index 631c0ceb1df..b9c0c6efc58 100644 --- a/doc/api/efi.rst +++ b/doc/api/efi.rst @@ -151,3 +151,9 @@ Text IO protocols .. kernel-doc:: lib/efi_loader/efi_console.c :internal: + +Unicode Collation protocol +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. kernel-doc:: lib/efi_loader/efi_unicode_collation.c + :internal: diff --git a/lib/efi_loader/efi_unicode_collation.c b/lib/efi_loader/efi_unicode_collation.c index c700be87560..6655c68092e 100644 --- a/lib/efi_loader/efi_unicode_collation.c +++ b/lib/efi_loader/efi_unicode_collation.c @@ -169,8 +169,8 @@ static bool metai_match(const u16 *string, const u16 *pattern) * case-insenitively * * @this: unicode collation protocol instance - * @s: string to compare - * @p: pattern string + * @string: string to compare + * @pattern: pattern string * * The pattern string may use these: * - * matches >= 0 characters @@ -199,7 +199,6 @@ static bool EFIAPI efi_metai_match(struct efi_unicode_collation_protocol *this, * * @this: unicode collation protocol instance * @string: string to convert - * @p: pattern string * * The conversion is done in place. As long as upper and lower letters use the * same number of words this does not pose a problem. @@ -221,7 +220,6 @@ static void EFIAPI efi_str_lwr(struct efi_unicode_collation_protocol *this, * * @this: unicode collation protocol instance * @string: string to convert - * @p: pattern string * * The conversion is done in place. As long as upper and lower letters use the * same number of words this does not pose a problem. -- cgit v1.3.1 From 55976b7fae8604300b07dc0c56139ec6c3a40d5e Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 10 Apr 2020 17:10:34 +0200 Subject: efi_loader: add missing doc comments in efi_disk.c Add missing descriptions for functions in efi_disk.c. Signed-off-by: Heinrich Schuchardt --- lib/efi_loader/efi_disk.c | 52 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) (limited to 'lib/efi_loader') diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c index fc0682bc48c..fd8fe175670 100644 --- a/lib/efi_loader/efi_disk.c +++ b/lib/efi_loader/efi_disk.c @@ -108,6 +108,21 @@ static efi_status_t efi_disk_rw_blocks(struct efi_block_io *this, return EFI_SUCCESS; } +/** + * efi_disk_read_blocks() - reads blocks from device + * + * This function implements the ReadBlocks service of the EFI_BLOCK_IO_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @media_id: id of the medium to be read from + * @lba: starting logical block for reading + * @buffer_size: size of the read buffer + * @buffer: pointer to the destination buffer + * Return: status code + */ static efi_status_t EFIAPI efi_disk_read_blocks(struct efi_block_io *this, u32 media_id, u64 lba, efi_uintn_t buffer_size, void *buffer) @@ -157,6 +172,22 @@ static efi_status_t EFIAPI efi_disk_read_blocks(struct efi_block_io *this, return EFI_EXIT(r); } +/** + * efi_disk_write_blocks() - writes blocks to device + * + * This function implements the WriteBlocks service of the + * EFI_BLOCK_IO_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @media_id: id of the medium to be written to + * @lba: starting logical block for writing + * @buffer_size: size of the write buffer + * @buffer: pointer to the source buffer + * Return: status code + */ static efi_status_t EFIAPI efi_disk_write_blocks(struct efi_block_io *this, u32 media_id, u64 lba, efi_uintn_t buffer_size, void *buffer) @@ -208,9 +239,22 @@ static efi_status_t EFIAPI efi_disk_write_blocks(struct efi_block_io *this, return EFI_EXIT(r); } +/** + * efi_disk_flush_blocks() - flushes modified data to the device + * + * This function implements the FlushBlocks service of the + * EFI_BLOCK_IO_PROTOCOL. + * + * As we always write synchronously nothing is done here. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * Return: status code + */ static efi_status_t EFIAPI efi_disk_flush_blocks(struct efi_block_io *this) { - /* We always write synchronously */ EFI_ENTRY("%p", this); return EFI_EXIT(EFI_SUCCESS); } @@ -286,7 +330,7 @@ static int efi_fs_exists(struct blk_desc *desc, int part) return 1; } -/* +/** * efi_disk_add_dev() - create a handle for a partition or disk * * @parent: parent handle @@ -295,6 +339,8 @@ static int efi_fs_exists(struct blk_desc *desc, int part) * @desc: internal block device * @dev_index: device index for block device * @offset: offset into disk for simple partitions + * @part: partition + * @disk: pointer to receive the created handle * Return: disk object */ static efi_status_t efi_disk_add_dev( @@ -381,7 +427,7 @@ static efi_status_t efi_disk_add_dev( * Create handles and protocols for the partitions of a block device. * * @parent: handle of the parent disk - * @blk_desc: block device + * @desc: block device * @if_typename: interface type * @diskid: device number * @pdevname: device name -- cgit v1.3.1 From 540faca8a1d98997d09cdb3ee964a57a9cf9c5c4 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 10 Apr 2020 17:51:56 +0200 Subject: efi_loader: function descriptions efi_watchdog.c Correct function descriptions in efi_watchdog.c. Add the descriptions to the generated HTML documentation. Signed-off-by: Heinrich Schuchardt --- doc/api/efi.rst | 6 ++++++ lib/efi_loader/efi_watchdog.c | 18 +++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'lib/efi_loader') diff --git a/doc/api/efi.rst b/doc/api/efi.rst index b9c0c6efc58..0667c3aef78 100644 --- a/doc/api/efi.rst +++ b/doc/api/efi.rst @@ -78,6 +78,12 @@ Memory services .. kernel-doc:: lib/efi_loader/efi_memory.c :internal: +SetWatchdogTimer service +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. kernel-doc:: lib/efi_loader/efi_watchdog.c + :internal: + Runtime services ---------------- diff --git a/lib/efi_loader/efi_watchdog.c b/lib/efi_loader/efi_watchdog.c index 6f69b76e4d9..61ea0f79260 100644 --- a/lib/efi_loader/efi_watchdog.c +++ b/lib/efi_loader/efi_watchdog.c @@ -13,7 +13,9 @@ static struct efi_event *watchdog_timer_event; -/* +/** + * efi_watchdog_timer_notify() - resets system upon watchdog event + * * Reset the system when the watchdog event is notified. * * @event: the watchdog event @@ -31,13 +33,13 @@ static void EFIAPI efi_watchdog_timer_notify(struct efi_event *event, EFI_EXIT(EFI_UNSUPPORTED); } -/* - * Reset the watchdog timer. +/** + * efi_set_watchdog() - resets the watchdog timer * * This function is used by the SetWatchdogTimer service. * * @timeout: seconds before reset by watchdog - * @return: status code + * Return: status code */ efi_status_t efi_set_watchdog(unsigned long timeout) { @@ -53,10 +55,12 @@ efi_status_t efi_set_watchdog(unsigned long timeout) return r; } -/* - * Initialize the EFI watchdog. +/** + * efi_watchdog_register() - initializes the EFI watchdog + * + * This function is called by efi_init_obj_list(). * - * This function is called by efi_init_obj_list() + * Return: status code */ efi_status_t efi_watchdog_register(void) { -- cgit v1.3.1 From 9bf09b5120da6d21d9e7774d958b98116828f907 Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:38 +0900 Subject: efi_loader: add CONFIG_EFI_SECURE_BOOT config option Under this configuration, UEFI secure boot support will be added in later patches. Signed-off-by: AKASHI Takahiro --- lib/efi_loader/Kconfig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'lib/efi_loader') diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index c89ce85c4a8..1cfa24ffcf7 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -146,4 +146,22 @@ config EFI_INITRD_FILESPEC help Full path of the initramfs file, e.g. mmc 0:2 initramfs.cpio.gz. +config EFI_SECURE_BOOT + bool "Enable EFI secure boot support" + depends on EFI_LOADER + select SHA256 + select RSA + select RSA_VERIFY_WITH_PKEY + select IMAGE_SIGN_INFO + select ASYMMETRIC_KEY_TYPE + select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select X509_CERTIFICATE_PARSER + select PKCS7_MESSAGE_PARSER + default n + help + Select this option to enable EFI secure boot support. + Once SecureBoot mode is enforced, any EFI binary can run only if + it is signed with a trusted key. To do that, you need to install, + at least, PK, KEK and db. + endif -- cgit v1.3.1 From 593e17d673abe8e903412e6d55b2eb54b159b096 Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:39 +0900 Subject: efi_loader: add signature verification functions In this commit, implemented are a couple of helper functions which will be used to materialize variable authentication as well as image authentication in later patches. Signed-off-by: AKASHI Takahiro --- include/efi_api.h | 87 +++++++ include/efi_loader.h | 72 +++++ lib/efi_loader/Makefile | 1 + lib/efi_loader/efi_signature.c | 578 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 738 insertions(+) create mode 100644 lib/efi_loader/efi_signature.c (limited to 'lib/efi_loader') diff --git a/include/efi_api.h b/include/efi_api.h index 1c40ffc4f56..77d6bf2660b 100644 --- a/include/efi_api.h +++ b/include/efi_api.h @@ -18,6 +18,7 @@ #include #include +#include #ifdef CONFIG_EFI_LOADER #include @@ -329,6 +330,10 @@ struct efi_runtime_services { EFI_GUID(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, \ 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) +#define EFI_IMAGE_SECURITY_DATABASE_GUID \ + EFI_GUID(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, \ + 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f) + #define EFI_FDT_GUID \ EFI_GUID(0xb1b621d5, 0xf19c, 0x41a5, \ 0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0) @@ -1682,4 +1687,86 @@ struct efi_load_file_protocol { #define LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL_VENDOR_RANGE_MIN 0x00001000 #define LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL_VENDOR_RANGE_MAX 0x00004000 +/* Certificate types in signature database */ +#define EFI_CERT_SHA256_GUID \ + EFI_GUID(0xc1c41626, 0x504c, 0x4092, 0xac, 0xa9, \ + 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28) +#define EFI_CERT_RSA2048_GUID \ + EFI_GUID(0x3c5766e8, 0x269c, 0x4e34, 0xaa, 0x14, \ + 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6) +#define EFI_CERT_X509_GUID \ + EFI_GUID(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, \ + 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72) +#define EFI_CERT_X509_SHA256_GUID \ + EFI_GUID(0x3bd2a492, 0x96c0, 0x4079, 0xb4, 0x20, \ + 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed) +#define EFI_CERT_TYPE_PKCS7_GUID \ + EFI_GUID(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9, \ + 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7) + +/** + * win_certificate_uefi_guid - A certificate that encapsulates + * a GUID-specific signature + * + * @hdr: Windows certificate header + * @cert_type: Certificate type + * @cert_data: Certificate data + */ +struct win_certificate_uefi_guid { + WIN_CERTIFICATE hdr; + efi_guid_t cert_type; + u8 cert_data[]; +} __attribute__((__packed__)); + +/** + * efi_variable_authentication_2 - A time-based authentication method + * descriptor + * + * This structure describes an authentication information for + * a variable with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS + * and should be included as part of a variable's value. + * Only EFI_CERT_TYPE_PKCS7_GUID is accepted. + * + * @time_stamp: Descriptor's time stamp + * @auth_info: Authentication info + */ +struct efi_variable_authentication_2 { + struct efi_time time_stamp; + struct win_certificate_uefi_guid auth_info; +} __attribute__((__packed__)); + +/** + * efi_signature_data - A format of signature + * + * This structure describes a single signature in signature database. + * + * @signature_owner: Signature owner + * @signature_data: Signature data + */ +struct efi_signature_data { + efi_guid_t signature_owner; + u8 signature_data[]; +} __attribute__((__packed__)); + +/** + * efi_signature_list - A format of signature database + * + * This structure describes a list of signatures with the same type. + * An authenticated variable's value is a concatenation of one or more + * efi_signature_list's. + * + * @signature_type: Signature type + * @signature_list_size: Size of signature list + * @signature_header_size: Size of signature header + * @signature_size: Size of signature + */ +struct efi_signature_list { + efi_guid_t signature_type; + u32 signature_list_size; + u32 signature_header_size; + u32 signature_size; +/* u8 signature_header[signature_header_size]; */ +/* struct efi_signature_data signatures[...][signature_size]; */ +} __attribute__((__packed__)); + #endif diff --git a/include/efi_loader.h b/include/efi_loader.h index 3f2792892f3..8cf85d2fb7e 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -26,6 +26,7 @@ static inline void *guidcpy(void *dst, const void *src) #if CONFIG_IS_ENABLED(EFI_LOADER) #include +#include /* Maximum number of configuration tables */ #define EFI_MAX_CONFIGURATION_TABLES 16 @@ -178,6 +179,11 @@ extern const efi_guid_t efi_guid_hii_config_routing_protocol; extern const efi_guid_t efi_guid_hii_config_access_protocol; extern const efi_guid_t efi_guid_hii_database_protocol; extern const efi_guid_t efi_guid_hii_string_protocol; +/* GUIDs for authentication */ +extern const efi_guid_t efi_guid_image_security_database; +extern const efi_guid_t efi_guid_sha256; +extern const efi_guid_t efi_guid_cert_x509; +extern const efi_guid_t efi_guid_cert_x509_sha256; /* GUID of RNG protocol */ extern const efi_guid_t efi_guid_rng_protocol; @@ -680,6 +686,72 @@ void efi_deserialize_load_option(struct efi_load_option *lo, u8 *data); unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data); efi_status_t efi_bootmgr_load(efi_handle_t *handle); +#ifdef CONFIG_EFI_SECURE_BOOT +#include + +/** + * efi_image_regions - A list of memory regions + * + * @max: Maximum number of regions + * @num: Number of regions + * @reg: array of regions + */ +struct efi_image_regions { + int max; + int num; + struct image_region reg[]; +}; + +/** + * efi_sig_data - A decoded data of struct efi_signature_data + * + * This structure represents an internal form of signature in + * signature database. A listed list may represent a signature list. + * + * @next: Pointer to next entry + * @onwer: Signature owner + * @data: Pointer to signature data + * @size: Size of signature data + */ +struct efi_sig_data { + struct efi_sig_data *next; + efi_guid_t owner; + void *data; + size_t size; +}; + +/** + * efi_signature_store - A decoded data of signature database + * + * This structure represents an internal form of signature database. + * + * @next: Pointer to next entry + * @sig_type: Signature type + * @sig_data_list: Pointer to signature list + */ +struct efi_signature_store { + struct efi_signature_store *next; + efi_guid_t sig_type; + struct efi_sig_data *sig_data_list; +}; + +struct x509_certificate; +struct pkcs7_message; + +bool efi_signature_verify_cert(struct x509_certificate *cert, + struct efi_signature_store *dbx); +bool efi_signature_verify_signers(struct pkcs7_message *msg, + struct efi_signature_store *dbx); +bool efi_signature_verify_with_sigdb(struct efi_image_regions *regs, + struct pkcs7_message *msg, + struct efi_signature_store *db, + struct x509_certificate **cert); + +efi_status_t efi_image_region_add(struct efi_image_regions *regs, + const void *start, const void *end, + int nocheck); +#endif /* CONFIG_EFI_SECURE_BOOT */ + #else /* CONFIG_IS_ENABLED(EFI_LOADER) */ /* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 9b3b7044733..eff3c25ec30 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -44,3 +44,4 @@ obj-$(CONFIG_GENERATE_ACPI_TABLE) += efi_acpi.o obj-$(CONFIG_GENERATE_SMBIOS_TABLE) += efi_smbios.o obj-$(CONFIG_EFI_RNG_PROTOCOL) += efi_rng.o obj-$(CONFIG_EFI_LOAD_FILE2_INITRD) += efi_load_initrd.o +obj-y += efi_signature.o diff --git a/lib/efi_loader/efi_signature.c b/lib/efi_loader/efi_signature.c new file mode 100644 index 00000000000..491fd5512d8 --- /dev/null +++ b/lib/efi_loader/efi_signature.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Patrick Wildt + * Copyright (c) 2019 Linaro Limited, Author: AKASHI Takahiro + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../lib/crypto/pkcs7_parser.h" + +const efi_guid_t efi_guid_image_security_database = + EFI_IMAGE_SECURITY_DATABASE_GUID; +const efi_guid_t efi_guid_sha256 = EFI_CERT_SHA256_GUID; +const efi_guid_t efi_guid_cert_rsa2048 = EFI_CERT_RSA2048_GUID; +const efi_guid_t efi_guid_cert_x509 = EFI_CERT_X509_GUID; +const efi_guid_t efi_guid_cert_x509_sha256 = EFI_CERT_X509_SHA256_GUID; + +#ifdef CONFIG_EFI_SECURE_BOOT + +/** + * efi_hash_regions - calculate a hash value + * @regs: List of regions + * @hash: Pointer to a pointer to buffer holding a hash value + * @size: Size of buffer to be returned + * + * Calculate a sha256 value of @regs and return a value in @hash. + * + * Return: true on success, false on error + */ +static bool efi_hash_regions(struct efi_image_regions *regs, void **hash, + size_t *size) +{ + *size = 0; + *hash = calloc(1, SHA256_SUM_LEN); + if (!*hash) { + debug("Out of memory\n"); + return false; + } + *size = SHA256_SUM_LEN; + + hash_calculate("sha256", regs->reg, regs->num, *hash); +#ifdef DEBUG + debug("hash calculated:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + *hash, SHA256_SUM_LEN, false); +#endif + + return true; +} + +/** + * efi_hash_msg_content - calculate a hash value of contentInfo + * @msg: Signature + * @hash: Pointer to a pointer to buffer holding a hash value + * @size: Size of buffer to be returned + * + * Calculate a sha256 value of contentInfo in @msg and return a value in @hash. + * + * Return: true on success, false on error + */ +static bool efi_hash_msg_content(struct pkcs7_message *msg, void **hash, + size_t *size) +{ + struct image_region regtmp; + + *size = 0; + *hash = calloc(1, SHA256_SUM_LEN); + if (!*hash) { + debug("Out of memory\n"); + free(msg); + return false; + } + *size = SHA256_SUM_LEN; + + regtmp.data = msg->data; + regtmp.size = msg->data_len; + + hash_calculate("sha256", ®tmp, 1, *hash); +#ifdef DEBUG + debug("hash calculated based on contentInfo:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + *hash, SHA256_SUM_LEN, false); +#endif + + return true; +} + +/** + * efi_signature_verify - verify a signature with a certificate + * @regs: List of regions to be authenticated + * @signed_info: Pointer to PKCS7's signed_info + * @cert: x509 certificate + * + * Signature pointed to by @signed_info against image pointed to by @regs + * is verified by a certificate pointed to by @cert. + * @signed_info holds a signature, including a message digest which is to be + * compared with a hash value calculated from @regs. + * + * Return: true if signature is verified, false if not + */ +static bool efi_signature_verify(struct efi_image_regions *regs, + struct pkcs7_message *msg, + struct pkcs7_signed_info *ps_info, + struct x509_certificate *cert) +{ + struct image_sign_info info; + struct image_region regtmp[2]; + void *hash; + size_t size; + char c; + bool verified; + + debug("%s: Enter, %p, %p, %p(issuer: %s, subject: %s)\n", __func__, + regs, ps_info, cert, cert->issuer, cert->subject); + + verified = false; + + memset(&info, '\0', sizeof(info)); + info.padding = image_get_padding_algo("pkcs-1.5"); + /* + * Note: image_get_[checksum|crypto]_algo takes an string + * argument like "," + * TODO: support other hash algorithms + */ + if (!strcmp(ps_info->sig->hash_algo, "sha1")) { + info.checksum = image_get_checksum_algo("sha1,rsa2048"); + info.name = "sha1,rsa2048"; + } else if (!strcmp(ps_info->sig->hash_algo, "sha256")) { + info.checksum = image_get_checksum_algo("sha256,rsa2048"); + info.name = "sha256,rsa2048"; + } else { + debug("unknown msg digest algo: %s\n", ps_info->sig->hash_algo); + goto out; + } + info.crypto = image_get_crypto_algo(info.name); + + info.key = cert->pub->key; + info.keylen = cert->pub->keylen; + + /* verify signature */ + debug("%s: crypto: %s, signature len:%x\n", __func__, + info.name, ps_info->sig->s_size); + if (ps_info->aa_set & (1UL << sinfo_has_message_digest)) { + debug("%s: RSA verify authentication attribute\n", __func__); + /* + * NOTE: This path will be executed only for + * PE image authentication + */ + + /* check if hash matches digest first */ + debug("checking msg digest first, len:0x%x\n", + ps_info->msgdigest_len); + +#ifdef DEBUG + debug("hash in database:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + ps_info->msgdigest, ps_info->msgdigest_len, + false); +#endif + /* against contentInfo first */ + if ((msg->data && efi_hash_msg_content(msg, &hash, &size)) || + /* for signed image */ + efi_hash_regions(regs, &hash, &size)) { + /* for authenticated variable */ + if (ps_info->msgdigest_len != size || + memcmp(hash, ps_info->msgdigest, size)) { + debug("Digest doesn't match\n"); + free(hash); + goto out; + } + + free(hash); + } else { + debug("Digesting image failed\n"); + goto out; + } + + /* against digest */ + c = 0x31; + regtmp[0].data = &c; + regtmp[0].size = 1; + regtmp[1].data = ps_info->authattrs; + regtmp[1].size = ps_info->authattrs_len; + + if (!rsa_verify(&info, regtmp, 2, + ps_info->sig->s, ps_info->sig->s_size)) + verified = true; + } else { + debug("%s: RSA verify content data\n", __func__); + /* against all data */ + if (!rsa_verify(&info, regs->reg, regs->num, + ps_info->sig->s, ps_info->sig->s_size)) + verified = true; + } + +out: + debug("%s: Exit, verified: %d\n", __func__, verified); + return verified; +} + +/** + * efi_signature_verify_with_list - verify a signature with signature list + * @regs: List of regions to be authenticated + * @msg: Signature + * @signed_info: Pointer to PKCS7's signed_info + * @siglist: Signature list for certificates + * @valid_cert: x509 certificate that verifies this signature + * + * Signature pointed to by @signed_info against image pointed to by @regs + * is verified by signature list pointed to by @siglist. + * Signature database is a simple concatenation of one or more + * signature list(s). + * + * Return: true if signature is verified, false if not + */ +static +bool efi_signature_verify_with_list(struct efi_image_regions *regs, + struct pkcs7_message *msg, + struct pkcs7_signed_info *signed_info, + struct efi_signature_store *siglist, + struct x509_certificate **valid_cert) +{ + struct x509_certificate *cert; + struct efi_sig_data *sig_data; + bool verified = false; + + debug("%s: Enter, %p, %p, %p, %p\n", __func__, + regs, signed_info, siglist, valid_cert); + + if (!signed_info) { + void *hash; + size_t size; + + debug("%s: unsigned image\n", __func__); + /* + * verify based on calculated hash value + * TODO: support other hash algorithms + */ + if (guidcmp(&siglist->sig_type, &efi_guid_sha256)) { + debug("Digest algorithm is not supported: %pUl\n", + &siglist->sig_type); + goto out; + } + + if (!efi_hash_regions(regs, &hash, &size)) { + debug("Digesting unsigned image failed\n"); + goto out; + } + + /* go through the list */ + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { +#ifdef DEBUG + debug("Msg digest in database:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + sig_data->data, sig_data->size, false); +#endif + if ((sig_data->size == size) && + !memcmp(sig_data->data, hash, size)) { + verified = true; + free(hash); + goto out; + } + } + free(hash); + goto out; + } + + debug("%s: signed image\n", __func__); + if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509)) { + debug("Signature type is not supported: %pUl\n", + &siglist->sig_type); + goto out; + } + + /* go through the list */ + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { + /* TODO: support owner check based on policy */ + + cert = x509_cert_parse(sig_data->data, sig_data->size); + if (IS_ERR(cert)) { + debug("Parsing x509 certificate failed\n"); + goto out; + } + + verified = efi_signature_verify(regs, msg, signed_info, cert); + + if (verified) { + if (valid_cert) + *valid_cert = cert; + else + x509_free_certificate(cert); + break; + } + x509_free_certificate(cert); + } + +out: + debug("%s: Exit, verified: %d\n", __func__, verified); + return verified; +} + +/** + * efi_signature_verify_with_sigdb - verify a signature with db + * @regs: List of regions to be authenticated + * @msg: Signature + * @db: Signature database for trusted certificates + * @cert: x509 certificate that verifies this signature + * + * Signature pointed to by @msg against image pointed to by @regs + * is verified by signature database pointed to by @db. + * + * Return: true if signature is verified, false if not + */ +bool efi_signature_verify_with_sigdb(struct efi_image_regions *regs, + struct pkcs7_message *msg, + struct efi_signature_store *db, + struct x509_certificate **cert) +{ + struct pkcs7_signed_info *info; + struct efi_signature_store *siglist; + bool verified = false; + + debug("%s: Enter, %p, %p, %p, %p\n", __func__, regs, msg, db, cert); + + if (!db) + goto out; + + if (!db->sig_data_list) + goto out; + + /* for unsigned image */ + if (!msg) { + debug("%s: Verify unsigned image with db\n", __func__); + for (siglist = db; siglist; siglist = siglist->next) + if (efi_signature_verify_with_list(regs, NULL, NULL, + siglist, cert)) { + verified = true; + goto out; + } + + goto out; + } + + /* for signed image or variable */ + debug("%s: Verify signed image with db\n", __func__); + for (info = msg->signed_infos; info; info = info->next) { + debug("Signed Info: digest algo: %s, pkey algo: %s\n", + info->sig->hash_algo, info->sig->pkey_algo); + + for (siglist = db; siglist; siglist = siglist->next) { + if (efi_signature_verify_with_list(regs, msg, info, + siglist, cert)) { + verified = true; + goto out; + } + } + } + +out: + debug("%s: Exit, verified: %d\n", __func__, verified); + return verified; +} + +/** + * efi_search_siglist - search signature list for a certificate + * @cert: x509 certificate + * @siglist: Signature list + * @revoc_time: Pointer to buffer for revocation time + * + * Search signature list pointed to by @siglist and find a certificate + * pointed to by @cert. + * If found, revocation time that is specified in signature database is + * returned in @revoc_time. + * + * Return: true if certificate is found, false if not + */ +static bool efi_search_siglist(struct x509_certificate *cert, + struct efi_signature_store *siglist, + time64_t *revoc_time) +{ + struct image_region reg[1]; + void *hash = NULL, *msg = NULL; + struct efi_sig_data *sig_data; + bool found = false; + + /* can be null */ + if (!siglist->sig_data_list) + return false; + + if (guidcmp(&siglist->sig_type, &efi_guid_cert_x509_sha256)) { + /* TODO: other hash algos */ + debug("Certificate's digest type is not supported: %pUl\n", + &siglist->sig_type); + goto out; + } + + /* calculate hash of TBSCertificate */ + msg = calloc(1, SHA256_SUM_LEN); + if (!msg) { + debug("Out of memory\n"); + goto out; + } + + hash = calloc(1, SHA256_SUM_LEN); + if (!hash) { + debug("Out of memory\n"); + goto out; + } + + reg[0].data = cert->tbs; + reg[0].size = cert->tbs_size; + hash_calculate("sha256", reg, 1, msg); + + /* go through signature list */ + for (sig_data = siglist->sig_data_list; sig_data; + sig_data = sig_data->next) { + /* + * struct efi_cert_x509_sha256 { + * u8 tbs_hash[256/8]; + * time64_t revocation_time; + * }; + */ + if ((sig_data->size == SHA256_SUM_LEN) && + !memcmp(sig_data->data, hash, SHA256_SUM_LEN)) { + memcpy(revoc_time, sig_data->data + SHA256_SUM_LEN, + sizeof(*revoc_time)); + found = true; + goto out; + } + } + +out: + free(hash); + free(msg); + + return found; +} + +/** + * efi_signature_verify_cert - verify a certificate with dbx + * @cert: x509 certificate + * @dbx: Signature database + * + * Search signature database pointed to by @dbx and find a certificate + * pointed to by @cert. + * This function is expected to be used against "dbx". + * + * Return: true if a certificate is not rejected, false otherwise. + */ +bool efi_signature_verify_cert(struct x509_certificate *cert, + struct efi_signature_store *dbx) +{ + struct efi_signature_store *siglist; + time64_t revoc_time; + bool found = false; + + debug("%s: Enter, %p, %p\n", __func__, dbx, cert); + + if (!cert) + return false; + + for (siglist = dbx; siglist; siglist = siglist->next) { + if (efi_search_siglist(cert, siglist, &revoc_time)) { + /* TODO */ + /* compare signing time with revocation time */ + + found = true; + break; + } + } + + debug("%s: Exit, verified: %d\n", __func__, !found); + return !found; +} + +/** + * efi_signature_verify_signers - verify signers' certificates with dbx + * @msg: Signature + * @dbx: Signature database + * + * Determine if any of signers' certificates in @msg may be verified + * by any of certificates in signature database pointed to by @dbx. + * This function is expected to be used against "dbx". + * + * Return: true if none of certificates is rejected, false otherwise. + */ +bool efi_signature_verify_signers(struct pkcs7_message *msg, + struct efi_signature_store *dbx) +{ + struct pkcs7_signed_info *info; + bool found = false; + + debug("%s: Enter, %p, %p\n", __func__, msg, dbx); + + if (!msg) + goto out; + + for (info = msg->signed_infos; info; info = info->next) { + if (info->signer && + !efi_signature_verify_cert(info->signer, dbx)) { + found = true; + goto out; + } + } +out: + debug("%s: Exit, verified: %d\n", __func__, !found); + return !found; +} + +/** + * efi_image_region_add - add an entry of region + * @regs: Pointer to array of regions + * @start: Start address of region + * @end: End address of region + * @nocheck: flag against overlapped regions + * + * Take one entry of region [@start, @end] and append it to the list + * pointed to by @regs. If @nocheck is false, overlapping among entries + * will be checked first. + * + * Return: 0 on success, status code (negative) on error + */ +efi_status_t efi_image_region_add(struct efi_image_regions *regs, + const void *start, const void *end, + int nocheck) +{ + struct image_region *reg; + int i, j; + + if (regs->num >= regs->max) { + debug("%s: no more room for regions\n", __func__); + return EFI_OUT_OF_RESOURCES; + } + + if (end < start) + return EFI_INVALID_PARAMETER; + + for (i = 0; i < regs->num; i++) { + reg = ®s->reg[i]; + if (nocheck) + continue; + + if (start > reg->data + reg->size) + continue; + + if ((start >= reg->data && start < reg->data + reg->size) || + (end > reg->data && end < reg->data + reg->size)) { + debug("%s: new region already part of another\n", + __func__); + return EFI_INVALID_PARAMETER; + } + + if (start < reg->data && end < reg->data + reg->size) { + for (j = regs->num - 1; j >= i; j--) + memcpy(®s->reg[j], ®s->reg[j + 1], + sizeof(*reg)); + break; + } + } + + reg = ®s->reg[i]; + reg->data = start; + reg->size = end - start; + regs->num++; + + return EFI_SUCCESS; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ -- cgit v1.3.1 From be6296d05b072f312a20587cf2de8e4456bcb8ff Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:40 +0900 Subject: efi_loader: add signature database parser efi_signature_parse_sigdb() is a helper function will be used to parse signature database variable and instantiate a signature store structure in later patches. Signed-off-by: AKASHI Takahiro --- include/efi_loader.h | 3 + lib/efi_loader/efi_signature.c | 226 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) (limited to 'lib/efi_loader') diff --git a/include/efi_loader.h b/include/efi_loader.h index 8cf85d2fb7e..fea2ead02e9 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -750,6 +750,9 @@ bool efi_signature_verify_with_sigdb(struct efi_image_regions *regs, efi_status_t efi_image_region_add(struct efi_image_regions *regs, const void *start, const void *end, int nocheck); + +void efi_sigstore_free(struct efi_signature_store *sigstore); +struct efi_signature_store *efi_sigstore_parse_sigdb(u16 *name); #endif /* CONFIG_EFI_SECURE_BOOT */ #else /* CONFIG_IS_ENABLED(EFI_LOADER) */ diff --git a/lib/efi_loader/efi_signature.c b/lib/efi_loader/efi_signature.c index 491fd5512d8..658e3547da3 100644 --- a/lib/efi_loader/efi_signature.c +++ b/lib/efi_loader/efi_signature.c @@ -575,4 +575,230 @@ efi_status_t efi_image_region_add(struct efi_image_regions *regs, return EFI_SUCCESS; } + +/** + * efi_sigstore_free - free signature store + * @sigstore: Pointer to signature store structure + * + * Feee all the memories held in signature store and itself, + * which were allocated by efi_sigstore_parse_sigdb(). + */ +void efi_sigstore_free(struct efi_signature_store *sigstore) +{ + struct efi_signature_store *sigstore_next; + struct efi_sig_data *sig_data, *sig_data_next; + + while (sigstore) { + sigstore_next = sigstore->next; + + sig_data = sigstore->sig_data_list; + while (sig_data) { + sig_data_next = sig_data->next; + free(sig_data->data); + free(sig_data); + sig_data = sig_data_next; + } + + free(sigstore); + sigstore = sigstore_next; + } +} + +/** + * efi_sigstore_parse_siglist - parse a signature list + * @name: Pointer to signature list + * + * Parse signature list and instantiate a signature store structure. + * Signature database is a simple concatenation of one or more + * signature list(s). + * + * Return: Pointer to signature store on success, NULL on error + */ +static struct efi_signature_store * +efi_sigstore_parse_siglist(struct efi_signature_list *esl) +{ + struct efi_signature_store *siglist = NULL; + struct efi_sig_data *sig_data, *sig_data_next; + struct efi_signature_data *esd; + size_t left; + + /* + * UEFI specification defines certificate types: + * for non-signed images, + * EFI_CERT_SHA256_GUID + * EFI_CERT_RSA2048_GUID + * EFI_CERT_RSA2048_SHA256_GUID + * EFI_CERT_SHA1_GUID + * EFI_CERT_RSA2048_SHA_GUID + * EFI_CERT_SHA224_GUID + * EFI_CERT_SHA384_GUID + * EFI_CERT_SHA512_GUID + * + * for signed images, + * EFI_CERT_X509_GUID + * NOTE: Each certificate will normally be in a separate + * EFI_SIGNATURE_LIST as the size may vary depending on + * its algo's. + * + * for timestamp revocation of certificate, + * EFI_CERT_X509_SHA512_GUID + * EFI_CERT_X509_SHA256_GUID + * EFI_CERT_X509_SHA384_GUID + */ + + if (esl->signature_list_size + <= (sizeof(*esl) + esl->signature_header_size)) { + debug("Siglist in wrong format\n"); + return NULL; + } + + /* Create a head */ + siglist = calloc(sizeof(*siglist), 1); + if (!siglist) { + debug("Out of memory\n"); + goto err; + } + memcpy(&siglist->sig_type, &esl->signature_type, sizeof(efi_guid_t)); + + /* Go through the list */ + sig_data_next = NULL; + left = esl->signature_list_size + - (sizeof(*esl) + esl->signature_header_size); + esd = (struct efi_signature_data *) + ((u8 *)esl + sizeof(*esl) + esl->signature_header_size); + + while ((left > 0) && left >= esl->signature_size) { + /* Signature must exist if there is remaining data. */ + if (left < esl->signature_size) { + debug("Certificate is too small\n"); + goto err; + } + + sig_data = calloc(esl->signature_size + - sizeof(esd->signature_owner), 1); + if (!sig_data) { + debug("Out of memory\n"); + goto err; + } + + /* Append signature data */ + memcpy(&sig_data->owner, &esd->signature_owner, + sizeof(efi_guid_t)); + sig_data->size = esl->signature_size + - sizeof(esd->signature_owner); + sig_data->data = malloc(sig_data->size); + if (!sig_data->data) { + debug("Out of memory\n"); + goto err; + } + memcpy(sig_data->data, esd->signature_data, sig_data->size); + + sig_data->next = sig_data_next; + sig_data_next = sig_data; + + /* Next */ + esd = (struct efi_signature_data *) + ((u8 *)esd + esl->signature_size); + left -= esl->signature_size; + } + siglist->sig_data_list = sig_data_next; + + return siglist; + +err: + efi_sigstore_free(siglist); + + return NULL; +} + +/** + * efi_sigstore_parse_sigdb - parse a signature database variable + * @name: Variable's name + * + * Read in a value of signature database variable pointed to by + * @name, parse it and instantiate a signature store structure. + * + * Return: Pointer to signature store on success, NULL on error + */ +struct efi_signature_store *efi_sigstore_parse_sigdb(u16 *name) +{ + struct efi_signature_store *sigstore = NULL, *siglist; + struct efi_signature_list *esl; + const efi_guid_t *vendor; + void *db; + efi_uintn_t db_size; + efi_status_t ret; + + if (!u16_strcmp(name, L"PK") || !u16_strcmp(name, L"KEK")) { + vendor = &efi_global_variable_guid; + } else if (!u16_strcmp(name, L"db") || !u16_strcmp(name, L"dbx")) { + vendor = &efi_guid_image_security_database; + } else { + debug("unknown signature database, %ls\n", name); + return NULL; + } + + /* retrieve variable data */ + db_size = 0; + ret = EFI_CALL(efi_get_variable(name, vendor, NULL, &db_size, NULL)); + if (ret == EFI_NOT_FOUND) { + debug("variable, %ls, not found\n", name); + sigstore = calloc(sizeof(*sigstore), 1); + return sigstore; + } else if (ret != EFI_BUFFER_TOO_SMALL) { + debug("Getting variable, %ls, failed\n", name); + return NULL; + } + + db = malloc(db_size); + if (!db) { + debug("Out of memory\n"); + return NULL; + } + + ret = EFI_CALL(efi_get_variable(name, vendor, NULL, &db_size, db)); + if (ret != EFI_SUCCESS) { + debug("Getting variable, %ls, failed\n", name); + goto err; + } + + /* Parse siglist list */ + esl = db; + while (db_size > 0) { + /* List must exist if there is remaining data. */ + if (db_size < sizeof(*esl)) { + debug("variable, %ls, in wrong format\n", name); + goto err; + } + + if (db_size < esl->signature_list_size) { + debug("variable, %ls, in wrong format\n", name); + goto err; + } + + /* Parse a single siglist. */ + siglist = efi_sigstore_parse_siglist(esl); + if (!siglist) { + debug("Parsing signature list of %ls failed\n", name); + goto err; + } + + /* Append siglist */ + siglist->next = sigstore; + sigstore = siglist; + + /* Next */ + db_size -= esl->signature_list_size; + esl = (void *)esl + esl->signature_list_size; + } + free(db); + + return sigstore; + +err: + efi_sigstore_free(sigstore); + free(db); + + return NULL; +} #endif /* CONFIG_EFI_SECURE_BOOT */ -- cgit v1.3.1 From 767f6eeb01d331a7570a1a5e89010b09e5499bfb Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:41 +0900 Subject: efi_loader: variable: support variable authentication With this commit, EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS is supported for authenticated variables and the system secure state will transfer between setup mode and user mode as UEFI specification section 32.3 describes. Internally, authentication data is stored as part of authenticated variable's value. It is nothing but a pkcs7 message (but we need some wrapper, see efi_variable_parse_signature()) and will be validated by efi_variable_authenticate(), hence efi_signature_verify_with_db(). Associated time value will be encoded in "{...,time=...}" along with other UEFI variable's attributes. Signed-off-by: AKASHI Takahiro --- include/efi_loader.h | 3 + lib/efi_loader/efi_variable.c | 666 +++++++++++++++++++++++++++++++++++------- 2 files changed, 565 insertions(+), 104 deletions(-) (limited to 'lib/efi_loader') diff --git a/include/efi_loader.h b/include/efi_loader.h index fea2ead02e9..ef8d184a6ee 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -184,6 +184,7 @@ extern const efi_guid_t efi_guid_image_security_database; extern const efi_guid_t efi_guid_sha256; extern const efi_guid_t efi_guid_cert_x509; extern const efi_guid_t efi_guid_cert_x509_sha256; +extern const efi_guid_t efi_guid_cert_type_pkcs7; /* GUID of RNG protocol */ extern const efi_guid_t efi_guid_rng_protocol; @@ -753,6 +754,8 @@ efi_status_t efi_image_region_add(struct efi_image_regions *regs, void efi_sigstore_free(struct efi_signature_store *sigstore); struct efi_signature_store *efi_sigstore_parse_sigdb(u16 *name); + +bool efi_secure_boot_enabled(void); #endif /* CONFIG_EFI_SECURE_BOOT */ #else /* CONFIG_IS_ENABLED(EFI_LOADER) */ diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index fe2f2645913..adb78470f2d 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -10,8 +10,14 @@ #include #include #include +#include #include +#include #include +#include "../lib/crypto/pkcs7_parser.h" + +const efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID; +static bool efi_secure_boot; #define READ_ONLY BIT(31) @@ -106,9 +112,10 @@ static const char *prefix(const char *str, const char *prefix) * * @str: value of U-Boot variable * @attrp: pointer to UEFI attributes + * @timep: pointer to time attribute * Return: pointer to remainder of U-Boot variable value */ -static const char *parse_attr(const char *str, u32 *attrp) +static const char *parse_attr(const char *str, u32 *attrp, u64 *timep) { u32 attr = 0; char sep = '{'; @@ -131,6 +138,12 @@ static const char *parse_attr(const char *str, u32 *attrp) attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS; } else if ((s = prefix(str, "run"))) { attr |= EFI_VARIABLE_RUNTIME_ACCESS; + } else if ((s = prefix(str, "time="))) { + attr |= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + hex2bin((u8 *)timep, s, sizeof(*timep)); + s += sizeof(*timep) * 2; + } else if (*str == '}') { + break; } else { printf("invalid attribute: %s\n", str); break; @@ -148,48 +161,291 @@ static const char *parse_attr(const char *str, u32 *attrp) } /** - * efi_get_variable() - retrieve value of a UEFI variable + * efi_secure_boot_enabled - return if secure boot is enabled or not * - * This function implements the GetVariable runtime service. + * Return: true if enabled, false if disabled + */ +bool efi_secure_boot_enabled(void) +{ + return efi_secure_boot; +} + +#ifdef CONFIG_EFI_SECURE_BOOT +static u8 pkcs7_hdr[] = { + /* SEQUENCE */ + 0x30, 0x82, 0x05, 0xc7, + /* OID: pkcs7-signedData */ + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, + /* Context Structured? */ + 0xa0, 0x82, 0x05, 0xb8, +}; + +/** + * efi_variable_parse_signature - parse a signature in variable + * @buf: Pointer to variable's value + * @buflen: Length of @buf * - * See the Unified Extensible Firmware Interface (UEFI) specification for - * details. + * Parse a signature embedded in variable's value and instantiate + * a pkcs7_message structure. Since pkcs7_parse_message() accepts only + * pkcs7's signedData, some header needed be prepended for correctly + * parsing authentication data, particularly for variable's. * - * @variable_name: name of the variable - * @vendor: vendor GUID - * @attributes: attributes of the variable - * @data_size: size of the buffer to which the variable value is copied - * @data: buffer to which the variable value is copied - * Return: status code + * Return: Pointer to pkcs7_message structure on success, NULL on error */ -efi_status_t EFIAPI efi_get_variable(u16 *variable_name, - const efi_guid_t *vendor, u32 *attributes, - efi_uintn_t *data_size, void *data) +static struct pkcs7_message *efi_variable_parse_signature(const void *buf, + size_t buflen) +{ + u8 *ebuf; + size_t ebuflen, len; + struct pkcs7_message *msg; + + /* + * This is the best assumption to check if the binary is + * already in a form of pkcs7's signedData. + */ + if (buflen > sizeof(pkcs7_hdr) && + !memcmp(&((u8 *)buf)[4], &pkcs7_hdr[4], 11)) { + msg = pkcs7_parse_message(buf, buflen); + goto out; + } + + /* + * Otherwise, we should add a dummy prefix sequence for pkcs7 + * message parser to be able to process. + * NOTE: EDK2 also uses similar hack in WrapPkcs7Data() + * in CryptoPkg/Library/BaseCryptLib/Pk/CryptPkcs7VerifyCommon.c + * TODO: + * The header should be composed in a more refined manner. + */ + debug("Makeshift prefix added to authentication data\n"); + ebuflen = sizeof(pkcs7_hdr) + buflen; + if (ebuflen <= 0x7f) { + debug("Data is too short\n"); + return NULL; + } + + ebuf = malloc(ebuflen); + if (!ebuf) { + debug("Out of memory\n"); + return NULL; + } + + memcpy(ebuf, pkcs7_hdr, sizeof(pkcs7_hdr)); + memcpy(ebuf + sizeof(pkcs7_hdr), buf, buflen); + len = ebuflen - 4; + ebuf[2] = (len >> 8) & 0xff; + ebuf[3] = len & 0xff; + len = ebuflen - 0x13; + ebuf[0x11] = (len >> 8) & 0xff; + ebuf[0x12] = len & 0xff; + + msg = pkcs7_parse_message(ebuf, ebuflen); + + free(ebuf); + +out: + if (IS_ERR(msg)) + return NULL; + + return msg; +} + +/** + * efi_variable_authenticate - authenticate a variable + * @variable: Variable name in u16 + * @vendor: Guid of variable + * @data_size: Size of @data + * @data: Pointer to variable's value + * @given_attr: Attributes to be given at SetVariable() + * @env_attr: Attributes that an existing variable holds + * @time: signed time that an existing variable holds + * + * Called by efi_set_variable() to verify that the input is correct. + * Will replace the given data pointer with another that points to + * the actual data to store in the internal memory. + * On success, @data and @data_size will be replaced with variable's + * actual data, excluding authentication data, and its size, and variable's + * attributes and signed time will also be returned in @env_attr and @time, + * respectively. + * + * Return: EFI_SUCCESS on success, status code (negative) on error + */ +static efi_status_t efi_variable_authenticate(u16 *variable, + const efi_guid_t *vendor, + efi_uintn_t *data_size, + const void **data, u32 given_attr, + u32 *env_attr, u64 *time) +{ + const struct efi_variable_authentication_2 *auth; + struct efi_signature_store *truststore, *truststore2; + struct pkcs7_message *var_sig; + struct efi_image_regions *regs; + struct efi_time timestamp; + struct rtc_time tm; + u64 new_time; + efi_status_t ret; + + var_sig = NULL; + truststore = NULL; + truststore2 = NULL; + regs = NULL; + ret = EFI_SECURITY_VIOLATION; + + if (*data_size < sizeof(struct efi_variable_authentication_2)) + goto err; + + /* authentication data */ + auth = *data; + if (*data_size < (sizeof(auth->time_stamp) + + auth->auth_info.hdr.dwLength)) + goto err; + + if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) + goto err; + + *data += sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength; + *data_size -= (sizeof(auth->time_stamp) + + auth->auth_info.hdr.dwLength); + + memcpy(×tamp, &auth->time_stamp, sizeof(timestamp)); + memset(&tm, 0, sizeof(tm)); + tm.tm_year = timestamp.year; + tm.tm_mon = timestamp.month; + tm.tm_mday = timestamp.day; + tm.tm_hour = timestamp.hour; + tm.tm_min = timestamp.minute; + tm.tm_sec = timestamp.second; + new_time = rtc_mktime(&tm); + + if (!efi_secure_boot_enabled()) { + /* finished checking */ + *time = new_time; + return EFI_SUCCESS; + } + + if (new_time <= *time) + goto err; + + /* data to be digested */ + regs = calloc(sizeof(*regs) + sizeof(struct image_region) * 5, 1); + if (!regs) + goto err; + regs->max = 5; + efi_image_region_add(regs, (uint8_t *)variable, + (uint8_t *)variable + + u16_strlen(variable) * sizeof(u16), 1); + efi_image_region_add(regs, (uint8_t *)vendor, + (uint8_t *)vendor + sizeof(*vendor), 1); + efi_image_region_add(regs, (uint8_t *)&given_attr, + (uint8_t *)&given_attr + sizeof(given_attr), 1); + efi_image_region_add(regs, (uint8_t *)×tamp, + (uint8_t *)×tamp + sizeof(timestamp), 1); + efi_image_region_add(regs, (uint8_t *)*data, + (uint8_t *)*data + *data_size, 1); + + /* variable's signature list */ + if (auth->auth_info.hdr.dwLength < sizeof(auth->auth_info)) + goto err; + var_sig = efi_variable_parse_signature(auth->auth_info.cert_data, + auth->auth_info.hdr.dwLength + - sizeof(auth->auth_info)); + if (IS_ERR(var_sig)) { + debug("Parsing variable's signature failed\n"); + var_sig = NULL; + goto err; + } + + /* signature database used for authentication */ + if (u16_strcmp(variable, L"PK") == 0 || + u16_strcmp(variable, L"KEK") == 0) { + /* with PK */ + truststore = efi_sigstore_parse_sigdb(L"PK"); + if (!truststore) + goto err; + } else if (u16_strcmp(variable, L"db") == 0 || + u16_strcmp(variable, L"dbx") == 0) { + /* with PK and KEK */ + truststore = efi_sigstore_parse_sigdb(L"KEK"); + truststore2 = efi_sigstore_parse_sigdb(L"PK"); + + if (!truststore) { + if (!truststore2) + goto err; + + truststore = truststore2; + truststore2 = NULL; + } + } else { + /* TODO: support private authenticated variables */ + goto err; + } + + /* verify signature */ + if (efi_signature_verify_with_sigdb(regs, var_sig, truststore, NULL)) { + debug("Verified\n"); + } else { + if (truststore2 && + efi_signature_verify_with_sigdb(regs, var_sig, + truststore2, NULL)) { + debug("Verified\n"); + } else { + debug("Verifying variable's signature failed\n"); + goto err; + } + } + + /* finished checking */ + *time = rtc_mktime(&tm); + ret = EFI_SUCCESS; + +err: + efi_sigstore_free(truststore); + efi_sigstore_free(truststore2); + pkcs7_free_message(var_sig); + free(regs); + + return ret; +} +#else +static efi_status_t efi_variable_authenticate(u16 *variable, + const efi_guid_t *vendor, + efi_uintn_t *data_size, + const void **data, u32 given_attr, + u32 *env_attr, u64 *time) +{ + return EFI_SUCCESS; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ + +static +efi_status_t EFIAPI efi_get_variable_common(u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes, + efi_uintn_t *data_size, void *data, + bool is_non_volatile) { char *native_name; efi_status_t ret; unsigned long in_size; - const char *val, *s; + const char *val = NULL, *s; + u64 time = 0; u32 attr; - EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes, - data_size, data); - if (!variable_name || !vendor || !data_size) return EFI_EXIT(EFI_INVALID_PARAMETER); ret = efi_to_native(&native_name, variable_name, vendor); if (ret) - return EFI_EXIT(ret); + return ret; EFI_PRINT("get '%s'\n", native_name); val = env_get(native_name); free(native_name); if (!val) - return EFI_EXIT(EFI_NOT_FOUND); + return EFI_NOT_FOUND; - val = parse_attr(val, &attr); + val = parse_attr(val, &attr, &time); in_size = *data_size; @@ -198,7 +454,7 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, /* number of hexadecimal digits must be even */ if (len & 1) - return EFI_EXIT(EFI_DEVICE_ERROR); + return EFI_DEVICE_ERROR; /* two characters per byte: */ len /= 2; @@ -209,11 +465,13 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, goto out; } - if (!data) - return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!data) { + debug("Variable with no data shouldn't exist.\n"); + return EFI_INVALID_PARAMETER; + } if (hex2bin(data, s, len)) - return EFI_EXIT(EFI_DEVICE_ERROR); + return EFI_DEVICE_ERROR; EFI_PRINT("got value: \"%s\"\n", s); } else if ((s = prefix(val, "(utf8)"))) { @@ -226,8 +484,10 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, goto out; } - if (!data) - return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!data) { + debug("Variable with no data shouldn't exist.\n"); + return EFI_INVALID_PARAMETER; + } memcpy(data, s, len); ((char *)data)[len] = '\0'; @@ -235,13 +495,67 @@ efi_status_t EFIAPI efi_get_variable(u16 *variable_name, EFI_PRINT("got value: \"%s\"\n", (char *)data); } else { EFI_PRINT("invalid value: '%s'\n", val); - return EFI_EXIT(EFI_DEVICE_ERROR); + return EFI_DEVICE_ERROR; } out: if (attributes) *attributes = attr & EFI_VARIABLE_MASK; + return ret; +} + +static +efi_status_t EFIAPI efi_get_volatile_variable(u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes, + efi_uintn_t *data_size, + void *data) +{ + return efi_get_variable_common(variable_name, vendor, attributes, + data_size, data, false); +} + +efi_status_t EFIAPI efi_get_nonvolatile_variable(u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes, + efi_uintn_t *data_size, + void *data) +{ + return efi_get_variable_common(variable_name, vendor, attributes, + data_size, data, true); +} + +/** + * efi_efi_get_variable() - retrieve value of a UEFI variable + * + * This function implements the GetVariable runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer to which the variable value is copied + * @data: buffer to which the variable value is copied + * Return: status code + */ +efi_status_t EFIAPI efi_get_variable(u16 *variable_name, + const efi_guid_t *vendor, u32 *attributes, + efi_uintn_t *data_size, void *data) +{ + efi_status_t ret; + + EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes, + data_size, data); + + ret = efi_get_volatile_variable(variable_name, vendor, attributes, + data_size, data); + if (ret == EFI_NOT_FOUND) + ret = efi_get_nonvolatile_variable(variable_name, vendor, + attributes, data_size, data); + return EFI_EXIT(ret); } @@ -275,6 +589,7 @@ static efi_status_t parse_uboot_variable(char *variable, char *guid, *name, *end, c; size_t name_len; efi_uintn_t old_variable_name_size; + u64 time; u16 *p; guid = strchr(variable, '_'); @@ -309,7 +624,7 @@ static efi_status_t parse_uboot_variable(char *variable, *(name - 1) = c; /* attributes */ - parse_attr(end, attributes); + parse_attr(end, attributes, &time); return EFI_SUCCESS; } @@ -391,7 +706,7 @@ efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, list_len = hexport_r(&env_htab, '\n', H_MATCH_REGEX | H_MATCH_KEY, &efi_variables_list, 0, 1, regexlist); - /* 1 indicates that no match was found */ + if (list_len <= 1) return EFI_EXIT(EFI_NOT_FOUND); @@ -404,143 +719,286 @@ efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, return EFI_EXIT(ret); } -/** - * efi_set_variable() - set value of a UEFI variable - * - * This function implements the SetVariable runtime service. - * - * See the Unified Extensible Firmware Interface (UEFI) specification for - * details. - * - * @variable_name: name of the variable - * @vendor: vendor GUID - * @attributes: attributes of the variable - * @data_size: size of the buffer with the variable value - * @data: buffer with the variable value - * Return: status code - */ -efi_status_t EFIAPI efi_set_variable(u16 *variable_name, - const efi_guid_t *vendor, u32 attributes, - efi_uintn_t data_size, const void *data) +static +efi_status_t EFIAPI efi_set_variable_common(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check, + bool is_non_volatile) { - char *native_name = NULL, *val = NULL, *s; - const char *old_val; - size_t old_size; - efi_status_t ret = EFI_SUCCESS; + char *native_name = NULL, *old_data = NULL, *val = NULL, *s; + efi_uintn_t old_size; + bool append, delete; + u64 time = 0; u32 attr; + efi_status_t ret = EFI_SUCCESS; - EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes, - data_size, data); + debug("%s: set '%s'\n", __func__, native_name); if (!variable_name || !*variable_name || !vendor || ((attributes & EFI_VARIABLE_RUNTIME_ACCESS) && !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) { ret = EFI_INVALID_PARAMETER; - goto out; + goto err; } ret = efi_to_native(&native_name, variable_name, vendor); if (ret) - goto out; + goto err; + + /* check if a variable exists */ + old_size = 0; + attr = 0; + ret = EFI_CALL(efi_get_variable(variable_name, vendor, &attr, + &old_size, NULL)); + if (ret == EFI_BUFFER_TOO_SMALL) { + if ((is_non_volatile && !(attr & EFI_VARIABLE_NON_VOLATILE)) || + (!is_non_volatile && (attr & EFI_VARIABLE_NON_VOLATILE))) { + ret = EFI_INVALID_PARAMETER; + goto err; + } + } - old_val = env_get(native_name); - if (old_val) { - old_val = parse_attr(old_val, &attr); + append = !!(attributes & EFI_VARIABLE_APPEND_WRITE); + attributes &= ~(u32)EFI_VARIABLE_APPEND_WRITE; + delete = !append && (!data_size || !attributes); - /* check read-only first */ - if (attr & READ_ONLY) { + /* check attributes */ + if (old_size) { + if (ro_check && (attr & READ_ONLY)) { ret = EFI_WRITE_PROTECTED; - goto out; - } - - if ((data_size == 0 && - !(attributes & EFI_VARIABLE_APPEND_WRITE)) || - !attributes) { - /* delete the variable: */ - env_set(native_name, NULL); - ret = EFI_SUCCESS; - goto out; + goto err; } /* attributes won't be changed */ - if (attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) { + if (!delete && + ((ro_check && attr != attributes) || + (!ro_check && ((attr & ~(u32)READ_ONLY) + != (attributes & ~(u32)READ_ONLY))))) { ret = EFI_INVALID_PARAMETER; - goto out; - } - - if (attributes & EFI_VARIABLE_APPEND_WRITE) { - if (!prefix(old_val, "(blob)")) { - ret = EFI_DEVICE_ERROR; - goto out; - } - old_size = strlen(old_val); - } else { - old_size = 0; + goto err; } } else { - if (data_size == 0 || !attributes || - (attributes & EFI_VARIABLE_APPEND_WRITE)) { + if (delete || append) { /* * Trying to delete or to update a non-existent * variable. */ ret = EFI_NOT_FOUND; - goto out; + goto err; + } + } + + if (((!u16_strcmp(variable_name, L"PK") || + !u16_strcmp(variable_name, L"KEK")) && + !guidcmp(vendor, &efi_global_variable_guid)) || + ((!u16_strcmp(variable_name, L"db") || + !u16_strcmp(variable_name, L"dbx")) && + !guidcmp(vendor, &efi_guid_image_security_database))) { + /* authentication is mandatory */ + if (!(attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) { + debug("%ls: AUTHENTICATED_WRITE_ACCESS required\n", + variable_name); + ret = EFI_INVALID_PARAMETER; + goto err; } + } + + /* authenticate a variable */ + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) { + if (attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) { + ret = EFI_INVALID_PARAMETER; + goto err; + } + if (attributes & + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + ret = efi_variable_authenticate(variable_name, vendor, + &data_size, &data, + attributes, &attr, + &time); + if (ret != EFI_SUCCESS) + goto err; + + /* last chance to check for delete */ + if (!data_size) + delete = true; + } + } else { + if (attributes & + (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) { + debug("Secure boot is not configured\n"); + ret = EFI_INVALID_PARAMETER; + goto err; + } + } + + /* delete a variable */ + if (delete) { + /* !old_size case has been handled before */ + val = NULL; + ret = EFI_SUCCESS; + goto out; + } + if (append) { + old_data = malloc(old_size); + if (!old_data) { + return EFI_OUT_OF_RESOURCES; + goto err; + } + ret = EFI_CALL(efi_get_variable(variable_name, vendor, + &attr, &old_size, old_data)); + if (ret != EFI_SUCCESS) + goto err; + } else { old_size = 0; } - val = malloc(old_size + 2 * data_size - + strlen("{ro,run,boot,nv}(blob)") + 1); + val = malloc(2 * old_size + 2 * data_size + + strlen("{ro,run,boot,nv,time=0123456701234567}(blob)") + + 1); if (!val) { ret = EFI_OUT_OF_RESOURCES; - goto out; + goto err; } s = val; - /* store attributes */ - attributes &= (EFI_VARIABLE_NON_VOLATILE | + /* + * store attributes + */ + attributes &= (READ_ONLY | + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | - EFI_VARIABLE_RUNTIME_ACCESS); + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS); s += sprintf(s, "{"); while (attributes) { - u32 attr = 1 << (ffs(attributes) - 1); + attr = 1 << (ffs(attributes) - 1); - if (attr == EFI_VARIABLE_NON_VOLATILE) + if (attr == READ_ONLY) { + s += sprintf(s, "ro"); + } else if (attr == EFI_VARIABLE_NON_VOLATILE) { s += sprintf(s, "nv"); - else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) + } else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) { s += sprintf(s, "boot"); - else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) + } else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) { s += sprintf(s, "run"); + } else if (attr == + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) { + s += sprintf(s, "time="); + s = bin2hex(s, (u8 *)&time, sizeof(time)); + } attributes &= ~attr; if (attributes) s += sprintf(s, ","); } s += sprintf(s, "}"); - - if (old_size) - /* APPEND_WRITE */ - s += sprintf(s, old_val); - else - s += sprintf(s, "(blob)"); + s += sprintf(s, "(blob)"); /* store payload: */ + if (append) + s = bin2hex(s, old_data, old_size); s = bin2hex(s, data, data_size); *s = '\0'; EFI_PRINT("setting: %s=%s\n", native_name, val); +out: if (env_set(native_name, val)) ret = EFI_DEVICE_ERROR; + else + ret = EFI_SUCCESS; -out: +err: free(native_name); + free(old_data); free(val); - return EFI_EXIT(ret); + return ret; +} + +static +efi_status_t EFIAPI efi_set_volatile_variable(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check) +{ + return efi_set_variable_common(variable_name, vendor, attributes, + data_size, data, ro_check, false); +} + +efi_status_t EFIAPI efi_set_nonvolatile_variable(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check) +{ + efi_status_t ret; + + ret = efi_set_variable_common(variable_name, vendor, attributes, + data_size, data, ro_check, true); + + return ret; +} + +static efi_status_t efi_set_variable_internal(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check) +{ + efi_status_t ret; + + if (attributes & EFI_VARIABLE_NON_VOLATILE) + ret = efi_set_nonvolatile_variable(variable_name, vendor, + attributes, + data_size, data, ro_check); + else + ret = efi_set_volatile_variable(variable_name, vendor, + attributes, data_size, data, + ro_check); + + return ret; +} + +/** + * efi_set_variable() - set value of a UEFI variable + * + * This function implements the SetVariable runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +efi_status_t EFIAPI efi_set_variable(u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + efi_uintn_t data_size, const void *data) +{ + EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes, + data_size, data); + + /* READ_ONLY bit is not part of API */ + attributes &= ~(u32)READ_ONLY; + + return EFI_EXIT(efi_set_variable_internal(variable_name, vendor, + attributes, data_size, data, + true)); } /** -- cgit v1.3.1 From 434ffb642240132ab6ecc04c3e8804b8693ace75 Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:42 +0900 Subject: efi_loader: variable: add secure boot state transition UEFI specification defines several global variables which are related to the current secure boot state. In this commit, those values will be maintained according to operations. Currently, AuditMode and DeployedMode are defined but not implemented. Signed-off-by: AKASHI Takahiro --- lib/efi_loader/efi_variable.c | 231 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 3 deletions(-) (limited to 'lib/efi_loader') diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index adb78470f2d..fd5c41f830b 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -16,8 +16,16 @@ #include #include "../lib/crypto/pkcs7_parser.h" +enum efi_secure_mode { + EFI_MODE_SETUP, + EFI_MODE_USER, + EFI_MODE_AUDIT, + EFI_MODE_DEPLOYED, +}; + const efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID; static bool efi_secure_boot; +static int efi_secure_mode; #define READ_ONLY BIT(31) @@ -160,6 +168,210 @@ static const char *parse_attr(const char *str, u32 *attrp, u64 *timep) return str; } +static efi_status_t efi_set_variable_internal(u16 *variable_name, + const efi_guid_t *vendor, + u32 attributes, + efi_uintn_t data_size, + const void *data, + bool ro_check); + +/** + * efi_transfer_secure_state - handle a secure boot state transition + * @mode: new state + * + * Depending on @mode, secure boot related variables are updated. + * Those variables are *read-only* for users, efi_set_variable_internal() + * is called here. + * + * Return: EFI_SUCCESS on success, status code (negative) on error + */ +static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode) +{ + u32 attributes; + u8 val; + efi_status_t ret; + + debug("Secure state from %d to %d\n", efi_secure_mode, mode); + + attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + if (mode == EFI_MODE_DEPLOYED) { + val = 1; + ret = efi_set_variable_internal(L"SecureBoot", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"SetupMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"AuditMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 1; + ret = efi_set_variable_internal(L"DeployedMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + + efi_secure_boot = true; + } else if (mode == EFI_MODE_AUDIT) { + ret = efi_set_variable_internal(L"PK", + &efi_global_variable_guid, + attributes, + 0, NULL, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"SecureBoot", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 1; + ret = efi_set_variable_internal(L"SetupMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 1; + ret = efi_set_variable_internal(L"AuditMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"DeployedMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + + efi_secure_boot = true; + } else if (mode == EFI_MODE_USER) { + val = 1; + ret = efi_set_variable_internal(L"SecureBoot", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"SetupMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"AuditMode", + &efi_global_variable_guid, + attributes, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"DeployedMode", + &efi_global_variable_guid, + attributes, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + + efi_secure_boot = true; + } else if (mode == EFI_MODE_SETUP) { + val = 0; + ret = efi_set_variable_internal(L"SecureBoot", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 1; + ret = efi_set_variable_internal(L"SetupMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"AuditMode", + &efi_global_variable_guid, + attributes, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + val = 0; + ret = efi_set_variable_internal(L"DeployedMode", + &efi_global_variable_guid, + attributes | READ_ONLY, + sizeof(val), &val, + false); + if (ret != EFI_SUCCESS) + goto err; + } else { + return EFI_INVALID_PARAMETER; + } + + return EFI_SUCCESS; + +err: + /* TODO: What action should be taken here? */ + printf("ERROR: Secure state transition failed\n"); + return ret; +} + +/** + * efi_init_secure_state - initialize secure boot state + * + * Return: EFI_SUCCESS on success, status code (negative) on error + */ +static efi_status_t efi_init_secure_state(void) +{ + efi_uintn_t size = 0; + efi_status_t ret; + + ret = EFI_CALL(efi_get_variable(L"PK", &efi_global_variable_guid, + NULL, &size, NULL)); + if (ret == EFI_BUFFER_TOO_SMALL && IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) + ret = efi_transfer_secure_state(EFI_MODE_USER); + else + ret = efi_transfer_secure_state(EFI_MODE_SETUP); + + return ret; +} + /** * efi_secure_boot_enabled - return if secure boot is enabled or not * @@ -910,10 +1122,19 @@ efi_status_t EFIAPI efi_set_variable_common(u16 *variable_name, EFI_PRINT("setting: %s=%s\n", native_name, val); out: - if (env_set(native_name, val)) + if (env_set(native_name, val)) { ret = EFI_DEVICE_ERROR; - else + } else { + if ((u16_strcmp(variable_name, L"PK") == 0 && + guidcmp(vendor, &efi_global_variable_guid) == 0)) { + ret = efi_transfer_secure_state( + (delete ? EFI_MODE_SETUP : + EFI_MODE_USER)); + if (ret != EFI_SUCCESS) + goto err; + } ret = EFI_SUCCESS; + } err: free(native_name); @@ -1098,5 +1319,9 @@ void efi_variables_boot_exit_notify(void) */ efi_status_t efi_init_variables(void) { - return EFI_SUCCESS; + efi_status_t ret; + + ret = efi_init_secure_state(); + + return ret; } -- cgit v1.3.1 From 8353516f4a7e31ee655050033b65218c9733c65a Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:43 +0900 Subject: efi_loader: variable: add VendorKeys variable The following variable is exported as UEFI specification defines: VendorKeys: whether the system is configured to use only vendor-provided keys or not The value will have to be modified if a platform has its own way of initializing signature database, in particular, PK. Signed-off-by: AKASHI Takahiro --- lib/efi_loader/efi_variable.c | 69 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 6 deletions(-) (limited to 'lib/efi_loader') diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index fd5c41f830b..7df881a74b4 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -26,6 +26,7 @@ enum efi_secure_mode { const efi_guid_t efi_guid_cert_type_pkcs7 = EFI_CERT_TYPE_PKCS7_GUID; static bool efi_secure_boot; static int efi_secure_mode; +static u8 efi_vendor_keys; #define READ_ONLY BIT(31) @@ -344,6 +345,8 @@ static efi_status_t efi_transfer_secure_state(enum efi_secure_mode mode) return EFI_INVALID_PARAMETER; } + efi_secure_mode = mode; + return EFI_SUCCESS; err: @@ -359,16 +362,46 @@ err: */ static efi_status_t efi_init_secure_state(void) { - efi_uintn_t size = 0; + enum efi_secure_mode mode; + efi_uintn_t size; efi_status_t ret; + /* + * TODO: + * Since there is currently no "platform-specific" installation + * method of Platform Key, we can't say if VendorKeys is 0 or 1 + * precisely. + */ + + size = 0; ret = EFI_CALL(efi_get_variable(L"PK", &efi_global_variable_guid, NULL, &size, NULL)); - if (ret == EFI_BUFFER_TOO_SMALL && IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) - ret = efi_transfer_secure_state(EFI_MODE_USER); - else - ret = efi_transfer_secure_state(EFI_MODE_SETUP); + if (ret == EFI_BUFFER_TOO_SMALL) { + if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT)) + mode = EFI_MODE_USER; + else + mode = EFI_MODE_SETUP; + + efi_vendor_keys = 0; + } else if (ret == EFI_NOT_FOUND) { + mode = EFI_MODE_SETUP; + efi_vendor_keys = 1; + } else { + goto err; + } + ret = efi_transfer_secure_state(mode); + if (ret == EFI_SUCCESS) + ret = efi_set_variable_internal(L"VendorKeys", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_RUNTIME_ACCESS + | READ_ONLY, + sizeof(efi_vendor_keys), + &efi_vendor_keys, + false); + +err: return ret; } @@ -1125,6 +1158,8 @@ out: if (env_set(native_name, val)) { ret = EFI_DEVICE_ERROR; } else { + bool vendor_keys_modified = false; + if ((u16_strcmp(variable_name, L"PK") == 0 && guidcmp(vendor, &efi_global_variable_guid) == 0)) { ret = efi_transfer_secure_state( @@ -1132,8 +1167,30 @@ out: EFI_MODE_USER)); if (ret != EFI_SUCCESS) goto err; + + if (efi_secure_mode != EFI_MODE_SETUP) + vendor_keys_modified = true; + } else if ((u16_strcmp(variable_name, L"KEK") == 0 && + guidcmp(vendor, &efi_global_variable_guid) == 0)) { + if (efi_secure_mode != EFI_MODE_SETUP) + vendor_keys_modified = true; + } + + /* update VendorKeys */ + if (vendor_keys_modified & efi_vendor_keys) { + efi_vendor_keys = 0; + ret = efi_set_variable_internal( + L"VendorKeys", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_RUNTIME_ACCESS + | READ_ONLY, + sizeof(efi_vendor_keys), + &efi_vendor_keys, + false); + } else { + ret = EFI_SUCCESS; } - ret = EFI_SUCCESS; } err: -- cgit v1.3.1 From 4540dabdcacaea50bf874115f28adc103966d25a Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:44 +0900 Subject: efi_loader: image_loader: support image authentication With this commit, image validation can be enforced, as UEFI specification section 32.5 describes, if CONFIG_EFI_SECURE_BOOT is enabled. Currently we support * authentication based on db and dbx, so dbx-validated image will always be rejected. * following signature types: EFI_CERT_SHA256_GUID (SHA256 digest for unsigned images) EFI_CERT_X509_GUID (x509 certificate for signed images) Timestamp-based certificate revocation is not supported here. Internally, authentication data is stored in one of certificates tables of PE image (See efi_image_parse()) and will be verified by efi_image_authenticate() before loading a given image. It seems that UEFI specification defines the verification process in a bit ambiguous way. I tried to implement it as closely to as EDK2 does. Signed-off-by: AKASHI Takahiro --- include/efi_loader.h | 13 +- lib/efi_loader/efi_boottime.c | 10 +- lib/efi_loader/efi_image_loader.c | 462 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 469 insertions(+), 16 deletions(-) (limited to 'lib/efi_loader') diff --git a/include/efi_loader.h b/include/efi_loader.h index ef8d184a6ee..0ba9a1f702a 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -11,6 +11,7 @@ #include #include #include +#include static inline int guidcmp(const void *g1, const void *g2) { @@ -263,6 +264,11 @@ struct efi_object { enum efi_object_type type; }; +enum efi_image_auth_status { + EFI_IMAGE_AUTH_FAILED = 0, + EFI_IMAGE_AUTH_PASSED, +}; + /** * struct efi_loaded_image_obj - handle of a loaded image * @@ -282,6 +288,7 @@ struct efi_loaded_image_obj { EFIAPI efi_status_t (*entry)(efi_handle_t image_handle, struct efi_system_table *st); u16 image_type; + enum efi_image_auth_status auth_status; }; /** @@ -415,7 +422,8 @@ efi_status_t efi_set_watchdog(unsigned long timeout); /* Called from places to check whether a timer expired */ void efi_timer_check(void); /* PE loader implementation */ -efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, +efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, + void *efi, size_t efi_size, struct efi_loaded_image *loaded_image_info); /* Called once to store the pristine gd pointer */ void efi_save_gd(void); @@ -756,6 +764,9 @@ void efi_sigstore_free(struct efi_signature_store *sigstore); struct efi_signature_store *efi_sigstore_parse_sigdb(u16 *name); bool efi_secure_boot_enabled(void); + +bool efi_image_parse(void *efi, size_t len, struct efi_image_regions **regp, + WIN_CERTIFICATE **auth, size_t *auth_len); #endif /* CONFIG_EFI_SECURE_BOOT */ #else /* CONFIG_IS_ENABLED(EFI_LOADER) */ diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 3b79a88a480..a3f11eaf622 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -1882,12 +1882,12 @@ efi_status_t EFIAPI efi_load_image(bool boot_policy, efi_dp_split_file_path(file_path, &dp, &fp); ret = efi_setup_loaded_image(dp, fp, image_obj, &info); if (ret == EFI_SUCCESS) - ret = efi_load_pe(*image_obj, dest_buffer, info); + ret = efi_load_pe(*image_obj, dest_buffer, source_size, info); if (!source_buffer) /* Release buffer to which file was loaded */ efi_free_pages((uintptr_t)dest_buffer, efi_size_in_pages(source_size)); - if (ret == EFI_SUCCESS) { + if (ret == EFI_SUCCESS || ret == EFI_SECURITY_VIOLATION) { info->system_table = &systab; info->parent_handle = parent_image; } else { @@ -2885,10 +2885,16 @@ efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle, EFI_ENTRY("%p, %p, %p", image_handle, exit_data_size, exit_data); + if (!efi_search_obj(image_handle)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + /* Check parameters */ if (image_obj->header.type != EFI_OBJECT_TYPE_LOADED_IMAGE) return EFI_EXIT(EFI_INVALID_PARAMETER); + if (image_obj->auth_status != EFI_IMAGE_AUTH_PASSED) + return EFI_EXIT(EFI_SECURITY_VIOLATION); + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, &info, NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL)); diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c index d5de6df16d8..6c270ce94f4 100644 --- a/lib/efi_loader/efi_image_loader.c +++ b/lib/efi_loader/efi_image_loader.c @@ -10,7 +10,10 @@ #include #include #include +#include #include +#include +#include "../lib/crypto/pkcs7_parser.h" const efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID; const efi_guid_t efi_guid_device_path = EFI_DEVICE_PATH_PROTOCOL_GUID; @@ -206,6 +209,386 @@ static void efi_set_code_and_data_type( } } +#ifdef CONFIG_EFI_SECURE_BOOT +/** + * cmp_pe_section - compare two sections + * @arg1: Pointer to pointer to first section + * @arg2: Pointer to pointer to second section + * + * Compare two sections in PE image. + * + * Return: -1, 0, 1 respectively if arg1 < arg2, arg1 == arg2 or + * arg1 > arg2 + */ +static int cmp_pe_section(const void *arg1, const void *arg2) +{ + const IMAGE_SECTION_HEADER *section1, *section2; + + section1 = *((const IMAGE_SECTION_HEADER **)arg1); + section2 = *((const IMAGE_SECTION_HEADER **)arg2); + + if (section1->VirtualAddress < section2->VirtualAddress) + return -1; + else if (section1->VirtualAddress == section2->VirtualAddress) + return 0; + else + return 1; +} + +/** + * efi_image_parse - parse a PE image + * @efi: Pointer to image + * @len: Size of @efi + * @regp: Pointer to a list of regions + * @auth: Pointer to a pointer to authentication data in PE + * @auth_len: Size of @auth + * + * Parse image binary in PE32(+) format, assuming that sanity of PE image + * has been checked by a caller. + * On success, an address of authentication data in @efi and its size will + * be returned in @auth and @auth_len, respectively. + * + * Return: true on success, false on error + */ +bool efi_image_parse(void *efi, size_t len, struct efi_image_regions **regp, + WIN_CERTIFICATE **auth, size_t *auth_len) +{ + struct efi_image_regions *regs; + IMAGE_DOS_HEADER *dos; + IMAGE_NT_HEADERS32 *nt; + IMAGE_SECTION_HEADER *sections, **sorted; + int num_regions, num_sections, i; + int ctidx = IMAGE_DIRECTORY_ENTRY_SECURITY; + u32 align, size, authsz, authoff; + size_t bytes_hashed; + + dos = (void *)efi; + nt = (void *)(efi + dos->e_lfanew); + + /* + * Count maximum number of regions to be digested. + * We don't have to have an exact number here. + * See efi_image_region_add()'s in parsing below. + */ + num_regions = 3; /* for header */ + num_regions += nt->FileHeader.NumberOfSections; + num_regions++; /* for extra */ + + regs = calloc(sizeof(*regs) + sizeof(struct image_region) * num_regions, + 1); + if (!regs) + goto err; + regs->max = num_regions; + + /* + * Collect data regions for hash calculation + * 1. File headers + */ + if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + IMAGE_NT_HEADERS64 *nt64 = (void *)nt; + IMAGE_OPTIONAL_HEADER64 *opt = &nt64->OptionalHeader; + + /* Skip CheckSum */ + efi_image_region_add(regs, efi, &opt->CheckSum, 0); + if (nt64->OptionalHeader.NumberOfRvaAndSizes <= ctidx) { + efi_image_region_add(regs, + &opt->CheckSum + 1, + efi + opt->SizeOfHeaders, 0); + } else { + /* Skip Certificates Table */ + efi_image_region_add(regs, + &opt->CheckSum + 1, + &opt->DataDirectory[ctidx], 0); + efi_image_region_add(regs, + &opt->DataDirectory[ctidx] + 1, + efi + opt->SizeOfHeaders, 0); + } + + bytes_hashed = opt->SizeOfHeaders; + align = opt->FileAlignment; + authoff = opt->DataDirectory[ctidx].VirtualAddress; + authsz = opt->DataDirectory[ctidx].Size; + } else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader; + + efi_image_region_add(regs, efi, &opt->CheckSum, 0); + efi_image_region_add(regs, &opt->CheckSum + 1, + &opt->DataDirectory[ctidx], 0); + efi_image_region_add(regs, &opt->DataDirectory[ctidx] + 1, + efi + opt->SizeOfHeaders, 0); + + bytes_hashed = opt->SizeOfHeaders; + align = opt->FileAlignment; + authoff = opt->DataDirectory[ctidx].VirtualAddress; + authsz = opt->DataDirectory[ctidx].Size; + } else { + debug("%s: Invalid optional header magic %x\n", __func__, + nt->OptionalHeader.Magic); + goto err; + } + + /* 2. Sections */ + num_sections = nt->FileHeader.NumberOfSections; + sections = (void *)((uint8_t *)&nt->OptionalHeader + + nt->FileHeader.SizeOfOptionalHeader); + sorted = calloc(sizeof(IMAGE_SECTION_HEADER *), num_sections); + if (!sorted) { + debug("%s: Out of memory\n", __func__); + goto err; + } + + /* + * Make sure the section list is in ascending order. + */ + for (i = 0; i < num_sections; i++) + sorted[i] = §ions[i]; + qsort(sorted, num_sections, sizeof(sorted[0]), cmp_pe_section); + + for (i = 0; i < num_sections; i++) { + if (!sorted[i]->SizeOfRawData) + continue; + + size = (sorted[i]->SizeOfRawData + align - 1) & ~(align - 1); + efi_image_region_add(regs, efi + sorted[i]->PointerToRawData, + efi + sorted[i]->PointerToRawData + size, + 0); + debug("section[%d](%s): raw: 0x%x-0x%x, virt: %x-%x\n", + i, sorted[i]->Name, + sorted[i]->PointerToRawData, + sorted[i]->PointerToRawData + size, + sorted[i]->VirtualAddress, + sorted[i]->VirtualAddress + + sorted[i]->Misc.VirtualSize); + + bytes_hashed += size; + } + free(sorted); + + /* 3. Extra data excluding Certificates Table */ + if (bytes_hashed + authsz < len) { + debug("extra data for hash: %lu\n", + len - (bytes_hashed + authsz)); + efi_image_region_add(regs, efi + bytes_hashed, + efi + len - authsz, 0); + } + + /* Return Certificates Table */ + if (authsz) { + if (len < authoff + authsz) { + debug("%s: Size for auth too large: %u >= %zu\n", + __func__, authsz, len - authoff); + goto err; + } + if (authsz < sizeof(*auth)) { + debug("%s: Size for auth too small: %u < %zu\n", + __func__, authsz, sizeof(*auth)); + goto err; + } + *auth = efi + authoff; + *auth_len = authsz; + debug("WIN_CERTIFICATE: 0x%x, size: 0x%x\n", authoff, authsz); + } else { + *auth = NULL; + *auth_len = 0; + } + + *regp = regs; + + return true; + +err: + free(regs); + + return false; +} + +/** + * efi_image_unsigned_authenticate - authenticate unsigned image with + * SHA256 hash + * @regs: List of regions to be verified + * + * If an image is not signed, it doesn't have a signature. In this case, + * its message digest is calculated and it will be compared with one of + * hash values stored in signature databases. + * + * Return: true if authenticated, false if not + */ +static bool efi_image_unsigned_authenticate(struct efi_image_regions *regs) +{ + struct efi_signature_store *db = NULL, *dbx = NULL; + bool ret = false; + + dbx = efi_sigstore_parse_sigdb(L"dbx"); + if (!dbx) { + debug("Getting signature database(dbx) failed\n"); + goto out; + } + + db = efi_sigstore_parse_sigdb(L"db"); + if (!db) { + debug("Getting signature database(db) failed\n"); + goto out; + } + + /* try black-list first */ + if (efi_signature_verify_with_sigdb(regs, NULL, dbx, NULL)) { + debug("Image is not signed and rejected by \"dbx\"\n"); + goto out; + } + + /* try white-list */ + if (efi_signature_verify_with_sigdb(regs, NULL, db, NULL)) + ret = true; + else + debug("Image is not signed and not found in \"db\" or \"dbx\"\n"); + +out: + efi_sigstore_free(db); + efi_sigstore_free(dbx); + + return ret; +} + +/** + * efi_image_authenticate - verify a signature of signed image + * @efi: Pointer to image + * @efi_size: Size of @efi + * + * A signed image should have its signature stored in a table of its PE header. + * So if an image is signed and only if if its signature is verified using + * signature databases, an image is authenticated. + * If an image is not signed, its validity is checked by using + * efi_image_unsigned_authenticated(). + * TODO: + * When AuditMode==0, if the image's signature is not found in + * the authorized database, or is found in the forbidden database, + * the image will not be started and instead, information about it + * will be placed in this table. + * When AuditMode==1, an EFI_IMAGE_EXECUTION_INFO element is created + * in the EFI_IMAGE_EXECUTION_INFO_TABLE for every certificate found + * in the certificate table of every image that is validated. + * + * Return: true if authenticated, false if not + */ +static bool efi_image_authenticate(void *efi, size_t efi_size) +{ + struct efi_image_regions *regs = NULL; + WIN_CERTIFICATE *wincerts = NULL, *wincert; + size_t wincerts_len; + struct pkcs7_message *msg = NULL; + struct efi_signature_store *db = NULL, *dbx = NULL; + struct x509_certificate *cert = NULL; + void *new_efi = NULL; + size_t new_efi_size; + bool ret = false; + + if (!efi_secure_boot_enabled()) + return true; + + /* + * Size must be 8-byte aligned and the trailing bytes must be + * zero'ed. Otherwise hash value may be incorrect. + */ + if (efi_size & 0x7) { + new_efi_size = (efi_size + 0x7) & ~0x7ULL; + new_efi = calloc(new_efi_size, 1); + if (!new_efi) + return false; + memcpy(new_efi, efi, efi_size); + efi = new_efi; + efi_size = new_efi_size; + } + + if (!efi_image_parse(efi, efi_size, ®s, &wincerts, + &wincerts_len)) { + debug("Parsing PE executable image failed\n"); + goto err; + } + + if (!wincerts) { + /* The image is not signed */ + ret = efi_image_unsigned_authenticate(regs); + + goto err; + } + + /* + * verify signature using db and dbx + */ + db = efi_sigstore_parse_sigdb(L"db"); + if (!db) { + debug("Getting signature database(db) failed\n"); + goto err; + } + + dbx = efi_sigstore_parse_sigdb(L"dbx"); + if (!dbx) { + debug("Getting signature database(dbx) failed\n"); + goto err; + } + + /* go through WIN_CERTIFICATE list */ + for (wincert = wincerts; + (void *)wincert < (void *)wincerts + wincerts_len; + wincert = (void *)wincert + ALIGN(wincert->dwLength, 8)) { + if (wincert->dwLength < sizeof(*wincert)) { + debug("%s: dwLength too small: %u < %zu\n", + __func__, wincert->dwLength, sizeof(*wincert)); + goto err; + } + msg = pkcs7_parse_message((void *)wincert + sizeof(*wincert), + wincert->dwLength - sizeof(*wincert)); + if (!msg) { + debug("Parsing image's signature failed\n"); + goto err; + } + + /* try black-list first */ + if (efi_signature_verify_with_sigdb(regs, msg, dbx, NULL)) { + debug("Signature was rejected by \"dbx\"\n"); + goto err; + } + + if (!efi_signature_verify_signers(msg, dbx)) { + debug("Signer was rejected by \"dbx\"\n"); + goto err; + } else { + ret = true; + } + + /* try white-list */ + if (!efi_signature_verify_with_sigdb(regs, msg, db, &cert)) { + debug("Verifying signature with \"db\" failed\n"); + goto err; + } else { + ret = true; + } + + if (!efi_signature_verify_cert(cert, dbx)) { + debug("Certificate was rejected by \"dbx\"\n"); + goto err; + } else { + ret = true; + } + } + +err: + x509_free_certificate(cert); + efi_sigstore_free(db); + efi_sigstore_free(dbx); + pkcs7_free_message(msg); + free(regs); + free(new_efi); + + return ret; +} +#else +static bool efi_image_authenticate(void *efi, size_t efi_size) +{ + return true; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ + /** * efi_load_pe() - relocate EFI binary * @@ -214,10 +597,12 @@ static void efi_set_code_and_data_type( * * @handle: loaded image handle * @efi: pointer to the EFI binary + * @efi_size: size of @efi binary * @loaded_image_info: loaded image protocol * Return: status code */ -efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, +efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, + void *efi, size_t efi_size, struct efi_loaded_image *loaded_image_info) { IMAGE_NT_HEADERS32 *nt; @@ -232,17 +617,41 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, uint64_t image_base; unsigned long virt_size = 0; int supported = 0; + efi_status_t ret; + + /* Sanity check for a file header */ + if (efi_size < sizeof(*dos)) { + printf("%s: Truncated DOS Header\n", __func__); + ret = EFI_LOAD_ERROR; + goto err; + } dos = efi; if (dos->e_magic != IMAGE_DOS_SIGNATURE) { printf("%s: Invalid DOS Signature\n", __func__); - return EFI_LOAD_ERROR; + ret = EFI_LOAD_ERROR; + goto err; + } + + /* assume sizeof(IMAGE_NT_HEADERS32) <= sizeof(IMAGE_NT_HEADERS64) */ + if (efi_size < dos->e_lfanew + sizeof(IMAGE_NT_HEADERS32)) { + printf("%s: Invalid offset for Extended Header\n", __func__); + ret = EFI_LOAD_ERROR; + goto err; } nt = (void *) ((char *)efi + dos->e_lfanew); + if ((nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) && + (efi_size < dos->e_lfanew + sizeof(IMAGE_NT_HEADERS64))) { + printf("%s: Invalid offset for Extended Header\n", __func__); + ret = EFI_LOAD_ERROR; + goto err; + } + if (nt->Signature != IMAGE_NT_SIGNATURE) { printf("%s: Invalid NT Signature\n", __func__); - return EFI_LOAD_ERROR; + ret = EFI_LOAD_ERROR; + goto err; } for (i = 0; machines[i]; i++) @@ -254,14 +663,29 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, if (!supported) { printf("%s: Machine type 0x%04x is not supported\n", __func__, nt->FileHeader.Machine); - return EFI_LOAD_ERROR; + ret = EFI_LOAD_ERROR; + goto err; } - /* Calculate upper virtual address boundary */ num_sections = nt->FileHeader.NumberOfSections; sections = (void *)&nt->OptionalHeader + nt->FileHeader.SizeOfOptionalHeader; + if (efi_size < ((void *)sections + sizeof(sections[0]) * num_sections + - efi)) { + printf("%s: Invalid number of sections: %d\n", + __func__, num_sections); + ret = EFI_LOAD_ERROR; + goto err; + } + + /* Authenticate an image */ + if (efi_image_authenticate(efi, efi_size)) + handle->auth_status = EFI_IMAGE_AUTH_PASSED; + else + handle->auth_status = EFI_IMAGE_AUTH_FAILED; + + /* Calculate upper virtual address boundary */ for (i = num_sections - 1; i >= 0; i--) { IMAGE_SECTION_HEADER *sec = §ions[i]; virt_size = max_t(unsigned long, virt_size, @@ -280,7 +704,8 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, if (!efi_reloc) { printf("%s: Could not allocate %lu bytes\n", __func__, virt_size); - return EFI_OUT_OF_RESOURCES; + ret = EFI_OUT_OF_RESOURCES; + goto err; } handle->entry = efi_reloc + opt->AddressOfEntryPoint; rel_size = opt->DataDirectory[rel_idx].Size; @@ -296,7 +721,8 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, if (!efi_reloc) { printf("%s: Could not allocate %lu bytes\n", __func__, virt_size); - return EFI_OUT_OF_RESOURCES; + ret = EFI_OUT_OF_RESOURCES; + goto err; } handle->entry = efi_reloc + opt->AddressOfEntryPoint; rel_size = opt->DataDirectory[rel_idx].Size; @@ -305,13 +731,16 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, } else { printf("%s: Invalid optional header magic %x\n", __func__, nt->OptionalHeader.Magic); - return EFI_LOAD_ERROR; + ret = EFI_LOAD_ERROR; + goto err; } /* Copy PE headers */ - memcpy(efi_reloc, efi, sizeof(*dos) + sizeof(*nt) - + nt->FileHeader.SizeOfOptionalHeader - + num_sections * sizeof(IMAGE_SECTION_HEADER)); + memcpy(efi_reloc, efi, + sizeof(*dos) + + sizeof(*nt) + + nt->FileHeader.SizeOfOptionalHeader + + num_sections * sizeof(IMAGE_SECTION_HEADER)); /* Load sections into RAM */ for (i = num_sections - 1; i >= 0; i--) { @@ -328,7 +757,8 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, (unsigned long)image_base) != EFI_SUCCESS) { efi_free_pages((uintptr_t) efi_reloc, (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT); - return EFI_LOAD_ERROR; + ret = EFI_LOAD_ERROR; + goto err; } /* Flush cache */ @@ -340,5 +770,11 @@ efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, loaded_image_info->image_base = efi_reloc; loaded_image_info->image_size = virt_size; - return EFI_SUCCESS; + if (handle->auth_status == EFI_IMAGE_AUTH_PASSED) + return EFI_SUCCESS; + else + return EFI_SECURITY_VIOLATION; + +err: + return ret; } -- cgit v1.3.1 From d0f07949cc390b06e6ee36a0b5e5a9f70ea3dae1 Mon Sep 17 00:00:00 2001 From: AKASHI Takahiro Date: Tue, 14 Apr 2020 11:51:45 +0900 Subject: efi_loader: set up secure boot The following variable is exported as UEFI specification defines: SignatureSupport: array of GUIDs representing the type of signatures supported by the platform firmware Signed-off-by: AKASHI Takahiro --- lib/efi_loader/efi_setup.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'lib/efi_loader') diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c index b458093dfbd..1b648c84673 100644 --- a/lib/efi_loader/efi_setup.c +++ b/lib/efi_loader/efi_setup.c @@ -82,6 +82,39 @@ out: return ret; } +#ifdef CONFIG_EFI_SECURE_BOOT +/** + * efi_init_secure_boot - initialize secure boot state + * + * Return: EFI_SUCCESS on success, status code (negative) on error + */ +static efi_status_t efi_init_secure_boot(void) +{ + efi_guid_t signature_types[] = { + EFI_CERT_SHA256_GUID, + EFI_CERT_X509_GUID, + }; + efi_status_t ret; + + /* TODO: read-only */ + ret = EFI_CALL(efi_set_variable(L"SignatureSupport", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(signature_types), + &signature_types)); + if (ret != EFI_SUCCESS) + printf("EFI: cannot initialize SignatureSupport variable\n"); + + return ret; +} +#else +static efi_status_t efi_init_secure_boot(void) +{ + return EFI_SUCCESS; +} +#endif /* CONFIG_EFI_SECURE_BOOT */ + /** * efi_init_obj_list() - Initialize and populate EFI object list * @@ -127,6 +160,11 @@ efi_status_t efi_init_obj_list(void) if (ret != EFI_SUCCESS) goto out; + /* Secure boot */ + ret = efi_init_secure_boot(); + if (ret != EFI_SUCCESS) + goto out; + /* Indicate supported runtime services */ ret = efi_init_runtime_supported(); if (ret != EFI_SUCCESS) -- cgit v1.3.1