Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Change-Id: If6cb7bc2d3703785794ac2c98e91cd3458c809e0
  • Loading branch information
Xingyu Jin committed Oct 17, 2024
1 parent f41821f commit bf4c0e4
Showing 1 changed file with 251 additions and 0 deletions.
251 changes: 251 additions & 0 deletions 0day-RCAs/2024/CVE-2024-44068.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# CVE-2024-44068: Samsung m2m1shot_scaler0 device driver page use-after-free in Android

- Xingyu Jin, Google Devices & Services Security Research
- Clement Lecigene, Google Threat Analysis Group

## The Basics

**Disclosure or Patch Date:** Oct 07, 2024

**Product:** Samsung Android

**Advisory:** https://semiconductor.samsung.com/support/quality-support/product-security-updates/cve-2024-44068/

**Affected Versions:** Samsung Exynos (9820, 9825, 980, 990, 850, W920), pre SMR-Oct-2024

**First Patched Version:** SMR-Oct-2024

**Issue/Bug Report:** N/A

**Patch CL:** N/A

**Bug-Introducing CL:** N/A

**Reporter(s):** Xingyu Jin and Clement Lecigne

## The Code

**Proof-of-concept:** N/A

**Exploit sample:** N/A

**Did you have access to the exploit sample when doing the analysis?** Yes

## The Vulnerability

**Bug class:** Use-After-Free

**Vulnerability details:**

By interacting with the IOCTL `M2M1SHOT_IOC_PROCESS`, the driver which provides hardware acceleration for media functions like JPEG decoding and image scaling may map the userspace pages to I/O pages, execute a firmware command and tear down mapped I/O pages.

The following IOCTL call is sent to the driver:

```c
m2m1shot_task.fmt_cap.fmt = V4L2_PIX_FMT_YUV420M;
m2m1shot_task.buf_cap.num_planes = 3;
m2m1shot_task.buf_cap.type = M2M1SHOT_BUFFER_USERPTR;
m2m1shot_task.buf_cap.plane[SC_PLANE_Y].userptr = ion_map;
m2m1shot_task.buf_cap.plane[SC_PLANE_Y].len = 0x8000LL;
m2m1shot_task.buf_cap.plane[SC_PLANE_CB].userptr = buf_cap_map_2;
m2m1shot_task.buf_cap.plane[SC_PLANE_CB].len = 0x8000LL;
m2m1shot_task.buf_cap.plane[SC_PLANE_CR].userptr = buf_cap_map_3;
m2m1shot_task.buf_cap.plane[SC_PLANE_CR].len = 0x8000LL;
m2m1shot_task.fmt_out.fmt = V4L2_PIX_FMT_YUV420M;
m2m1shot_task.buf_out.num_planes = 3;
m2m1shot_task.buf_out.type = M2M1SHOT_BUFFER_USERPTR;
m2m1shot_task.buf_out.plane[SC_PLANE_Y].userptr = buf_out_map_1;
m2m1shot_task.buf_out.plane[SC_PLANE_Y].len = 0x8000LL;
m2m1shot_task.buf_out.plane[SC_PLANE_CB].userptr = buf_out_map_2;
m2m1shot_task.buf_out.plane[SC_PLANE_CB].len = 0x8000LL;
m2m1shot_task.buf_out.plane[SC_PLANE_CR].userptr = buf_out_map_3;
m2m1shot_task.buf_out.plane[SC_PLANE_CR].len = 0x8000LL;
m2m1shot_task.op.op = M2M1SHOT_OP_CSC_NARROW;

ioctl_ret = ioctl(m2m1shot_scaler0_fd, 0xC0C04D00uLL, &m2m1shot_task);
```

First, the driver parses `m2m1shot_task.buf_cap` and maps three sets of I/O memory with each set containing 8 pages. Similarly, the driver also parses `m2m1shot_task.buf_out` and maps I/O memory correspondingly.

