// SPDX-License-Identifier: BSD-2-Clause-Patent /* * memattrs.c - EFI and DXE memory attribute helpers * Copyright Peter Jones */ #include "shim.h" static inline uint64_t shim_mem_attrs_to_uefi_mem_attrs (uint64_t attrs) { uint64_t ret = EFI_MEMORY_RP | EFI_MEMORY_RO | EFI_MEMORY_XP; if (attrs & MEM_ATTR_R) ret &= ~EFI_MEMORY_RP; if (attrs & MEM_ATTR_W) ret &= ~EFI_MEMORY_RO; if (attrs & MEM_ATTR_X) ret &= ~EFI_MEMORY_XP; return ret; } static inline uint64_t uefi_mem_attrs_to_shim_mem_attrs (uint64_t attrs) { uint64_t ret = MEM_ATTR_R | MEM_ATTR_W | MEM_ATTR_X; if (attrs & EFI_MEMORY_RP) ret &= ~MEM_ATTR_R; if (attrs & EFI_MEMORY_RO) ret &= ~MEM_ATTR_W; if (attrs & EFI_MEMORY_XP) ret &= ~MEM_ATTR_X; return ret; } static void get_dxe_services_table(EFI_DXE_SERVICES_TABLE **dstp) { static EFI_DXE_SERVICES_TABLE *dst = NULL; if (dst == NULL) { dprint(L"Looking for configuration table " LGUID_FMT L"\n", GUID_ARGS(gEfiDxeServicesTableGuid)); for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { EFI_CONFIGURATION_TABLE *ct = &ST->ConfigurationTable[i]; dprint(L"Testing configuration table " LGUID_FMT L"\n", GUID_ARGS(ct->VendorGuid)); if (CompareMem(&ct->VendorGuid, &gEfiDxeServicesTableGuid, sizeof(EFI_GUID)) != 0) continue; dst = (EFI_DXE_SERVICES_TABLE *)ct->VendorTable; dprint(L"Looking for DXE Services Signature 0x%16llx, found signature 0x%16llx\n", EFI_DXE_SERVICES_TABLE_SIGNATURE, dst->Hdr.Signature); if (dst->Hdr.Signature != EFI_DXE_SERVICES_TABLE_SIGNATURE) continue; if (!dst->GetMemorySpaceDescriptor || !dst->SetMemorySpaceAttributes) { /* * purposefully not treating this as an error so that HSIStatus * can tell us about it later. */ dprint(L"DXE Services lacks Get/SetMemorySpace* functions\n"); } dprint(L"Setting dxe_services_table to 0x%llx\n", dst); *dstp = dst; return; } } else { *dstp = dst; return; } dst = NULL; dprint(L"Couldn't find DXE services\n"); } static EFI_STATUS dxe_get_mem_attrs(uintptr_t physaddr, size_t size, uint64_t *attrs) { EFI_STATUS status; EFI_GCD_MEMORY_SPACE_DESCRIPTOR desc; EFI_PHYSICAL_ADDRESS start, end, next; EFI_DXE_SERVICES_TABLE *dst = NULL; get_dxe_services_table(&dst); if (!dst) return EFI_UNSUPPORTED; if (!dst->GetMemorySpaceDescriptor || !dst->SetMemorySpaceAttributes) return EFI_UNSUPPORTED; if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0 || attrs == NULL) { dprint(L"%a called on 0x%llx-0x%llx and attrs 0x%llx\n", __func__, (unsigned long long)physaddr, (unsigned long long)(physaddr+size-1), attrs); return EFI_SUCCESS; } start = ALIGN_DOWN(physaddr, EFI_PAGE_SIZE); end = ALIGN_UP(physaddr + size, EFI_PAGE_SIZE); for (; start < end; start = next) { status = dst->GetMemorySpaceDescriptor(start, &desc); if (EFI_ERROR(status)) { dprint(L"GetMemorySpaceDescriptor(0x%llx, ...): %r\n", start, status); return status; } next = desc.BaseAddress + desc.Length; if (desc.GcdMemoryType != EFI_GCD_MEMORY_TYPE_SYSTEM_MEMORY) continue; *attrs = uefi_mem_attrs_to_shim_mem_attrs(desc.Attributes); return EFI_SUCCESS; } return EFI_NOT_FOUND; } static EFI_STATUS dxe_update_mem_attrs(uintptr_t addr, size_t size, uint64_t set_attrs, uint64_t clear_attrs) { #if 0 EFI_STATUS status; EFI_GCD_MEMORY_SPACE_DESCRIPTOR desc; EFI_PHYSICAL_ADDRESS start, end, next; uint64_t before = 0, after = 0, dxe_set_attrs, dxe_clear_attrs; #endif EFI_DXE_SERVICES_TABLE *dst = NULL; get_dxe_services_table(&dst); if (!dst) return EFI_UNSUPPORTED; if (!dst->GetMemorySpaceDescriptor || !dst->SetMemorySpaceAttributes) return EFI_UNSUPPORTED; if (!IS_PAGE_ALIGNED(addr) || !IS_PAGE_ALIGNED(size) || size == 0) { perror(L"Invalid call %a(addr:0x%llx-0x%llx, size:0x%llx, +%a%a%a, -%a%a%a)\n", __func__, (unsigned long long)addr, (unsigned long long)(addr + size - 1), (unsigned long long)size, (set_attrs & MEM_ATTR_R) ? "r" : "", (set_attrs & MEM_ATTR_W) ? "w" : "", (set_attrs & MEM_ATTR_X) ? "x" : "", (clear_attrs & MEM_ATTR_R) ? "r" : "", (clear_attrs & MEM_ATTR_W) ? "w" : "", (clear_attrs & MEM_ATTR_X) ? "x" : ""); if (!IS_PAGE_ALIGNED(addr)) perror(L" addr is not page aligned\n"); if (!IS_PAGE_ALIGNED(size)) perror(L" size is not page aligned\n"); if (size == 0) perror(L" size is 0\n"); return EFI_SUCCESS; } /* * We know this only works coincidentally, so nerfing it for now * until we have a chance to debug more thoroughly on these niche * systems. */ #if 0 start = ALIGN_DOWN(addr, EFI_PAGE_SIZE); end = ALIGN_UP(addr + size, EFI_PAGE_SIZE); for (; start < end; start = next) { EFI_PHYSICAL_ADDRESS mod_start; UINT64 mod_size; status = dst->GetMemorySpaceDescriptor(start, &desc); if (EFI_ERROR(status)) { dprint(L"GetMemorySpaceDescriptor(0x%llx, ...): %r\n", start, status); return status; } next = desc.BaseAddress + desc.Length; if (desc.GcdMemoryType != EFI_GCD_MEMORY_TYPE_SYSTEM_MEMORY) continue; mod_start = MAX(start, desc.BaseAddress); mod_size = MIN(end, next) - mod_start; before = uefi_mem_attrs_to_shim_mem_attrs(desc.Attributes); dxe_set_attrs = shim_mem_attrs_to_uefi_mem_attrs(set_attrs); dprint("translating set_attrs from 0x%lx to 0x%lx\n", set_attrs, dxe_set_attrs); dxe_clear_attrs = shim_mem_attrs_to_uefi_mem_attrs(clear_attrs); dprint("translating clear_attrs from 0x%lx to 0x%lx\n", clear_attrs, dxe_clear_attrs); desc.Attributes |= dxe_set_attrs; desc.Attributes &= ~dxe_clear_attrs; after = uefi_mem_attrs_to_shim_mem_attrs(desc.Attributes); status = dst->SetMemorySpaceAttributes(mod_start, mod_size, desc.Attributes); if (EFI_ERROR(status)) { dprint(L"Failed to update memory attrs:0x%0x addr:0x%llx size:0x%0lx status:%r\n", desc.Attributes, mod_start, mod_size, status); return status; } break; } dprint(L"set +%a%a%a -%a%a%a on 0x%llx-0x%llx before:%c%c%c after:%c%c%c\n", (set_attrs & MEM_ATTR_R) ? "r" : "", (set_attrs & MEM_ATTR_W) ? "w" : "", (set_attrs & MEM_ATTR_X) ? "x" : "", (clear_attrs & MEM_ATTR_R) ? "r" : "", (clear_attrs & MEM_ATTR_W) ? "w" : "", (clear_attrs & MEM_ATTR_X) ? "x" : "", (unsigned long long)addr, (unsigned long long)(addr + size - 1), (before & MEM_ATTR_R) ? 'r' : '-', (before & MEM_ATTR_W) ? 'w' : '-', (before & MEM_ATTR_X) ? 'x' : '-', (after & MEM_ATTR_R) ? 'r' : '-', (after & MEM_ATTR_W) ? 'w' : '-', (after & MEM_ATTR_X) ? 'x' : '-'); #endif return EFI_SUCCESS; } static void get_efi_mem_attr_protocol(EFI_MEMORY_ATTRIBUTE_PROTOCOL **protop) { static EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; static bool has_mem_access_proto = true; if (proto == NULL && has_mem_access_proto == true) { EFI_STATUS efi_status; efi_status = LibLocateProtocol(&EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID, (VOID **)&proto); if (EFI_ERROR(efi_status) || !proto) { has_mem_access_proto = false; *protop = NULL; } } *protop = proto; } static EFI_STATUS efi_get_mem_attrs(uintptr_t addr, size_t size, uint64_t *attrs) { EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; EFI_PHYSICAL_ADDRESS physaddr = addr; EFI_STATUS efi_status; get_efi_mem_attr_protocol(&proto); if (!proto) return EFI_UNSUPPORTED; if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0 || attrs == NULL) { dprint(L"%a called on 0x%llx-0x%llx and attrs 0x%llx\n", __func__, (unsigned long long)physaddr, (unsigned long long)(physaddr+size-1), attrs); return EFI_SUCCESS; } efi_status = proto->GetMemoryAttributes(proto, physaddr, size, attrs); if (EFI_ERROR(efi_status)) { dprint(L"GetMemoryAttributes(..., 0x%llx, 0x%x, 0x%x): %r\n", physaddr, size, attrs, efi_status); } else { *attrs = uefi_mem_attrs_to_shim_mem_attrs (*attrs); } return efi_status; } static EFI_STATUS efi_update_mem_attrs(uintptr_t addr, uint64_t size, uint64_t set_attrs, uint64_t clear_attrs) { EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; EFI_PHYSICAL_ADDRESS physaddr = addr; EFI_STATUS efi_status, ret; uint64_t before = 0, after = 0, uefi_set_attrs, uefi_clear_attrs; get_efi_mem_attr_protocol(&proto); if (!proto) return EFI_UNSUPPORTED; efi_status = efi_get_mem_attrs(addr, size, &before); if (EFI_ERROR(efi_status)) dprint(L"efi_get_mem_attrs(0x%llx, 0x%llx, 0x%llx) -> 0x%lx\n", (unsigned long long)addr, (unsigned long long)size, &before, efi_status); if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0) { perror(L"Invalid call %a(addr:0x%llx-0x%llx, size:0x%llx, +%a%a%a, -%a%a%a)\n", __func__, (unsigned long long)physaddr, (unsigned long long)(physaddr + size - 1), (unsigned long long)size, (set_attrs & MEM_ATTR_R) ? "r" : "", (set_attrs & MEM_ATTR_W) ? "w" : "", (set_attrs & MEM_ATTR_X) ? "x" : "", (clear_attrs & MEM_ATTR_R) ? "r" : "", (clear_attrs & MEM_ATTR_W) ? "w" : "", (clear_attrs & MEM_ATTR_X) ? "x" : ""); if (!IS_PAGE_ALIGNED(physaddr)) perror(L" addr is not page aligned\n"); if (!IS_PAGE_ALIGNED(size)) perror(L" size is not page aligned\n"); if (size == 0) perror(L" size is 0\n"); return EFI_SUCCESS; } uefi_set_attrs = shim_mem_attrs_to_uefi_mem_attrs (set_attrs); dprint("translating set_attrs from 0x%lx to 0x%lx\n", set_attrs, uefi_set_attrs); uefi_clear_attrs = shim_mem_attrs_to_uefi_mem_attrs (clear_attrs); dprint("translating clear_attrs from 0x%lx to 0x%lx\n", clear_attrs, uefi_clear_attrs); efi_status = EFI_SUCCESS; if (uefi_set_attrs) { efi_status = proto->SetMemoryAttributes(proto, physaddr, size, uefi_set_attrs); if (EFI_ERROR(efi_status)) { dprint(L"Failed to set memory attrs:0x%0x physaddr:0x%llx size:0x%0lx status:%r\n", uefi_set_attrs, physaddr, size, efi_status); } } if (!EFI_ERROR(efi_status) && uefi_clear_attrs) { efi_status = proto->ClearMemoryAttributes(proto, physaddr, size, uefi_clear_attrs); if (EFI_ERROR(efi_status)) { dprint(L"Failed to clear memory attrs:0x%0x physaddr:0x%llx size:0x%0lx status:%r\n", uefi_clear_attrs, physaddr, size, efi_status); } } ret = efi_status; efi_status = efi_get_mem_attrs(addr, size, &after); if (EFI_ERROR(efi_status)) dprint(L"efi_get_mem_attrs(0x%llx, %llu, 0x%llx) -> 0x%lx\n", (unsigned long long)addr, (unsigned long long)size, &after, efi_status); dprint(L"set +%a%a%a -%a%a%a on 0x%llx-0x%llx before:%c%c%c after:%c%c%c\n", (set_attrs & MEM_ATTR_R) ? "r" : "", (set_attrs & MEM_ATTR_W) ? "w" : "", (set_attrs & MEM_ATTR_X) ? "x" : "", (clear_attrs & MEM_ATTR_R) ? "r" : "", (clear_attrs & MEM_ATTR_W) ? "w" : "", (clear_attrs & MEM_ATTR_X) ? "x" : "", (unsigned long long)addr, (unsigned long long)(addr + size - 1), (before & MEM_ATTR_R) ? 'r' : '-', (before & MEM_ATTR_W) ? 'w' : '-', (before & MEM_ATTR_X) ? 'x' : '-', (after & MEM_ATTR_R) ? 'r' : '-', (after & MEM_ATTR_W) ? 'w' : '-', (after & MEM_ATTR_X) ? 'x' : '-'); return ret; } EFI_STATUS update_mem_attrs(uintptr_t addr, uint64_t size, uint64_t set_attrs, uint64_t clear_attrs) { EFI_STATUS efi_status; efi_status = efi_update_mem_attrs(addr, size, set_attrs, clear_attrs); if (!EFI_ERROR(efi_status)) return efi_status; if (efi_status == EFI_UNSUPPORTED) efi_status = dxe_update_mem_attrs(addr, size, set_attrs, clear_attrs); return efi_status; } EFI_STATUS get_mem_attrs(uintptr_t addr, size_t size, uint64_t *attrs) { EFI_STATUS efi_status; efi_status = efi_get_mem_attrs(addr, size, attrs); if (!EFI_ERROR(efi_status)) return efi_status; if (efi_status == EFI_UNSUPPORTED) efi_status = dxe_get_mem_attrs(addr, size, attrs); return efi_status; } char * decode_hsi_bits(UINTN hsi) { static const struct { UINTN bit; char name[16]; } bits[] = { {.bit = SHIM_HSI_STATUS_HEAPX, .name = "HEAPX"}, {.bit = SHIM_HSI_STATUS_STACKX, .name = "STACKX"}, {.bit = SHIM_HSI_STATUS_ROW, .name = "ROW"}, {.bit = SHIM_HSI_STATUS_HASMAP, .name = "HASMAP"}, {.bit = SHIM_HSI_STATUS_HASDST, .name = "HASDST"}, {.bit = SHIM_HSI_STATUS_HASDSTGMSD, .name = "HASDSTGMSD"}, {.bit = SHIM_HSI_STATUS_HASDSTSMSA, .name = "HASDSTSMSA"}, {.bit = SHIM_HSI_STATUS_NX, .name = "NX"}, {.bit = 0, .name = ""}, }; static int x = 0; static char retbufs[2][sizeof(bits)]; char *retbuf = &retbufs[x % 2][0]; char *prev = &retbuf[0]; char *pos = &retbuf[0]; x = ( x + 1 ) % 2; ZeroMem(retbuf, sizeof(bits)); if (hsi == 0) { prev = stpcpy(retbuf, "0"); } else { for (UINTN i = 0; bits[i].bit != 0; i++) { if (hsi & bits[i].bit) { prev = stpcpy(pos, bits[i].name); pos = stpcpy(prev, "|"); } } } prev[0] = '\0'; return retbuf; } void get_hsi_mem_info(void) { EFI_STATUS efi_status; uintptr_t addr; uint64_t attrs = 0; uint32_t *tmp_alloc; EFI_MEMORY_ATTRIBUTE_PROTOCOL *efiproto = NULL; EFI_DXE_SERVICES_TABLE *dst = NULL; get_efi_mem_attr_protocol(&efiproto); if (efiproto) { dprint(L"Setting HSI from %a to %a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASMAP)); hsi_status |= SHIM_HSI_STATUS_HASMAP; } get_dxe_services_table(&dst); if (dst) { dprint(L"Setting HSI from %a to %a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASDST)); hsi_status |= SHIM_HSI_STATUS_HASDST; if (dst->GetMemorySpaceDescriptor) { dprint(L"Setting HSI from %a to %a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASDSTGMSD)); hsi_status |= SHIM_HSI_STATUS_HASDSTGMSD; } if (dst->SetMemorySpaceAttributes) { dprint(L"Setting HSI from %a to %a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASDSTSMSA)); hsi_status |= SHIM_HSI_STATUS_HASDSTSMSA; } } if (!(hsi_status & SHIM_HSI_STATUS_HASMAP) && !(hsi_status & SHIM_HSI_STATUS_HASDSTGMSD && hsi_status & SHIM_HSI_STATUS_HASDSTSMSA)) { dprint(L"No memory protocol, not testing further\n"); return; } addr = ((uintptr_t)&get_hsi_mem_info) & ~EFI_PAGE_MASK; efi_status = get_mem_attrs(addr, EFI_PAGE_SIZE, &attrs); if (EFI_ERROR(efi_status)) { dprint(L"get_mem_attrs(0x%016llx, 0x%x, &attrs) failed.\n", addr, EFI_PAGE_SIZE); goto error; } if (attrs & MEM_ATTR_W) { dprint(L"get_hsi_mem_info() is on a writable page: %a->%a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_ROW)); hsi_status |= SHIM_HSI_STATUS_ROW; } addr = ((uintptr_t)&addr) & ~EFI_PAGE_MASK; efi_status = get_mem_attrs(addr, EFI_PAGE_SIZE, &attrs); if (EFI_ERROR(efi_status)) { dprint(L"get_mem_attrs(0x%016llx, 0x%x, &attrs) failed.\n", addr, EFI_PAGE_SIZE); goto error; } if (attrs & MEM_ATTR_X) { dprint(L"Stack variable is on an executable page: %a->%a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_STACKX)); hsi_status |= SHIM_HSI_STATUS_STACKX; } tmp_alloc = AllocatePool(EFI_PAGE_SIZE); if (!tmp_alloc) { dprint(L"Failed to allocate heap variable.\n"); goto error; } addr = ((uintptr_t)tmp_alloc) & ~EFI_PAGE_MASK; efi_status = get_mem_attrs(addr, EFI_PAGE_SIZE, &attrs); FreePool(tmp_alloc); if (EFI_ERROR(efi_status)) { dprint(L"get_mem_attrs(0x%016llx, 0x%x, &attrs) failed.\n", addr, EFI_PAGE_SIZE); goto error; } if (attrs & MEM_ATTR_X) { dprint(L"Heap variable is on an executable page: %a->%a\n", decode_hsi_bits(hsi_status), decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HEAPX)); hsi_status |= SHIM_HSI_STATUS_HEAPX; } return; error: /* * In this case we can't actually tell anything, so assume * and report the worst case scenario. */ hsi_status = SHIM_HSI_STATUS_HEAPX | SHIM_HSI_STATUS_STACKX | SHIM_HSI_STATUS_ROW; dprint(L"Setting HSI to 0x%lx due to error: %r\n", decode_hsi_bits(hsi_status), efi_status); } // vim:fenc=utf-8:tw=75:noet