Talos Vulnerability Report

TALOS-2022-1497

Microsoft Azure Sphere /proc/fdt mmap operation out-of-bounds read vulnerability

August 17, 2022
CVE Number

CVE-2022-35821

SUMMARY

An out-of-bounds read vulnerability exists in the /proc/fdt mmap operation functionality of Microsoft Azure Sphere 22.02. A specially-crafted mmap invocation can lead to a kernel memory leak. An attacker can issue an mmap call to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Microsoft Azure Sphere 22.02

PRODUCT URLS

Azure Sphere - https://azure.microsoft.com/en-us/services/azure-sphere/

CVSSv3 SCORE

4.4 - CVSS:3.0/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N

CWE

CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

DETAILS

Microsoft’s Azure Sphere is a platform for the development of internet-of-things applications. It features a custom SoC that consists of a set of cores that run both high-level and real-time applications, enforces security and manages encryption (among other functions). The high-level applications execute on a custom Linux-based OS, with several modifications to make it smaller and more secure, specifically for IoT applications.

The Azure Sphere Linux Kernel exposes a /proc/fdt file to allow Flattened Device Tree (FDT) access from userland. The file driver backing /proc/fdt implements the following functions:

static const struct proc_ops fops_fdt = { // [1]
   .proc_read =    read_file_fdt,
   .proc_open =    simple_open,
   .proc_lseek =   default_llseek,
   .proc_mmap =    simple_mmap,           // [2]
};

While the simple_open function is implemented by the mainline Linux kernel, the simple_mmap function at [2] is Azure Sphere specific, which will be important later on. Continuing on, the /proc/fdt driver is implemented as such by of_fdt_raw_procfs_init:

static int __init of_fdt_raw_procfs_init(void)
{
   struct proc_dir_entry *proc_entry;

   if (!initial_boot_params)
       return 0;

   if (of_fdt_crc32 != crc32_be(~0, initial_boot_params,
                    fdt_totalsize(initial_boot_params))) {
       pr_warn("not creating '/proc/fdt': CRC check failed\n");
       return 0;
   }
   proc_entry = proc_create("fdt", 0, NULL, &fops_fdt);
   if (proc_entry != NULL)
       proc_set_size(proc_entry, fdt_totalsize(initial_boot_params));   // [3]

   return 0;
}

At [3] we see that proc_set_size is called to set the size of this proc entry to fdt_totalsize(initial_boot_params):

void proc_set_size(struct proc_dir_entry *de, loff_t size)
{
    de->size = size;
}

While we can see that several operations are defined at [1] in our initial struct proc_ops fops_fdt, for this advisory we’re interested specifically in the mmap operation [2], which is implemented by the simple_mmap function.

static int simple_mmap(struct file *filp, struct vm_area_struct *vma)
{
   unsigned long pfn;
   size_t size = vma->vm_end - vma->vm_start;                      // [4]
   phys_addr_t offset = (phys_addr_t) vma->vm_pgoff << PAGE_SHIFT;

   if (initial_boot_params == NULL)
       return -ENOENT;

   pfn = virt_to_phys(initial_boot_params) >> PAGE_SHIFT;

   // offset overflow check
   if (offset >> PAGE_SHIFT != vma->vm_pgoff)
       return -EINVAL;

   // ensure the mmap is read-only
   if ((vma->vm_page_prot & PAGE_READONLY) != PAGE_READONLY)
       return -EPERM;

   if (remap_pfn_range(vma, vma->vm_start,
                       pfn, size,                                  // [5]
                       vma->vm_page_prot)) {
       return -EAGAIN;
   }

   return 0;
}

When mmap is called on a /proc/fdt file-descriptor, the function above will be executed. At [4] the size of the page is calculated, based on the bounds computed from the size as specified by the mmap userland invocation. In this function, there isn’t any check on size against the fdt size (the one saved via proc_set_size at [3]). At [5] the mapping is performed, using the unchecked size parameter.

This lack of size checking allows a userland process to open /proc/fdt and mmap an arbitrarily large memory, which will read out-of-bounds of the fdt memory mapping, leaking kernel memory. If the mapping is made large enough, it is possible to dump the whole kernel memory.

In order to open /proc/fdt, one needs root privileges, however, since /proc/fdt is only readable by the root user. Commonly, being root in a system already implies ability to load modules, thus having the ability to execute kernel code. In Azure Sphere’s case, it’s not possible to insert modules at runtime, and there are a set of additional capabilities (i.e. AZURE_SPHERE_CAPABILITIES) that are added to processes, to isolate userland processes and prevent them access to kernel functionality. For these reasons, there is a defacto security boundary that must still be traversed by attackers even if they achieve root privileges, which makes this a legitimate vulnerability in Azure’s context. It is worth noting also that in order to trigger this vulnerability, the root user does not need any special capability (neither Azure capabilities, nor Linux capabilities).

TIMELINE

2022-04-04 - Vendor Disclosure
2022-08-09 - Vendor Patch Release
2022-08-17 - Public Release

Credit

Discovered by Claudio Bozzato and Lilith &gt;_&gt; of Cisco Talos.