Second, the firmware executes the command based on the `op` value `M2M1SHOT_OP_CSC_NARROW` and the `format` value `V4L2_PIX_FMT_YUV420M`. It copies the memory content from `m2m1shot_task.buf_out` to `m2m1shot_task.buf_cap` one by one. For example, the firmware copies data from the I/O pages mapped from `m2m1shot_task.buf_out.plane[0].userptr` to the I/O pages mapped from `m2m1shot_task.buf_cap.plane[0].userptr`.

To establish the I/O memory mapping, the driver function `sysmmu_map_pte` is called through the call chain `m2m1shot_dma_addr_map` -> `exynos_iovmm_map_userptr` -> `exynos_iommu_map_userptr` -> `sysmmu_map_pud`:

```c
#define mk_lv2ent_pfnmap(pent) (*(pent) |= (1 << 5)) /* unused field */

static int sysmmu_map_pte(struct mm_struct *mm,
pmd_t *pmd, unsigned long addr, unsigned long end,
struct exynos_iommu_domain *domain, sysmmu_iova_t iova, int prot)
{
pte_t *pte;
int ret = 0;
spinlock_t *ptl;
bool write = !!(prot & IOMMU_WRITE);
bool pfnmap = !!(prot & IOMMU_PFNMAP); /** [1] **/ If vma->vm_flags & VM_PFNMAP is true, exynos_iovmm_map_userptr appends the IOMMU_PFNMAP flag to prot.
bool shareable = !!(prot & IOMMU_CACHE);
unsigned int fault_flag = write ? FAULT_FLAG_WRITE : 0;
sysmmu_pte_t *ent, *ent_beg;

pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);
if (!pte)
return -ENOMEM;

ent = alloc_lv2entry_userptr(domain, iova);
if (IS_ERR(ent)) {
ret = PTR_ERR(ent);
goto err;
}

ent_beg = ent;

do {
if (pte_none(*pte) || !pte_present(*pte) ||
(write && !pte_write(*pte))) {
int cnt = 0;
int maxcnt = 1;

if (pfnmap) {
ret = -EFAULT;
goto err;
}

while (cnt++ < maxcnt) {
spin_unlock(ptl);
/* find_vma() always successes */
ret = handle_mm_fault(find_vma(mm, addr),
addr, fault_flag);
spin_lock(ptl);
if (ret & VM_FAULT_ERROR) {
ret = mm_fault_translate(ret);
goto err;
} else {
ret = 0;
}
[...]
}
}

BUG_ON(!lv2ent_fault(ent));

*ent = mk_lv2ent_spage(pte_pfn(*pte) << PAGE_SHIFT);

if (!pfnmap)
get_page(pte_page(*pte));
else
mk_lv2ent_pfnmap(ent); /** [2] **/ For PFNMAP pages, the page reference count is not elevated.

[...]
} while (pte++, addr += PAGE_SIZE, addr != end);

pgtable_flush(ent_beg, ent);
err:
pte_unmap_unlock(pte - 1, ptl);
return ret;
}
```
Unfortunately there's a bug in `sysmmu_map_pte`: the page reference count is not incremented for PFNMAP pages [1][2]. The driver only decrements the page reference count for non-PFNMAP pages when tearing down the I/O virtual memory in `exynos_iommu_unmap_userptr` [3]:
```c
void exynos_iommu_unmap_userptr(struct iommu_domain *dom,
dma_addr_t d_iova, size_t size)
{
struct exynos_iommu_domain *domain = to_exynos_domain(dom);
sysmmu_iova_t iova = (sysmmu_iova_t)d_iova;
sysmmu_pte_t *sent = section_entry(domain->pgtable, iova);
unsigned int entries = (unsigned int)(size >> SPAGE_ORDER);
dma_addr_t start = d_iova;
while (entries > 0) {
[...]
pent = page_entry(sent, iova);
for (i = 0; i < lv2ents; i++, pent++) {
/* ignore fault entries */
if (lv2ent_fault(pent))
continue;
BUG_ON(!lv2ent_small(pent));
if (!lv2ent_pfnmap(pent))
put_page(phys_to_page(spage_phys(pent))); /** [3] **/ put_page only applies on the non-PFNMAP pages.
*pent = 0;
}
```

