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