CVE-2024-23848

· omacs's blog

#1day

# Contents

  1. Introduction
  2. Background
  3. Root Cause Analysis
  4. References

# Introduction

본 취약점은 linux의 HDMI Consumer Electronics Control (CEC) framework에서 발생한 취약점으로, CEC 디바이스를 release할 때 적절한 lock이 걸려 있지 않아 발생하였다[1, 2].

CEC는 HDMI에서 사용되는 프로토콜 중 하나로, HDMI connectors는 이를 위한 핀을 하나 제공한다. 이 프로토콜은 HDMI 케이블로 연결된 디바이스들 간의 통신에 사용된다[3].

CEC를 사용하기 위해서는 관련 디바이스 파일을 열어야 하며, 특정 동작의 경우 관련 권한이 요구될 수 있다. CEC를 사용하는 디바이스들은 CEC message를 송수신하여 통신하게 된다[3].

CEC message는 큐에 저장되었다가 송수신되는데, 이 큐는 CEC filehandle의 멤버로 존재한다. 그런데 CEC 디바이스를 release할 때 CEC filehandle에 대한 lock이 존재하지 않아 race condition이 발생할 수 있다. 그리고 release 과정에서 filehandle이 free되므로 UAF가 발생할 수 있다[2, 4].

# Background

CEC는 HDMI로 연결된 디바이스들이 통신하기 위한 프로토콜로, CEC message를 사용하여 통신하게 된다[3, 4].

CEC를 사용하는 방법은 다른 디바이스를 사용하는 방법과 다르지 않다. 즉, 디바이스 파일을 open system call을 사용해서 열고 ioctl system call을 사용하여 디바이스를 제어하고 close system call을 사용해서 디바이스를 release하는 것이다. 이 중 ioctl system call을 호출할 때 사용 가능한 command 중 CEC message와 관련된 것은 다음과 같다[2]:

@ CEC_ADAP_S_PHYS_ADDR: Sets a new physical address; it requires CEC_CAP_PHYS_ADDR and file descriptor to be in initiator mode

@ CEC_S_MODE: There are Initiator modes and Follower modes; Initiator mode is CEC_MODE_NO_INITIATOR, CEC_MODE_INITIATOR (default), CEC_MODE_EXCL_INITIATOR

@ CEC_RECEIVE: Received messasge can be a message received form another CEC device or the result of an earlier non-blocking transmit

@ CEC_TRANSMIT: Send CEC message; it requires CEC_CAP_TRANSMIT

이때 수신된 CEC message를 큐에 추가하는 과정은 커널 내부 스레드로 동작한다. 이는 다음 call trace를 통해 알 수 있다[4]:

 1ioctl() /* system call with CEC_ADP_S_PHYS_ADDR command */
 2cec_ioctl() /* with CEC_ADAP_S_PHYS_ADDR command */
 3cec_adap_s_phys_addr()
 4__cec_s_phys_addr()
 5cec_adap_enable()
 6adap->ops->adap_enable() /* this is cec_pin_adap_enable() */
 7kthread_run() /* with cec_pin_thread_func(), it works as a thread */
 8cec_received_msg_ts()
 9cec_receive_notify()
10cec_queue_msg_fh()

