CVE-2023-48409 Mali GPU overflow

CVE-2023-48409 Mali GPU overflow

2025년 7월 13일

ref #

이 취약점의 PoC는 pixel 7, pixel 8 의 특정 이미지에서 실행 가능한 코드를 담고있다. 글을 좀 더 찾아보니 pixel 6 pro에서도 악용한 글이 있었고 내가 가지고있는 pixel 6a 에서도 취약점이 존재할 수 있을 것 같아서 따라해볼 예정이다.


CVE-2023-48409 #

취약점 개요 #

Mali GPU는 저가 안드로이드 기기에서 자주 사용되는 ARM GPU 이다. 이 취약점은 Mali GPU 커널 드라이버 코드 중 Pixel 전용 커스터마이징 확장 코드(벤더용 커널 모듈)에서 추가된 ioctl에서 발견됐다.

gpu_pixel_handle_buffer_liveness_update_ioctl() 에서 64bit 정수 오버플로우가 발생하여 잘못 계산된 크기로 힙을 할당하고, 할당된 버퍼의 앞쪽에 OOB가 발생하여 인접한 커널 객체를 덮어쓰게 된다. 이로인해 페이지테이블이나 cred 구조체를 수정하여 SELinux를 꺼버리고, root 셸까지 획득할 수 있게된다.

패치커밋 에서 조치되었으며, pixel 6a 기준으로는 qpr1 버전의 이미지부터 커밋이 포함되어있다.

1PS C:\Users\kdh\Documents\GitHub\gpu> git branch -a --contains 5dec6c | grep bluejay
2  remotes/origin/android-gs-bluejay-5.10-android14-qpr1
3  remotes/origin/android-gs-bluejay-5.10-android14-qpr1-beta
4  remotes/origin/android-gs-bluejay-5.10-android14-qpr2
5  remotes/origin/android-gs-bluejay-5.10-android14-qpr2-beta

커밋이 포함된 기기들은 tegu, tangorpro, shusky, pantah, raviole, lynx, felix, comet, caimito, bluejay, akita 이런식인데, 두개의 기기에서 같이 사용하는 버전은 이름이 합쳐진 것을 알 수 있다.
ex) shusky = shiba (Pixel 8) + husky (Pixel 8 Pro)

UP1A = 최초릴리즈, UQ1A = qpr1, AP1A = qpr2, AP2A = qpr3 이기 때문에 UP1A 로 시작하는 이미지를 사용하면 취약점을 트리거할 수 있을것이다.


사전지식 #

ioctl #

커널과 유저영역 프로그램 사이에서 입출력 제어 명령을 전달하기 위한 시스템콜이다.
일반 파일을 대상으로 사용하는 read, write 로 처리하기 어려운 디바이스별 특수한 제어 작업을 수 행할때 사용한다.

 1// 터미널 크기 조회
 2struct winsize ws;
 3ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); 
 4
 5// 파일 속성 조회
 6int flags;
 7ioctl(fd, FS_IOC_GETFLAGS, &flags); 
 8
 9// Mali GPU의 liveness 업데이트 명령 (보통 vulkan, opengl 같은 API를 통해 사용됨)
10int fd = open("/dev/mali0", O_RDWR);
11struct update_data data = { ... };
12ioctl(fd, GPU_IOCTL_LIVENESS_UPDATE, &data);

CONFIG_HARDENED_USERCOPY #


취약 코드 #

 1int gpu_pixel_handle_buffer_liveness_update_ioctl(struct kbase_context* kctx,
 2                                                  struct kbase_ioctl_buffer_liveness_update* update)
 3{
 4	int err = 0;
 5	struct gpu_slc_liveness_update_info info;
 6	u64* buff;
 7
 8	/* Compute the sizes of the user space arrays that we need to copy */
 9	u64 const buffer_info_size = sizeof(u64) * update->buffer_count;			// [1]
10	u64 const live_ranges_size =
11	    sizeof(struct kbase_pixel_gpu_slc_liveness_mark) * update->live_ranges_count;
12	/* Nothing to do */
13	if (!buffer_info_size || !live_ranges_size)
14		goto done;
15
16	/* Guard against nullptr */
17	if (!update->live_ranges_address || !update->buffer_va_address || !update->buffer_sizes_address)
18		goto done;
19	/* Allocate the memory we require to copy from user space */
20	buff = kmalloc(buffer_info_size * 2 + live_ranges_size, GFP_KERNEL);			// [2]
21
22	/* Set up the info struct by pointing into the allocation. All 8 byte aligned */
23	info = (struct gpu_slc_liveness_update_info){						// [3]
24	    .buffer_va = buff,
25	    .buffer_sizes = buff + update->buffer_count,
26	    .live_ranges = (struct kbase_pixel_gpu_slc_liveness_mark*)(buff + update->buffer_count * 2),
27	    .live_ranges_count = update->live_ranges_count,
28	};
29	/* Copy the data from user space */
30	err =
31	    copy_from_user(info.live_ranges, u64_to_user_ptr(update->live_ranges_address), live_ranges_size);
32	if (err) {
33		dev_err(kctx->kbdev->dev, "pixel: failed to copy live ranges");
34		err = -EFAULT;
35		goto done;									// [4]
36	}
37    
38...
39
40done:
41	kfree(buff);
42	return err;
43}

패치 내역 #


<br>

## pixel 6a에서 트리거하기
comments powered by Disqus