• 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:

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:

#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]:

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.)

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:

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