The Linux kernel, at the very heart of countless systems from Android smartphones to massive cloud infrastructure, is known for its robustness and security. But even the best kernel isn't perfect: subtle bugs appear and get fixed regularly. One such recently resolved but important issue is CVE-2024-46847, a vulnerability tied to the kernel's memory management code (the vmalloc subsystem). In this post, we’ll break down what happened, how it could be exploited, and how it was fixed—all in plain English.
What Is the Problem?
The Linux kernel allows memory to be mapped into a process's address space using various subsystems, such as vmalloc. To manage these mappings efficiently, the kernel uses special structures called vmap_blocks. These are sometimes kept in per-CPU queues (vmap_block_queues), which can be touched by many CPUs at once.
Recently, a change (commit 8c61291fd850) added a new field to the vmap_block structure: the cpu id of the initializing processor. The idea was to track which CPU allocated a given block.
However, the code that adds a newly allocated block to the queue did so before fully initializing the structure. Specifically, it would put the new block in the shared xarray queue (vmap_block_queue) and only then set the cpu field.
If another CPU tried to process the queue at just the wrong moment (say, during an operation like vm_unmap_aliases()), it might read a still-uninitialized cpu field, turning it into a random index—leading to an out-of-bounds (OOB) access and possible kernel crash or other misbehavior.
On affected systems, this showed up as a kernel error
Internal error: UBSAN: array index out of bounds: 00000000f2005512 [#1] PREEMPT SMP
Call trace:
purge_fragmented_block+x204/x21c
_vm_unmap_aliases+x170/x378
vm_unmap_aliases+x1c/x28
change_memory_common+x1dc/x26c
set_memory_ro+x18/x24
module_enable_ro+x98/x238
do_init_module+x1b/x310
UBSAN is the Undefined Behavior Sanitizer; here it's telling us that code tried to index outside the array bounds—dangerous in C code running in the kernel.
How Could This Be Exploited?
This specific race condition could theoretically be triggered by an attacker who has local code execution (such as a rogue app on Android) and can trigger mapping and unmapping operations at precisely the right times, trying to cause concurrent accesses.
- Out-of-bounds accesses can sometimes be escalated to privilege escalation, though in this case, the window is tiny, and the bug is "just" a use-after-init bug (bad for reliability, possibly bad for security).
- Denial of service (crash): By hitting the race, an attacker might kernel panic the system by causing the OOB access.
Information leak: Reading uninitialized or neighboring structures.
Exploiting this requires precise timing and a deep understanding of kernel internals, but in carefully controlled environments (like on Android with constant memory allocations), it isn’t impossible.
The Fix
The solution, merged in this commit, is simple but important. The idea: Initialize all required struct fields before exposing the struct to other CPUs.
Before (vulnerable code)
struct vmap_block *vb = kzalloc(sizeof(*vb), GFP_KERNEL);
if (!vb)
return NULL;
xa_store(&vbq->xa, idx, vb, GFP_KERNEL);
// The cpu field is still uninitialized here!
vb->cpu = smp_processor_id();
After (fixed code)
struct vmap_block *vb = kzalloc(sizeof(*vb), GFP_KERNEL);
if (!vb)
return NULL;
// Initialize vb->cpu *before* publishing to the xarray
vb->cpu = smp_processor_id();
xa_store(&vbq->xa, idx, vb, GFP_KERNEL);
By putting vb->cpu = smp_processor_id(); before publishing the block to the queue, you guarantee that every field is valid before other CPUs can see it.
References
- CVE-2024-46847 at NVD
- Linux kernel commit fixing CVE-2024-46847
- The problematic commit
Real-World Impact and Advice
- Android phones were especially prone to this, as they do a lot of vmalloc/vunmap activity and run on multi-core chips. If your devices run unpatched kernels, you can be at risk.
- All kernel developers and distribution maintainers should update to include this fix as soon as possible.
Summary
CVE-2024-46847 is a classic example of how subtle concurrency bugs can sneak into even the most heavily reviewed projects. It serves as a reminder to always initialize memory structures fully before making them visible in concurrent contexts. The fix is straightforward, but the bug could have enabled out-of-bounds access, kernel crashes, or leaks in certain scenarios. If you run or maintain Linux-based systems, patching is recommended.
Timeline
Published on: 09/27/2024 13:15:16 UTC
Last modified on: 11/05/2024 09:47:45 UTC