An attacker can allocate PFNMAP pages (e.g. ION), map them to I/O virtual memory and free the pages by `munmap` in the meantime. Thus, the I/O virtual pages may map to freed physical pages.

**Patch analysis:**: N/A

**Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:**

The code audit likely revealed a bug in the memory management, specifically in how the driver setups the page mapping.

**(Historical/present/future) context of bug:**

This 0-day exploit is part of an EoP chain. The actor is able to execute arbitrary code in a privileged `cameraserver` process. The exploit also renamed the process name itself to “vendor.samsung.hardware.camera.provider@3.0-service”, probably for anti-forensic purposes.

The `m2m1shot_scaler0` exploit works on the S10 (G973FXXSGHWC2).

## The Exploit

(The terms *exploit primitive*, *exploit strategy*, *exploit technique*, and *exploit flow* are [defined here](https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html).)

**Exploit strategy (or strategies):**

The exploit unmaps PFNMAP pages and triggers a page use-after-free where the I/O virtual pages mapped by `m2m1shot_task.buf_cap.plane[0].userptr` may map to freed physical pages.

Next, the exploit uses `M2M1SHOT_OP_CSC_NARROW` firmware command with the format `V4L2_PIX_FMT_YUV420M` to copy data from `m2m1shot_task.buf_out.plane[0].userptr` filled with a PMD entry to the I/O virtual pages mapped by `m2m1shot_task.buf_cap.plane[0].userptr`. By spamming a number of page tables, it means the exploit may overwrite a PMD entry to a page table in-use and implement Kernel Space Mirroring Attack (KSMA).

**Exploit flow:**

The I/O virtual page mapping and executing firmware command is done in one IOCTL call. After the operation is complete, the driver will tear down all mapped I/O virtual pages. It means the exploit must call `munmap` during the `ioctl` call in the right timing.

To increase the chance of the exploitation, `m2m1shot_task.buf_cap.plane[0].userptr` is set by a PFNMAP memory and `m2m1shot_task.buf_cap.plane[1].userptr` is allocated by `mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0)`. When the driver sets up the I/O memory for `m2m1shot_task.buf_cap.plane[1].userptr`, it means the PFNMAP memory is already mapped to the I/O memory.

Because the pages are not backed before the access, the virtual pages from `m2m1shot_task.buf_cap.plane[1].userptr` are not backed with physical pages until the driver starts handling the I/O memory mapping. The exploit abuses `mincore` syscall to determine the timing when `m2m1shot_task.buf_cap.plane[1].userptr` is mapped by the driver:

```c
do {
mincore(m2m1shot_task.buf_cap.plane[1].userptr, 0x1000, vec);
} while (!vec[0]);
munmap(m2m1shot_task.buf_cap.plane[0].userptr, 0x8000);
```
**Known cases of the same exploit flow:** Yes (KSMA)
**Part of an exploit chain?** Yes
## The Next Steps
### Variant analysis
**Areas/approach for variant analysis (and why):** The code logic is not complex, researchers can audit the source code or fuzz all ioctl calls.
**Found variants:** N/A
### Structural improvements
What are structural improvements such as ways to kill the bug class, prevent the introduction of this vulnerability, mitigate the exploit flow, make this type of vulnerability harder to exploit, etc.?
**Ideas to kill the bug class:** Properly review the object reference count management.
**Ideas to mitigate the exploit flow:** N/A
**Other potential improvements:** N/A
### 0-day detection methods
What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected **as a 0-day**?
Potential signal: Trace every IOCTL call for identifying and validating arguments.
## Other References
N/A

0 comments on commit bf4c0e4

Please sign in to comment.