ARMv8-A Translation Table APTable Permission Faults in Secure World Implementations
Understanding the APTable Permission Mechanism in ARMv8-A Translation Tables
The ARMv8-A architecture employs a multi-level translation table system to manage virtual-to-physical address mappings and access permissions. The translation tables are hierarchical, with each level responsible for a portion of the virtual address space. The Access Permission Table (APTable) bits, specifically APTable[1:0], play a critical role in controlling the permissions for subsequent levels of the translation table. These bits are located in the translation table descriptors and are used to enforce access permissions at different levels of the page table hierarchy.
In the context of the issue at hand, the goal is to set the upper levels of the translation table (e.g., the third level in a 4-level hierarchy) as read-only, such that any attempt to modify the Page Table Entries (PTEs) in the lower levels would trigger an access permission fault. This is particularly relevant in secure world implementations, where the secure world (EL3) aims to enforce fine-grained access control over the normal world (EL1) kernel’s ability to modify page tables.
The APTable bits are defined as follows:
- APTable[1:0]: These bits control the access permissions for subsequent levels of the translation table. When set to specific values, they can restrict write access to the lower-level page tables, thereby enforcing read-only permissions.
However, the ARMv8-A architecture manual specifies that the APTable bits do not restrict or change the values entered in the descriptors of subsequent levels. This means that while the APTable bits can influence the access permissions, they do not directly prevent modifications to the descriptors themselves. This subtle distinction is crucial for understanding why the access permission fault is not being triggered as expected.
Misalignment Between APTable Bits and Descriptor Modifications
The core issue arises from a misunderstanding of how the APTable bits interact with the translation table descriptors. The user in the discussion attempted to set the APTable bits in the upper-level translation table descriptors to enforce read-only permissions on the lower-level PTEs. However, the access permission fault was not triggered when modifying the PTEs, despite the APTable bits being set correctly.
This behavior can be attributed to the following factors:
-
APTable Bits Do Not Restrict Descriptor Modifications: As mentioned earlier, the APTable bits influence the access permissions for subsequent levels but do not prevent modifications to the descriptors themselves. This means that even if the APTable bits are set to enforce read-only permissions, the descriptors in the lower levels can still be modified unless additional measures are taken.
-
Hierarchical Permission Checks: The ARMv8-A architecture performs hierarchical permission checks when accessing memory. However, these checks are not always straightforward, especially when dealing with nested translation tables. The APTable bits in the upper levels may not propagate the intended permissions to the lower levels as expected, leading to inconsistent behavior.
-
TLB and Cache Coherency: The Translation Lookaside Buffer (TLB) and cache coherency mechanisms can also impact the enforcement of access permissions. If the TLB entries are not invalidated or the cache is not flushed correctly, the processor may not detect the updated permissions, leading to unexpected behavior.
Implementing Secure World Page Table Protection with APTable Bits
To address the issue of enforcing read-only permissions on the lower-level PTEs, the following steps can be taken:
-
Set APTable Bits in Upper-Level Descriptors: Ensure that the APTable bits in the upper-level translation table descriptors are set correctly to enforce read-only permissions. This involves setting the appropriate values for APTable[1:0] in the descriptors of the third-level translation tables.
-
Invalidate TLB Entries: After modifying the translation table descriptors, invalidate the TLB entries to ensure that the processor detects the updated permissions. This can be done using the
TLBI
(TLB Invalidate) instruction, which flushes the TLB entries for the specified address range. -
Flush Data Cache: Ensure that the data cache is flushed to maintain coherency between the cache and the main memory. This can be done using the
DC CIVAC
(Data Cache Clean and Invalidate by Virtual Address to PoC) instruction, which cleans and invalidates the cache entries for the specified address range. -
Handle Access Permission Faults in Secure World: Implement an exception handler in the secure world to handle access permission faults. When a fault occurs, the handler should switch to the secure world (EL3) and perform the necessary actions, such as logging the fault or enforcing additional security policies.
-
Verify Descriptor Modifications: After setting the APTable bits and invalidating the TLB and cache, verify that the descriptors in the lower levels cannot be modified. This can be done by attempting to write to the PTEs and checking if an access permission fault is triggered.
Example Implementation
The following example demonstrates how to set the APTable bits in the upper-level translation table descriptors and handle access permission faults in the secure world:
// Set APTable bits in the third-level translation table descriptor
void set_aptable_bits(uint64_t *descriptor) {
// Set APTable[1:0] to enforce read-only permissions
*descriptor |= (1 << 61); // Set APTable[1]
*descriptor |= (1 << 62); // Set APTable[0]
}
// Invalidate TLB entries for the specified address range
void invalidate_tlb(uint64_t va) {
asm volatile("TLBI VAE1IS, %0" : : "r" (va));
}
// Flush data cache for the specified address range
void flush_cache(uint64_t va) {
asm volatile("DC CIVAC, %0" : : "r" (va));
}
// Handle access permission faults in the secure world
void handle_access_permission_fault() {
// Switch to secure world (EL3)
asm volatile("SMC #0");
// Perform necessary actions, such as logging the fault or enforcing security policies
// ...
}
int main() {
// Allocate memory for the translation tables
uint64_t *p = vmalloc(4096);
uint64_t *q = vmalloc(4096);
// Set APTable bits in the third-level translation table descriptors
set_aptable_bits(p);
set_aptable_bits(q);
// Invalidate TLB entries and flush cache
invalidate_tlb((uint64_t)p);
invalidate_tlb((uint64_t)q);
flush_cache((uint64_t)p);
flush_cache((uint64_t)q);
// Attempt to modify the PTEs
*p = 10;
*q = 20;
// Check if an access permission fault is triggered
// ...
return 0;
}
Conclusion
The issue of enforcing read-only permissions on the lower-level PTEs in ARMv8-A translation tables is a complex one, requiring a deep understanding of the APTable bits, hierarchical permission checks, and TLB/cache coherency mechanisms. By correctly setting the APTable bits in the upper-level descriptors, invalidating TLB entries, flushing the cache, and handling access permission faults in the secure world, it is possible to achieve the desired level of protection. However, it is important to note that the APTable bits do not directly restrict modifications to the descriptors themselves, and additional measures may be required to enforce fine-grained access control.
In summary, the key to resolving this issue lies in a thorough understanding of the ARMv8-A translation table mechanics and careful implementation of the necessary steps to enforce access permissions. By following the guidelines outlined in this post, developers can effectively implement secure world page table protection and ensure that the normal world kernel cannot modify the PTEs without triggering an access permission fault.