CVE-2021-46910 - Linux Kernel ARM Vulnerability in kmap_local() with DEBUG_KMAP_LOCAL Enabled

In early 2021, engineers working on the Linux kernel discovered a tricky bug on ARM devices, now tracked as CVE-2021-46910. The vulnerability affects how per-CPU virtual memory is set up for temporary mappings (using kmap_local()), especially when extra debugging checks are enabled. While the bug has now been fixed, understanding what went wrong is essential for anyone dealing with embedded or ARM-based Linux systems.

Background: What Went Wrong?

The kmap_local() function offers a way for the kernel to temporarily map high memory pages into a process's address space. It uses a system known as the "fixmap region." For ARM, if you turned on debugging with CONFIG_DEBUG_KMAP_LOCAL, the number of per-CPU slots doubled—half for actual use, half as "guard regions" to help catch bugs.

But here’s the catch: on systems with lots of CPUs (like NR_CPUS=32), this region _grew too large_. The fixmap region started growing downward in memory, bumping into areas used by other critical kernel systems, such as the Device Tree or EFI tables.

That caused the kernel to _misinterpret_ normal memory blocks as fixmap table entries—a recipe for chaos! When that happened, the kernel would crash with impossible-to-debug errors during early boot, especially on ARM devices using EFI.

Here’s what you might see on a failed boot

ftrace: allocating 45548 entries in 89 pages
8<--- cut here ---
Unable to handle kernel paging request at virtual address fc6006f
pgd = (ptrval)
[fc6006f] *pgd=80000040207003, *pmd=00000000
Internal error: Oops: a06 [#1] SMP ARM
...
PC is at cpu_ca15_set_pte_ext+x24/x30
LR is at __set_fixmap+xe4/x118
...

Translation: the kernel tried to access a memory region that wasn’t valid, all because its bookkeeping had gone haywire.

The problematic logic allocating fixmap slots looked something like this (simplified for clarity)

#ifdef CONFIG_DEBUG_KMAP_LOCAL
    #define KM_LOCAL_PER_CPU_FIXMAP_SLOTS (KM_LOCAL_FIXMAP_SLOTS * 2)
#else
    #define KM_LOCAL_PER_CPU_FIXMAP_SLOTS KM_LOCAL_FIXMAP_SLOTS
#endif

#define TOTAL_FIXMAP_SLOTS (KM_LOCAL_PER_CPU_FIXMAP_SLOTS * CONFIG_NR_CPUS)

If CONFIG_NR_CPUS was 32 (or even higher), and debugging was enabled, this could easily outgrow the reserved fixmap space, leading to region overlap.

Limit CONFIG_NR_CPUS to 16 when CONFIG_DEBUG_KMAP_LOCAL is on for ARM.

2. Fix the sanity checks (BUILD_BUG_ON) so they correctly compare the start and end addresses of the fixmap region.

Here’s the essence of the fix (from the commit)

#if defined(CONFIG_DEBUG_KMAP_LOCAL) && CONFIG_NR_CPUS > 16
    #error "Too many CPUs with DEBUG_KMAP_LOCAL enabled! Reduce NR_CPUS to 16 or below."
#endif

And the improved build check ensures the fixmap won’t spill over

BUILD_BUG_ON(FIXMAP_START - 
    KM_LOCAL_PER_CPU_FIXMAP_SLOTS * CONFIG_NR_CPUS * PAGE_SIZE
    < FIXMAP_END);

Impact

- Denial of Service: Any ARM system built with CONFIG_DEBUG_KMAP_LOCAL=y and NR_CPUS set above 16 could hang or crash during boot, especially on EFI-based systems.
- Privilege Escalation: Not directly exploitable by ordinary users. The risk mainly concerns system stability, not direct code execution.

Exploitation Example

It’s _not_ the kind of bug that a normal user could exploit with a shell script. But a malicious or misinformed admin (or automated system) who set these kernel config flags could make a device unbootable, causing potential disruption, denial-of-service, or even bricking of headless embedded devices.

Official Patch and Commit:

linux-arm-kernel: ARM: 9063/1: mm: reduce maximum number of CPUs if DEBUG_KMAP_LOCAL is enabled

CVE Record:

CVE-2021-46910 at NIST NVD

Linux Kernel Documentation:

Linux Kernel - Fixmap Mechanism

Summary Table

| Condition | Vulnerable? |
|---------------------------------------|:-----------:|
| ARM CPU, DEBUG_KMAP_LOCAL off | No |
| ARM CPU, DEBUG_KMAP_LOCAL on, NR_CPUS > 16 | Yes |
| Non-ARM arch, or NR_CPUS16 | No |

Test any custom configurations on non-critical hardware.

Bottom line:
CVE-2021-46910 is a classic example of how debugging code can unexpectedly break real systems if you’re not careful with compile-time limits. If you’re building or deploying ARM Linux devices with custom kernels, _stay aware_, don’t over-allocate CPUs with debug flags on, and always watch the latest kernel patches!


*Exclusive longform explainer by [Assistant], April 2024. Please reference original kernel sources for further technical detail.*

Timeline

Published on: 02/27/2024 07:15:07 UTC
Last modified on: 04/17/2024 16:55:44 UTC