Introduction
Mapping application memory as (read-write-execute (RWX) is often considered dangerous as it removes a critical security boundary between data and code. When a memory region is both writable and executable, an attacker who finds any way to write into that region—such as through a buffer overflow, use-after-free, or injection bug etc —can immediately execute the injected data as code. Mapping memory properly greatly reduces the attack surface and makes vulnerabilities harder to exploit.
In U-Boot, memory was classified as either normal memory, which is mapped with read, write, and execute permissions, or device memory, which is mapped as read/write only. When U-Boot relocated itself to the top of DRAM, the memory permissions remained unchanged as RWX, resulting in an increased attack surface.
Arm Memory Configuration
The Arm architecture has complex ways of configuring the Memory Management Unit (MMU) and it depends on the overall system configuration.
For U-Boot we only care about “Stage 1 VMSAv8-64 Block and Page descriptor fields” found in D8-6492 of the Arm(R) Architecture Reference Manual version L.a
- XN: eXecute Never — Prevents execution
- PXN: Privileged eXecute Never — Prevents execution from privileged mode
- UXN: Unprivileged eXecute Never — Prevents execution from unprivileged mode
- RO: Read-only — Prevents memory writes
Which ones you have to set depends mainly on the translation regime. Running U-Boot in QEMU without virtualization ends up running U-Boot in EL1&0 translation regime and both PXN/UXN need to be set.
Inspecting U-Boot
Compiling qemu_arm64_lwip_defconfig (which now includes our patchset) and enabling CMD_MEMINFO && CMD_CMD_MEMINFO_MAP, allows us to inspect the memory maps.

- PXN UXN: Read-Erite memory
- RWX: Read-Write-Execute memory
As you can see, only device mapped memory is not allowed to execute. The remaining memory regions are mapped as RWX
Enable memory permissions
The patchset above adds another interesting flag CONFIG_MMU_PGROT which only applies to arm64. Unfortunately we can’t yet enable it by default for all arm64 boards, because bugs like this and this will now lead to a crash.

- PXN UXN RO: Read-only memory
- PXN UXN: Read-Write memory
- RO: Read-Execute memory
U-Boot has relocated to [0x0000023f6b9000 - 0x0000023f77d000] [0x0000023f77e000 - 0x0000023f7c8000] and [0x0000023f7c8000 - 0x0000023f7e0000] which now have proper memory permissions!
But we still see RWX mappings
There are several reasons that the rest of the memory is left as RWX. One of them is EFI runtime services.
U-Boot doesn’t separate between EFI RO, RW and RX sections, instead it bundles all of them in the .efi_runtime and places them right before .text. We end up mapping 64kb for runtime services, which includes all the EFI related sections and .text. The linker script will need a bigger rewrite to map EFI services properly.
Another reason is the SetVirtualAddressMap which allows the OS to remap EFI runtime services in a VA of its choice. This is rarely called for the arm64 architecture (only when VA_BITS < 39) in Linux, but when it’s needed we need to switch any RX (which will now hold the runtime services) to RWX to allow relocations.
So for now certain pages are left as RWX.
Remaining Issues
The story isn’t complete as there are other aspects that need attention. Examples include:
- Reviewing linker scripts to potentially decouple EFI memory from .text
- Investigating how to apply proper mapping to EFI runtime services as well
If your organisation is interested in further work in securing U-boot or you have questions about the above configuration changes, please get in touch.