그리고 다음 코드를 통해 수신된 CEC message가 큐에 저장됨을 알 수 있다[4]:

 1/*
 2 * Queue a new message for this filehandle.
 3 *
 4 * We keep a queue of at most CEC_MAX_MSG_RX_QUEUE_SZ messages. If the
 5 * queue becomes full, then drop the oldest message and keep track
 6 * of how many messages we've dropped.
 7 */
 8static void cec_queue_msg_fh(struct cec_fh *fh, const struct cec_msg *msg)
 9{
10	static const struct cec_event ev_lost_msgs = {
11		.event = CEC_EVENT_LOST_MSGS,
12		.flags = 0,
13		{
14			.lost_msgs = { 1 },
15		},
16	};
17	struct cec_msg_entry *entry;
18
19	mutex_lock(&fh->lock);
20	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
21	if (entry) {
22		entry->msg = *msg;
23		/* Add new msg at the end of the queue */
24		list_add_tail(&entry->list, &fh->msgs);
25
26		if (fh->queued_msgs < CEC_MAX_MSG_RX_QUEUE_SZ) {
27			/* All is fine if there is enough room */
28			fh->queued_msgs++;
29			mutex_unlock(&fh->lock);
30			wake_up_interruptible(&fh->wait);
31			return;
32		}
33
34		/*
35		 * if the message queue is full, then drop the oldest one and
36		 * send a lost message event.
37		 */
38		entry = list_first_entry(&fh->msgs, struct cec_msg_entry, list);
39		list_del(&entry->list);
40		kfree(entry);
41	}
42	mutex_unlock(&fh->lock);
43
44	/*
45	 * We lost a message, either because kmalloc failed or the queue
46	 * was full.
47	 */
48	cec_queue_event_fh(fh, &ev_lost_msgs, ktime_get_ns());
49}

CEC device는 close system call이 호출되었을 때 release된다. 이때 메모리를 정리하는 작업이 수행되며, 이는 다음 코드를 통해 알 수 있다[4]:

 1/* Override for the release function */
 2static int cec_release(struct inode *inode, struct file *filp)
 3{
 4	struct cec_devnode *devnode = cec_devnode_data(filp);
 5	struct cec_adapter *adap = to_cec_adapter(devnode);
 6	struct cec_fh *fh = filp->private_data;
 7	unsigned int i;
 8
 9	mutex_lock(&adap->lock);
10	if (adap->cec_initiator == fh)
11		adap->cec_initiator = NULL;
12	if (adap->cec_follower == fh) {
13		adap->cec_follower = NULL;
14		adap->passthrough = false;
15	}
16	if (fh->mode_follower == CEC_MODE_FOLLOWER)
17		adap->follower_cnt--;
18	if (fh->mode_follower == CEC_MODE_MONITOR_PIN)
19		cec_monitor_pin_cnt_dec(adap);
20	if (fh->mode_follower == CEC_MODE_MONITOR_ALL)
21		cec_monitor_all_cnt_dec(adap);
22	mutex_unlock(&adap->lock);
23
24	mutex_lock(&devnode->lock);
25	mutex_lock(&devnode->lock_fhs);
26	list_del(&fh->list);
27	mutex_unlock(&devnode->lock_fhs);
28	mutex_unlock(&devnode->lock);
29
30	/* Unhook pending transmits from this filehandle. */
31	mutex_lock(&adap->lock);
32	while (!list_empty(&fh->xfer_list)) {
33		struct cec_data *data =
34			list_first_entry(&fh->xfer_list, struct cec_data, xfer_list);
35
36		data->blocking = false;
37		data->fh = NULL;
38		list_del_init(&data->xfer_list);
39	}
40	mutex_unlock(&adap->lock);
41	while (!list_empty(&fh->msgs)) {
42		struct cec_msg_entry *entry =
43			list_first_entry(&fh->msgs, struct cec_msg_entry, list);
44
45		list_del(&entry->list);
46		kfree(entry);
47	}
48	for (i = CEC_NUM_CORE_EVENTS; i < CEC_NUM_EVENTS; i++) {
49		while (!list_empty(&fh->events[i])) {
50			struct cec_event_entry *entry =
51				list_first_entry(&fh->events[i],
52						 struct cec_event_entry, list);
53
54			list_del(&entry->list);
55			kfree(entry);
56		}
57	}
58	kfree(fh);
59
60	cec_put_device(devnode);
61	filp->private_data = NULL;
62	return 0;
63}

# Root Cause Analysis

