CVE-2021-46927 is an important vulnerability that was discovered and quickly resolved in the Linux kernel, specifically within the Nitro Enclaves subsystem. This vulnerability revolves around the incorrect usage of the memory mapping (mmap) asserts in the kernel when handling user pages, leading to potential kernel panics and, consequently, Denial of Service (DoS) scenarios.
In this post, we'll take you step-by-step through how this bug happens, how the exploit could be triggered, the technical details behind it, and how the patch finally fixed the problem. We’ll also provide you with references and a sample code snippet, all in plain American English.
What Is Nitro Enclaves?
Nitro Enclaves is a technology from AWS to create isolated compute environments in EC2 instances for highly sensitive data. Those environments rely on proper Linux kernel memory management to guarantee high-level security.
How Did the Vulnerability Happen?
When using Nitro Enclaves, the kernel must manage user memory regions securely. Before the fix, Nitro Enclaves used the get_user_pages() function to pin user-space memory in kernel space. However, after the Linux kernel commit 5b78ed24e8ec ("mm/pagemap: add mmap_assert_locked() annotations to find_vma*()"), there was a new requirement: whenever code tried to access user memory, it had to *hold the mmap lock*.
If the mmap lock isn’t held, the kernel now checks and fires a critical kernel BUG, crashing the kernel:
static inline void mmap_assert_locked(struct mm_struct *mm)
{
lockdep_assert_held(&mm->mmap_lock);
VM_BUG_ON_MM(!rwsem_is_locked(&mm->mmap_lock), mm);
}
What Does This Mean?
Any call to get_user_pages() (which internally uses functions like find_vma()) without first taking the mmap_lock would now trigger a kernel panic.
The classic dmesg/panic output would look like
[ 62.521410] kernel BUG at include/linux/mmap_lock.h:156!
[ 62.538938] RIP: 001:find_vma+x32/x80
[ 62.605889] Call Trace:
[ 62.610956] ? lock_timer_base+x61/x80
[ 62.614106] find_extend_vma+x19/x80
[ 62.617195] __get_user_pages+x9b/x6a
[ 62.620356] __gup_longterm_locked+x42d/x450
[ 62.639541] ne_set_user_memory_region_ioctl.isra.+x225/x6a [nitro_enclaves]
[ 62.635776] ne_enclave_ioctl+x1cf/x6d7 [nitro_enclaves]
[ 62.639541] __x64_sys_ioctl+x82/xb
[ 62.642620] do_syscall_64+x3b/x90
[ 62.645642] entry_SYSCALL_64_after_hwframe+x44/xae
Who Could Exploit It
Google Project Zero and community researchers found the bug was reachable by any process with permissions to perform certain Nitro Enclaves ioctl commands. For example, a user program could ask the Nitro Enclaves device driver to register user memory, which would internally call the vulnerable function.
Here’s a simplified C code snippet to demonstrate how the bug could’ve been triggered
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define NE_SET_USER_MEMORY_REGION x123 // Example IOCTL, real value differs
int main() {
int fd = open("/dev/nitro_enclaves", O_RDWR);
if (fd < ) {
perror("open");
return 1;
}
// Provide user buffer (simulate untrusted input)
char *user_buffer = malloc(4096);
// Attempt ioctl triggering user memory region set
if (ioctl(fd, NE_SET_USER_MEMORY_REGION, user_buffer) < ) {
perror("ioctl");
} else {
printf("ioctl succeeded\n");
}
close(fd);
free(user_buffer);
return ;
}
When running this on a vulnerable kernel, the handler function in Nitro Enclaves would call get_user_pages() without holding the mmap_lock, resulting in an immediate kernel crash—effectively a DoS.
The Fix: How Was It Resolved?
The fix was straightforward once the new kernel rules were understood. get_user_pages_unlocked() should be used instead of get_user_pages(). This variant always takes and releases the mmap lock internally, matching the expectations from the kernel after commit 5b78ed24e8ec.
Before Patch (Vulnerable)
// BAD! May call get_user_pages() without locking mmap:
long ne_set_user_memory_region_io(struct ... *info) {
...
ret = get_user_pages(....);
...
}
After Patch (Safe)
// GOOD! This calls the _unlocked variant which does the right locking!
long ne_set_user_memory_region_io(struct ... *info) {
...
ret = get_user_pages_unlocked(....);
...
}
This change was pushed upstream, closing the vulnerability.
References and Original Commit Links
- CVE-2021-46927 on NVD
- AWS Nitro Enclaves Documentation
- Linux Kernel Commit: "mm/pagemap: add mmap_assert_locked()"
- Nitro Enclaves kernel code
Conclusion
CVE-2021-46927 is a great example of how subtle kernel API changes—like stricter mmap locking—can introduce security holes if not respected in custom drivers. Even well-isolated environments such as Nitro Enclaves depend on strict adherence to these rules, and skipping a lock can crash the whole system. Always double-check how kernel-side APIs evolve and update your code to match the latest expectations.
If you run Nitro Enclaves or custom kernel modules interacting with user memory, ensure you’re using the right locking and the correct APIs—such as get_user_pages_unlocked()! Patch your kernels if you haven't yet.
Timeline
Published on: 02/27/2024 10:15:07 UTC
Last modified on: 04/10/2024 16:25:32 UTC