Skip to main content
Vulnerability Database/CVE-2026-19574

CVE-2026-19574: ARM64 MMU Privilege Escalation Vulnerability

CVE-2026-19574 is a privilege escalation flaw in ARM64 MMU back-end that allows memory domain isolation bypass through ASID aliasing. This article covers the technical details, exploitation requirements, and mitigation steps.

Published:

CVE-2026-19574 Overview

CVE-2026-19574 affects the Zephyr RTOS ARM64 Memory Management Unit (MMU) back-end. The arch_mem_domain_init() function in arch/arm64/core/mmu.c assigns Address Space Identifiers (ASIDs) using a bare round-robin counter. With VM_ASID_BITS set to 8, only 255 ASIDs exist. Once the counter wraps, the kernel can assign an ASID that is still held by a live memory domain. Because domain-private mappings are installed as non-global (MT_NG), the ASID is the sole TLB tag separating one domain's cached translations from another's. The result is a breakdown of memory-domain isolation on CONFIG_USERSPACE builds.

Critical Impact

A user-mode thread in one memory domain can read and write memory in another aliased domain, bypassing the Zephyr userspace isolation boundary on ARM64.

Affected Products

  • Zephyr RTOS on ARM64 targets with CONFIG_USERSPACE enabled
  • Applications instantiating more than 255 memory domains over their lifetime
  • SMP ARM64 configurations where aliased domains may run concurrently on separate CPUs

Discovery Timeline

  • 2026-10-09 - CVE-2026-19574 published to the National Vulnerability Database (NVD)
  • 2026-10-09 - Last updated in NVD database

Technical Details for CVE-2026-19574

Vulnerability Analysis

The flaw is an improper access control weakness ([CWE-284]) in the ARM64 MMU back-end. Zephyr assigns ASIDs with a monotonically increasing next_asid counter and no check against live domains. When the 8-bit counter wraps past 255, arch_mem_domain_init() can hand an ASID to a new domain while an existing domain still owns the same tag. The context-switch routine z_arm64_swap_ptables() only flushes the TLB when the outgoing and incoming domains share the same ASID. For aliased domains A and C with an unrelated domain B in between, the schedule A → B → C skips the flush branch. Entries A populated remain resident while C executes with the same ASID tag. Under Symmetric Multi-Processing (SMP), two live domains sharing an ASID can additionally reside on two CPUs simultaneously, a state the ARM architecture does not permit for distinct translation-table sets.

Root Cause

The root cause is that arch_mem_domain_init() performs ASID allocation without consulting domain_list. The round-robin counter overflow is not detected, and no uniqueness check gates the assignment. Combined with the non-global (MT_NG) mapping policy, the ASID becomes the only entity distinguishing cached translations per domain.

Attack Vector

k_mem_domain_init() and k_mem_domain_deinit() are supervisor-only APIs and are not exposed as syscalls, so an unprivileged thread cannot directly drive the counter. Triggering the wrap requires the application itself to create more than 255 memory domains across its lifetime. Once two live domains alias, a user-mode thread in one domain gains read and write access to the other domain's partitions and thread stacks with that domain's permissions, defeating the memory-domain isolation boundary.

c
// Patch excerpt: scan domain_list and advance past ASIDs already in use
    struct arm_mmu_ptables *domain_ptables = &domain->arch.ptables;
    k_spinlock_key_t key;
    uint16_t asid;
    uint16_t candidate;
    bool found = false;

    key = k_spin_lock(&xlat_lock);

    /*
     * Find a free ASID. The round-robin counter may point to an ASID
     * still in use by a live domain, so scan domain_list and advance
     * until an unused ASID is found.
     */
    candidate = next_asid;
    do {
        sys_snode_t *node;
        struct arch_mem_domain *arch_domain;
        bool in_use = false;

        SYS_SLIST_FOR_EACH_NODE(&domain_list, node) {
            arch_domain = CONTAINER_OF(node, struct arch_mem_domain, node);
            if (get_asid(arch_domain->ptables.ttbr0) == candidate) {
                in_use = true;
                break;

Source: Zephyr commit 805054b

Detection Methods for CVE-2026-19574

Indicators of Compromise

  • Unexpected reads or writes to memory regions owned by a different memory domain than the acting thread
  • Thread stack corruption in processes that never share partitions with the modifying thread
  • Divergent behavior on SMP ARM64 systems where two domains with distinct partitions appear to share translations

Detection Strategies

  • Audit application code for cumulative k_mem_domain_init() call counts exceeding 255 across the device lifetime
  • Instrument arch_mem_domain_init() to log the assigned ASID and compare against the live domain_list
  • Add runtime assertions that no two entries in domain_list share the value returned by get_asid(ptables.ttbr0)

Monitoring Recommendations

  • Track memory domain creation and teardown events in long-running Zephyr firmware
  • Enable Zephyr MMU_DEBUG output during qualification testing to surface ASID allocation patterns
  • Review crash telemetry for faults in user threads that reference addresses outside their declared partitions

How to Mitigate CVE-2026-19574

Immediate Actions Required

  • Apply the upstream Zephyr patch that scans domain_list before assigning an ASID and returns -ENOMEM when none are free
  • Rebuild and redeploy affected ARM64 firmware images with CONFIG_USERSPACE enabled
  • Handle the new -ENOMEM return path from k_mem_domain_init() in application code

Patch Information

The fix is published in Zephyr commit 805054b8ed89dd5441fbca36b3cd3be7e4fb4100 and documented in GitHub Security Advisory GHSA-923h-8c2j-pcm5. The patch replaces the bare round-robin allocation with a scan of the live domain_list, advances past in-use ASIDs, and fails closed when the ASID space is exhausted.

Workarounds

  • Constrain application design so that fewer than 255 memory domains are created across the device lifetime
  • Reuse existing memory domains instead of calling k_mem_domain_init() for each short-lived task
  • Disable CONFIG_USERSPACE on ARM64 builds where domain-based isolation is not required
bash
# Fetch and apply the upstream fix in a Zephyr workspace
cd zephyr
git fetch origin
git cherry-pick 805054b8ed89dd5441fbca36b3cd3be7e4fb4100
west build -b <arm64_board> samples/userspace/hello_world --pristine

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.