linux/drivers/firmware/efi/memattr.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (C) 2016 Linaro Ltd. <ard.biesheuvel@linaro.org>
   4 */
   5
   6#define pr_fmt(fmt)     "efi: memattr: " fmt
   7
   8#include <linux/efi.h>
   9#include <linux/init.h>
  10#include <linux/io.h>
  11#include <linux/memblock.h>
  12
  13#include <asm/early_ioremap.h>
  14
  15static int __initdata tbl_size;
  16
  17/*
  18 * Reserve the memory associated with the Memory Attributes configuration
  19 * table, if it exists.
  20 */
  21int __init efi_memattr_init(void)
  22{
  23        efi_memory_attributes_table_t *tbl;
  24
  25        if (efi.mem_attr_table == EFI_INVALID_TABLE_ADDR)
  26                return 0;
  27
  28        tbl = early_memremap(efi.mem_attr_table, sizeof(*tbl));
  29        if (!tbl) {
  30                pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n",
  31                       efi.mem_attr_table);
  32                return -ENOMEM;
  33        }
  34
  35        if (tbl->version > 1) {
  36                pr_warn("Unexpected EFI Memory Attributes table version %d\n",
  37                        tbl->version);
  38                goto unmap;
  39        }
  40
  41        tbl_size = sizeof(*tbl) + tbl->num_entries * tbl->desc_size;
  42        memblock_reserve(efi.mem_attr_table, tbl_size);
  43        set_bit(EFI_MEM_ATTR, &efi.flags);
  44
  45unmap:
  46        early_memunmap(tbl, sizeof(*tbl));
  47        return 0;
  48}
  49
  50/*
  51 * Returns a copy @out of the UEFI memory descriptor @in if it is covered
  52 * entirely by a UEFI memory map entry with matching attributes. The virtual
  53 * address of @out is set according to the matching entry that was found.
  54 */
  55static bool entry_is_valid(const efi_memory_desc_t *in, efi_memory_desc_t *out)
  56{
  57        u64 in_paddr = in->phys_addr;
  58        u64 in_size = in->num_pages << EFI_PAGE_SHIFT;
  59        efi_memory_desc_t *md;
  60
  61        *out = *in;
  62
  63        if (in->type != EFI_RUNTIME_SERVICES_CODE &&
  64            in->type != EFI_RUNTIME_SERVICES_DATA) {
  65                pr_warn("Entry type should be RuntimeServiceCode/Data\n");
  66                return false;
  67        }
  68
  69        if (!(in->attribute & (EFI_MEMORY_RO | EFI_MEMORY_XP))) {
  70                pr_warn("Entry attributes invalid: RO and XP bits both cleared\n");
  71                return false;
  72        }
  73
  74        if (PAGE_SIZE > EFI_PAGE_SIZE &&
  75            (!PAGE_ALIGNED(in->phys_addr) ||
  76             !PAGE_ALIGNED(in->num_pages << EFI_PAGE_SHIFT))) {
  77                /*
  78                 * Since arm64 may execute with page sizes of up to 64 KB, the
  79                 * UEFI spec mandates that RuntimeServices memory regions must
  80                 * be 64 KB aligned. We need to validate this here since we will
  81                 * not be able to tighten permissions on such regions without
  82                 * affecting adjacent regions.
  83                 */
  84                pr_warn("Entry address region misaligned\n");
  85                return false;
  86        }
  87
  88        for_each_efi_memory_desc(md) {
  89                u64 md_paddr = md->phys_addr;
  90                u64 md_size = md->num_pages << EFI_PAGE_SHIFT;
  91
  92                if (!(md->attribute & EFI_MEMORY_RUNTIME))
  93                        continue;
  94                if (md->virt_addr == 0 && md->phys_addr != 0) {
  95                        /* no virtual mapping has been installed by the stub */
  96                        break;
  97                }
  98
  99                if (md_paddr > in_paddr || (in_paddr - md_paddr) >= md_size)
 100                        continue;
 101
 102                /*
 103                 * This entry covers the start of @in, check whether
 104                 * it covers the end as well.
 105                 */
 106                if (md_paddr + md_size < in_paddr + in_size) {
 107                        pr_warn("Entry covers multiple EFI memory map regions\n");
 108                        return false;
 109                }
 110
 111                if (md->type != in->type) {
 112                        pr_warn("Entry type deviates from EFI memory map region type\n");
 113                        return false;
 114                }
 115
 116                out->virt_addr = in_paddr + (md->virt_addr - md_paddr);
 117
 118                return true;
 119        }
 120
 121        pr_warn("No matching entry found in the EFI memory map\n");
 122        return false;
 123}
 124
 125/*
 126 * To be called after the EFI page tables have been populated. If a memory
 127 * attributes table is available, its contents will be used to update the
 128 * mappings with tightened permissions as described by the table.
 129 * This requires the UEFI memory map to have already been populated with
 130 * virtual addresses.
 131 */
 132int __init efi_memattr_apply_permissions(struct mm_struct *mm,
 133                                         efi_memattr_perm_setter fn)
 134{
 135        efi_memory_attributes_table_t *tbl;
 136        int i, ret;
 137
 138        if (tbl_size <= sizeof(*tbl))
 139                return 0;
 140
 141        /*
 142         * We need the EFI memory map to be setup so we can use it to
 143         * lookup the virtual addresses of all entries in the  of EFI
 144         * Memory Attributes table. If it isn't available, this
 145         * function should not be called.
 146         */
 147        if (WARN_ON(!efi_enabled(EFI_MEMMAP)))
 148                return 0;
 149
 150        tbl = memremap(efi.mem_attr_table, tbl_size, MEMREMAP_WB);
 151        if (!tbl) {
 152                pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n",
 153                       efi.mem_attr_table);
 154                return -ENOMEM;
 155        }
 156
 157        if (efi_enabled(EFI_DBG))
 158                pr_info("Processing EFI Memory Attributes table:\n");
 159
 160        for (i = ret = 0; ret == 0 && i < tbl->num_entries; i++) {
 161                efi_memory_desc_t md;
 162                unsigned long size;
 163                bool valid;
 164                char buf[64];
 165
 166                valid = entry_is_valid((void *)tbl->entry + i * tbl->desc_size,
 167                                       &md);
 168                size = md.num_pages << EFI_PAGE_SHIFT;
 169                if (efi_enabled(EFI_DBG) || !valid)
 170                        pr_info("%s 0x%012llx-0x%012llx %s\n",
 171                                valid ? "" : "!", md.phys_addr,
 172                                md.phys_addr + size - 1,
 173                                efi_md_typeattr_format(buf, sizeof(buf), &md));
 174
 175                if (valid) {
 176                        ret = fn(mm, &md);
 177                        if (ret)
 178                                pr_err("Error updating mappings, skipping subsequent md's\n");
 179                }
 180        }
 181        memunmap(tbl);
 182        return ret;
 183}
 184