상기의 cec_release 함수의 코드를 다시 살펴보면, fh에 대한 lock이 없음을 알 수 있다[4].

 1/* Override for the release function */
 2static int cec_release(struct inode *inode, struct file *filp)
 3{
 4	struct cec_devnode *devnode = cec_devnode_data(filp);
 5	struct cec_adapter *adap = to_cec_adapter(devnode);
 6	struct cec_fh *fh = filp->private_data;
 7	unsigned int i;
 8
 9	mutex_lock(&adap->lock);
10	
11	/* ... */
12	
13	mutex_unlock(&adap->lock);
14
15	mutex_lock(&devnode->lock);
16	mutex_lock(&devnode->lock_fhs);
17	list_del(&fh->list);
18	mutex_unlock(&devnode->lock_fhs);
19	mutex_unlock(&devnode->lock);
20
21	/* Unhook pending transmits from this filehandle. */
22	mutex_lock(&adap->lock);
23	
24	/* ... */
25	
26	mutex_unlock(&adap->lock);
27	
28	/* ... */
29	
30	kfree(fh);
31
32	cec_put_device(devnode);
33	filp->private_data = NULL;
34	return 0;
35}

그런데 상기의 cec_queue_msg_fh 함수에는 fh에 lock을 걸고 fh->msgs에 접근하여 큐에 메시지를 추가하는 작업이 존재한다[4].

 1/*
 2 * Queue a new message for this filehandle.
 3 *
 4 * We keep a queue of at most CEC_MAX_MSG_RX_QUEUE_SZ messages. If the
 5 * queue becomes full, then drop the oldest message and keep track
 6 * of how many messages we've dropped.
 7 */
 8static void cec_queue_msg_fh(struct cec_fh *fh, const struct cec_msg *msg)
 9{
10	static const struct cec_event ev_lost_msgs = {
11		.event = CEC_EVENT_LOST_MSGS,
12		.flags = 0,
13		{
14			.lost_msgs = { 1 },
15		},
16	};
17	struct cec_msg_entry *entry;
18
19	mutex_lock(&fh->lock);
20	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
21	if (entry) {
22		entry->msg = *msg;
23		/* Add new msg at the end of the queue */
24		list_add_tail(&entry->list, &fh->msgs);
25		
26		/* ... */
27	}
28	mutex_unlock(&fh->lock);
29
30	/*
31	 * We lost a message, either because kmalloc failed or the queue
32	 * was full.
33	 */
34	cec_queue_event_fh(fh, &ev_lost_msgs, ktime_get_ns());
35}

그럼 우리는 상기의 두 함수를 race 시켜서 fh가 free된 상태에서 fh->msgs에 접근하도록 만들 수 있을 것이다.

   thread 1     |                      thread 2
---------------------------------------------------------------------   
   kfree(fh)    |
                |   mutex_lock(fh);
                |   list_add_tail(&entry->list, &fh->msgs); ---> UAF

따라서 race condition에 의한 UAF가 발생할 수 있다.

# References

  1. "CVE-2024-23848 Detail," 2024. [Online]. Available: https://nvd.nist.gov/vuln/detail/CVE-2024-23848, [Accessed Jan. 30, 2024].
  2. Hans Verkuil, "[Linux Kernel Bugs] KASAN: slab-use-after-free Read in cec_queue_msg_fh and 4 other crashes in the cec device (cec_ioctl)," 2024. Available: https://lore.kernel.org/lkml/e9f42704-2f99-4f2c-ade5-f952e5fd53e5%40xs4all.nl/, [Accessed Jan. 30, 2024].
  3. "Linux Media Subsystem Documentation," 2016. [Online]. Available: https://www.kernel.org/doc/html/v4.9/media/uapi/cec/cec-intro.html, [Accessed Jan. 31, 2024].
  4. torvalds, "Linux kernel," 2024. [Online]. Available: https://github.com/torvalds/linux, [Accessed Jan. 30, 2024].