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