The Access Permissions (AP) attribute controls whether a location can be read and written, and what privilege is necessary. This table shows the AP bit settings:
|AP||Unprivileged (EL0)||Privileged (EL1/2/3)|
If an access breaks the specified permissions, for example a write to a read-only region, an exception (labelled as a permission fault) is generated.
Privileged accesses to unprivileged data
The standard permission model is that a more privileged entity can access anything belonging to a less privileged entity. Explained another way, an Operating System (OS) can see all the resources that are allocated to an application. For example, a hypervisor can see all the resources that are allocated to a virtual machine. This is because executing at a higher exception level means that the level of privilege is also higher.
However, this is not always desirable. Malicious applications might try to trick an OS into accessing data on behalf of the application, which the application should not be able to see. This requires the OS to check pointers in systems calls.
The Arm architecture provides several controls to make this simpler. First, there is the
PSTATE.PAN (Privileged Access Never) bit. When this bit is set, loads and stores from EL1 (or EL2 when
E2H==1) to unprivileged regions will generate an exception (Permission Fault),like this diagram illustrates:
PAN was added in Armv8.1-A.
PAN allows unintended accesses to unprivileged data to be trapped. For example, the OS performs an access thinking that the destination is privileged. In fact, the destination is unprivileged. This means that there is a mismatch between the OS expectation (that the destination is privileged) and reality (the destination is unprivileged). This could occur due to a programming error, or could be the result of an attack on the system. In either case,
PAN allows us to trap the access before it occurs, ensuring safe operation.
Sometimes the OS does need to access unprivileged regions, for example, to write to a buffer owned by an application. To support this, the instruction set provides the
STTR are unprivileged loads and stores. They are checked against EL0 permission checking even when executed by the OS at EL1 or EL2. Because these are explicitly unprivileged accesses, they are not blocked by
PAN, like this diagram shows:
This allows the OS to distinguish between accesses that are intended to access privileged data and those which are expected to access unprivileged data. This also allows the hardware to use that information to check the accesses.
Note: The T in LDTR stands for translation. This is because the first Arm processors to support virtual to physical translation only did so for User mode applications, not for the OS. For the OS to access application data it needed a special load, a load with translation. Today of course, all software sees virtual addresses, but the name has remained.
In addition to access permissions, there are also execution permissions. These attributes let you specify that instructions cannot be fetched from the address:
- UXN. User (EL0) Execute Never (Not used at EL3, or EL2 when
- PXN. Privileged Execute Never (Called XN at EL3, and EL2 when
These are Execute Never bits. This means that setting the bit makes the location not executable.
There are separate Privileged and Unprivileged bits, because application code needs to be executable in user space (EL0) but should never be executed with kernel permissions (EL1/EL2), like this diagram shows:
The architecture also provides controls bits in the System Control Register (
SCTLR_ELx) to make all write-able addresses non-executable.
A location with EL0 write permissions is never executable at EL1.
Note: Remember, Arm recommends that Device regions are always marked as Execute Never (