162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * mokvar-table.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2020 Red Hat 662306a36Sopenharmony_ci * Author: Lenny Szubowicz <lszubowi@redhat.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * This module contains the kernel support for the Linux EFI Machine 962306a36Sopenharmony_ci * Owner Key (MOK) variable configuration table, which is identified by 1062306a36Sopenharmony_ci * the LINUX_EFI_MOK_VARIABLE_TABLE_GUID. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * This EFI configuration table provides a more robust alternative to 1362306a36Sopenharmony_ci * EFI volatile variables by which an EFI boot loader can pass the 1462306a36Sopenharmony_ci * contents of the Machine Owner Key (MOK) certificate stores to the 1562306a36Sopenharmony_ci * kernel during boot. If both the EFI MOK config table and corresponding 1662306a36Sopenharmony_ci * EFI MOK variables are present, the table should be considered as 1762306a36Sopenharmony_ci * more authoritative. 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * This module includes code that validates and maps the EFI MOK table, 2062306a36Sopenharmony_ci * if it's presence was detected very early in boot. 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * Kernel interface routines are provided to walk through all the 2362306a36Sopenharmony_ci * entries in the MOK config table or to search for a specific named 2462306a36Sopenharmony_ci * entry. 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * The contents of the individual named MOK config table entries are 2762306a36Sopenharmony_ci * made available to user space via read-only sysfs binary files under: 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * /sys/firmware/efi/mok-variables/ 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_ci#define pr_fmt(fmt) "mokvar: " fmt 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#include <linux/capability.h> 3562306a36Sopenharmony_ci#include <linux/efi.h> 3662306a36Sopenharmony_ci#include <linux/init.h> 3762306a36Sopenharmony_ci#include <linux/io.h> 3862306a36Sopenharmony_ci#include <linux/kernel.h> 3962306a36Sopenharmony_ci#include <linux/kobject.h> 4062306a36Sopenharmony_ci#include <linux/list.h> 4162306a36Sopenharmony_ci#include <linux/slab.h> 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#include <asm/early_ioremap.h> 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* 4662306a36Sopenharmony_ci * The LINUX_EFI_MOK_VARIABLE_TABLE_GUID config table is a packed 4762306a36Sopenharmony_ci * sequence of struct efi_mokvar_table_entry, one for each named 4862306a36Sopenharmony_ci * MOK variable. The sequence is terminated by an entry with a 4962306a36Sopenharmony_ci * completely NULL name and 0 data size. 5062306a36Sopenharmony_ci * 5162306a36Sopenharmony_ci * efi_mokvar_table_size is set to the computed size of the 5262306a36Sopenharmony_ci * MOK config table by efi_mokvar_table_init(). This will be 5362306a36Sopenharmony_ci * non-zero if and only if the table if present and has been 5462306a36Sopenharmony_ci * validated by efi_mokvar_table_init(). 5562306a36Sopenharmony_ci */ 5662306a36Sopenharmony_cistatic size_t efi_mokvar_table_size; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* 5962306a36Sopenharmony_ci * efi_mokvar_table_va is the kernel virtual address at which the 6062306a36Sopenharmony_ci * EFI MOK config table has been mapped by efi_mokvar_sysfs_init(). 6162306a36Sopenharmony_ci */ 6262306a36Sopenharmony_cistatic struct efi_mokvar_table_entry *efi_mokvar_table_va; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/* 6562306a36Sopenharmony_ci * Each /sys/firmware/efi/mok-variables/ sysfs file is represented by 6662306a36Sopenharmony_ci * an instance of struct efi_mokvar_sysfs_attr on efi_mokvar_sysfs_list. 6762306a36Sopenharmony_ci * bin_attr.private points to the associated EFI MOK config table entry. 6862306a36Sopenharmony_ci * 6962306a36Sopenharmony_ci * This list is created during boot and then remains unchanged. 7062306a36Sopenharmony_ci * So no synchronization is currently required to walk the list. 7162306a36Sopenharmony_ci */ 7262306a36Sopenharmony_cistruct efi_mokvar_sysfs_attr { 7362306a36Sopenharmony_ci struct bin_attribute bin_attr; 7462306a36Sopenharmony_ci struct list_head node; 7562306a36Sopenharmony_ci}; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic LIST_HEAD(efi_mokvar_sysfs_list); 7862306a36Sopenharmony_cistatic struct kobject *mokvar_kobj; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci/* 8162306a36Sopenharmony_ci * efi_mokvar_table_init() - Early boot validation of EFI MOK config table 8262306a36Sopenharmony_ci * 8362306a36Sopenharmony_ci * If present, validate and compute the size of the EFI MOK variable 8462306a36Sopenharmony_ci * configuration table. This table may be provided by an EFI boot loader 8562306a36Sopenharmony_ci * as an alternative to ordinary EFI variables, due to platform-dependent 8662306a36Sopenharmony_ci * limitations. The memory occupied by this table is marked as reserved. 8762306a36Sopenharmony_ci * 8862306a36Sopenharmony_ci * This routine must be called before efi_free_boot_services() in order 8962306a36Sopenharmony_ci * to guarantee that it can mark the table as reserved. 9062306a36Sopenharmony_ci * 9162306a36Sopenharmony_ci * Implicit inputs: 9262306a36Sopenharmony_ci * efi.mokvar_table: Physical address of EFI MOK variable config table 9362306a36Sopenharmony_ci * or special value that indicates no such table. 9462306a36Sopenharmony_ci * 9562306a36Sopenharmony_ci * Implicit outputs: 9662306a36Sopenharmony_ci * efi_mokvar_table_size: Computed size of EFI MOK variable config table. 9762306a36Sopenharmony_ci * The table is considered present and valid if this 9862306a36Sopenharmony_ci * is non-zero. 9962306a36Sopenharmony_ci */ 10062306a36Sopenharmony_civoid __init efi_mokvar_table_init(void) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci efi_memory_desc_t md; 10362306a36Sopenharmony_ci void *va = NULL; 10462306a36Sopenharmony_ci unsigned long cur_offset = 0; 10562306a36Sopenharmony_ci unsigned long offset_limit; 10662306a36Sopenharmony_ci unsigned long map_size = 0; 10762306a36Sopenharmony_ci unsigned long map_size_needed = 0; 10862306a36Sopenharmony_ci unsigned long size; 10962306a36Sopenharmony_ci struct efi_mokvar_table_entry *mokvar_entry; 11062306a36Sopenharmony_ci int err; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (!efi_enabled(EFI_MEMMAP)) 11362306a36Sopenharmony_ci return; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (efi.mokvar_table == EFI_INVALID_TABLE_ADDR) 11662306a36Sopenharmony_ci return; 11762306a36Sopenharmony_ci /* 11862306a36Sopenharmony_ci * The EFI MOK config table must fit within a single EFI memory 11962306a36Sopenharmony_ci * descriptor range. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ci err = efi_mem_desc_lookup(efi.mokvar_table, &md); 12262306a36Sopenharmony_ci if (err) { 12362306a36Sopenharmony_ci pr_warn("EFI MOKvar config table is not within the EFI memory map\n"); 12462306a36Sopenharmony_ci return; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci offset_limit = efi_mem_desc_end(&md) - efi.mokvar_table; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* 13062306a36Sopenharmony_ci * Validate the MOK config table. Since there is no table header 13162306a36Sopenharmony_ci * from which we could get the total size of the MOK config table, 13262306a36Sopenharmony_ci * we compute the total size as we validate each variably sized 13362306a36Sopenharmony_ci * entry, remapping as necessary. 13462306a36Sopenharmony_ci */ 13562306a36Sopenharmony_ci err = -EINVAL; 13662306a36Sopenharmony_ci while (cur_offset + sizeof(*mokvar_entry) <= offset_limit) { 13762306a36Sopenharmony_ci mokvar_entry = va + cur_offset; 13862306a36Sopenharmony_ci map_size_needed = cur_offset + sizeof(*mokvar_entry); 13962306a36Sopenharmony_ci if (map_size_needed > map_size) { 14062306a36Sopenharmony_ci if (va) 14162306a36Sopenharmony_ci early_memunmap(va, map_size); 14262306a36Sopenharmony_ci /* 14362306a36Sopenharmony_ci * Map a little more than the fixed size entry 14462306a36Sopenharmony_ci * header, anticipating some data. It's safe to 14562306a36Sopenharmony_ci * do so as long as we stay within current memory 14662306a36Sopenharmony_ci * descriptor. 14762306a36Sopenharmony_ci */ 14862306a36Sopenharmony_ci map_size = min(map_size_needed + 2*EFI_PAGE_SIZE, 14962306a36Sopenharmony_ci offset_limit); 15062306a36Sopenharmony_ci va = early_memremap(efi.mokvar_table, map_size); 15162306a36Sopenharmony_ci if (!va) { 15262306a36Sopenharmony_ci pr_err("Failed to map EFI MOKvar config table pa=0x%lx, size=%lu.\n", 15362306a36Sopenharmony_ci efi.mokvar_table, map_size); 15462306a36Sopenharmony_ci return; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci mokvar_entry = va + cur_offset; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* Check for last sentinel entry */ 16062306a36Sopenharmony_ci if (mokvar_entry->name[0] == '\0') { 16162306a36Sopenharmony_ci if (mokvar_entry->data_size != 0) 16262306a36Sopenharmony_ci break; 16362306a36Sopenharmony_ci err = 0; 16462306a36Sopenharmony_ci break; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci /* Sanity check that the name is null terminated */ 16862306a36Sopenharmony_ci size = strnlen(mokvar_entry->name, 16962306a36Sopenharmony_ci sizeof(mokvar_entry->name)); 17062306a36Sopenharmony_ci if (size >= sizeof(mokvar_entry->name)) 17162306a36Sopenharmony_ci break; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* Advance to the next entry */ 17462306a36Sopenharmony_ci cur_offset = map_size_needed + mokvar_entry->data_size; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (va) 17862306a36Sopenharmony_ci early_memunmap(va, map_size); 17962306a36Sopenharmony_ci if (err) { 18062306a36Sopenharmony_ci pr_err("EFI MOKvar config table is not valid\n"); 18162306a36Sopenharmony_ci return; 18262306a36Sopenharmony_ci } 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (md.type == EFI_BOOT_SERVICES_DATA) 18562306a36Sopenharmony_ci efi_mem_reserve(efi.mokvar_table, map_size_needed); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci efi_mokvar_table_size = map_size_needed; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci/* 19162306a36Sopenharmony_ci * efi_mokvar_entry_next() - Get next entry in the EFI MOK config table 19262306a36Sopenharmony_ci * 19362306a36Sopenharmony_ci * mokvar_entry: Pointer to current EFI MOK config table entry 19462306a36Sopenharmony_ci * or null. Null indicates get first entry. 19562306a36Sopenharmony_ci * Passed by reference. This is updated to the 19662306a36Sopenharmony_ci * same value as the return value. 19762306a36Sopenharmony_ci * 19862306a36Sopenharmony_ci * Returns: Pointer to next EFI MOK config table entry 19962306a36Sopenharmony_ci * or null, if there are no more entries. 20062306a36Sopenharmony_ci * Same value is returned in the mokvar_entry 20162306a36Sopenharmony_ci * parameter. 20262306a36Sopenharmony_ci * 20362306a36Sopenharmony_ci * This routine depends on the EFI MOK config table being entirely 20462306a36Sopenharmony_ci * mapped with it's starting virtual address in efi_mokvar_table_va. 20562306a36Sopenharmony_ci */ 20662306a36Sopenharmony_cistruct efi_mokvar_table_entry *efi_mokvar_entry_next( 20762306a36Sopenharmony_ci struct efi_mokvar_table_entry **mokvar_entry) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci struct efi_mokvar_table_entry *mokvar_cur; 21062306a36Sopenharmony_ci struct efi_mokvar_table_entry *mokvar_next; 21162306a36Sopenharmony_ci size_t size_cur; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci mokvar_cur = *mokvar_entry; 21462306a36Sopenharmony_ci *mokvar_entry = NULL; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci if (efi_mokvar_table_va == NULL) 21762306a36Sopenharmony_ci return NULL; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (mokvar_cur == NULL) { 22062306a36Sopenharmony_ci mokvar_next = efi_mokvar_table_va; 22162306a36Sopenharmony_ci } else { 22262306a36Sopenharmony_ci if (mokvar_cur->name[0] == '\0') 22362306a36Sopenharmony_ci return NULL; 22462306a36Sopenharmony_ci size_cur = sizeof(*mokvar_cur) + mokvar_cur->data_size; 22562306a36Sopenharmony_ci mokvar_next = (void *)mokvar_cur + size_cur; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (mokvar_next->name[0] == '\0') 22962306a36Sopenharmony_ci return NULL; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci *mokvar_entry = mokvar_next; 23262306a36Sopenharmony_ci return mokvar_next; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci/* 23662306a36Sopenharmony_ci * efi_mokvar_entry_find() - Find EFI MOK config entry by name 23762306a36Sopenharmony_ci * 23862306a36Sopenharmony_ci * name: Name of the entry to look for. 23962306a36Sopenharmony_ci * 24062306a36Sopenharmony_ci * Returns: Pointer to EFI MOK config table entry if found; 24162306a36Sopenharmony_ci * null otherwise. 24262306a36Sopenharmony_ci * 24362306a36Sopenharmony_ci * This routine depends on the EFI MOK config table being entirely 24462306a36Sopenharmony_ci * mapped with it's starting virtual address in efi_mokvar_table_va. 24562306a36Sopenharmony_ci */ 24662306a36Sopenharmony_cistruct efi_mokvar_table_entry *efi_mokvar_entry_find(const char *name) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct efi_mokvar_table_entry *mokvar_entry = NULL; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci while (efi_mokvar_entry_next(&mokvar_entry)) { 25162306a36Sopenharmony_ci if (!strncmp(name, mokvar_entry->name, 25262306a36Sopenharmony_ci sizeof(mokvar_entry->name))) 25362306a36Sopenharmony_ci return mokvar_entry; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci return NULL; 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci/* 25962306a36Sopenharmony_ci * efi_mokvar_sysfs_read() - sysfs binary file read routine 26062306a36Sopenharmony_ci * 26162306a36Sopenharmony_ci * Returns: Count of bytes read. 26262306a36Sopenharmony_ci * 26362306a36Sopenharmony_ci * Copy EFI MOK config table entry data for this mokvar sysfs binary file 26462306a36Sopenharmony_ci * to the supplied buffer, starting at the specified offset into mokvar table 26562306a36Sopenharmony_ci * entry data, for the specified count bytes. The copy is limited by the 26662306a36Sopenharmony_ci * amount of data in this mokvar config table entry. 26762306a36Sopenharmony_ci */ 26862306a36Sopenharmony_cistatic ssize_t efi_mokvar_sysfs_read(struct file *file, struct kobject *kobj, 26962306a36Sopenharmony_ci struct bin_attribute *bin_attr, char *buf, 27062306a36Sopenharmony_ci loff_t off, size_t count) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct efi_mokvar_table_entry *mokvar_entry = bin_attr->private; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (!capable(CAP_SYS_ADMIN)) 27562306a36Sopenharmony_ci return 0; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (off >= mokvar_entry->data_size) 27862306a36Sopenharmony_ci return 0; 27962306a36Sopenharmony_ci if (count > mokvar_entry->data_size - off) 28062306a36Sopenharmony_ci count = mokvar_entry->data_size - off; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci memcpy(buf, mokvar_entry->data + off, count); 28362306a36Sopenharmony_ci return count; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci/* 28762306a36Sopenharmony_ci * efi_mokvar_sysfs_init() - Map EFI MOK config table and create sysfs 28862306a36Sopenharmony_ci * 28962306a36Sopenharmony_ci * Map the EFI MOK variable config table for run-time use by the kernel 29062306a36Sopenharmony_ci * and create the sysfs entries in /sys/firmware/efi/mok-variables/ 29162306a36Sopenharmony_ci * 29262306a36Sopenharmony_ci * This routine just returns if a valid EFI MOK variable config table 29362306a36Sopenharmony_ci * was not found earlier during boot. 29462306a36Sopenharmony_ci * 29562306a36Sopenharmony_ci * This routine must be called during a "middle" initcall phase, i.e. 29662306a36Sopenharmony_ci * after efi_mokvar_table_init() but before UEFI certs are loaded 29762306a36Sopenharmony_ci * during late init. 29862306a36Sopenharmony_ci * 29962306a36Sopenharmony_ci * Implicit inputs: 30062306a36Sopenharmony_ci * efi.mokvar_table: Physical address of EFI MOK variable config table 30162306a36Sopenharmony_ci * or special value that indicates no such table. 30262306a36Sopenharmony_ci * 30362306a36Sopenharmony_ci * efi_mokvar_table_size: Computed size of EFI MOK variable config table. 30462306a36Sopenharmony_ci * The table is considered present and valid if this 30562306a36Sopenharmony_ci * is non-zero. 30662306a36Sopenharmony_ci * 30762306a36Sopenharmony_ci * Implicit outputs: 30862306a36Sopenharmony_ci * efi_mokvar_table_va: Start virtual address of the EFI MOK config table. 30962306a36Sopenharmony_ci */ 31062306a36Sopenharmony_cistatic int __init efi_mokvar_sysfs_init(void) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci void *config_va; 31362306a36Sopenharmony_ci struct efi_mokvar_table_entry *mokvar_entry = NULL; 31462306a36Sopenharmony_ci struct efi_mokvar_sysfs_attr *mokvar_sysfs = NULL; 31562306a36Sopenharmony_ci int err = 0; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (efi_mokvar_table_size == 0) 31862306a36Sopenharmony_ci return -ENOENT; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci config_va = memremap(efi.mokvar_table, efi_mokvar_table_size, 32162306a36Sopenharmony_ci MEMREMAP_WB); 32262306a36Sopenharmony_ci if (!config_va) { 32362306a36Sopenharmony_ci pr_err("Failed to map EFI MOKvar config table\n"); 32462306a36Sopenharmony_ci return -ENOMEM; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci efi_mokvar_table_va = config_va; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci mokvar_kobj = kobject_create_and_add("mok-variables", efi_kobj); 32962306a36Sopenharmony_ci if (!mokvar_kobj) { 33062306a36Sopenharmony_ci pr_err("Failed to create EFI mok-variables sysfs entry\n"); 33162306a36Sopenharmony_ci return -ENOMEM; 33262306a36Sopenharmony_ci } 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci while (efi_mokvar_entry_next(&mokvar_entry)) { 33562306a36Sopenharmony_ci mokvar_sysfs = kzalloc(sizeof(*mokvar_sysfs), GFP_KERNEL); 33662306a36Sopenharmony_ci if (!mokvar_sysfs) { 33762306a36Sopenharmony_ci err = -ENOMEM; 33862306a36Sopenharmony_ci break; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci sysfs_bin_attr_init(&mokvar_sysfs->bin_attr); 34262306a36Sopenharmony_ci mokvar_sysfs->bin_attr.private = mokvar_entry; 34362306a36Sopenharmony_ci mokvar_sysfs->bin_attr.attr.name = mokvar_entry->name; 34462306a36Sopenharmony_ci mokvar_sysfs->bin_attr.attr.mode = 0400; 34562306a36Sopenharmony_ci mokvar_sysfs->bin_attr.size = mokvar_entry->data_size; 34662306a36Sopenharmony_ci mokvar_sysfs->bin_attr.read = efi_mokvar_sysfs_read; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci err = sysfs_create_bin_file(mokvar_kobj, 34962306a36Sopenharmony_ci &mokvar_sysfs->bin_attr); 35062306a36Sopenharmony_ci if (err) 35162306a36Sopenharmony_ci break; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci list_add_tail(&mokvar_sysfs->node, &efi_mokvar_sysfs_list); 35462306a36Sopenharmony_ci } 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci if (err) { 35762306a36Sopenharmony_ci pr_err("Failed to create some EFI mok-variables sysfs entries\n"); 35862306a36Sopenharmony_ci kfree(mokvar_sysfs); 35962306a36Sopenharmony_ci } 36062306a36Sopenharmony_ci return err; 36162306a36Sopenharmony_ci} 36262306a36Sopenharmony_cifs_initcall(efi_mokvar_sysfs_init); 363