Post

[Linux] VirtFuzz 1-day

개요

해당 포스터는 VirtFuzz에서 찾은 취약점에 대해서 RCA를 분석하기 위해 작성되었습니다. 해당 논문은 유료이기 때문에 내용은 올릴 수 없고, 현재 공개되어있는 취약점에 대해서 분석을 진행하겠습니다.

CVE-2022-42722

2개의 함수가 수정되었고, 공통적으로 rx->sdata->dev가 존재하는지 검증하는 로직이 추가된 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index bd215fe3c79693..6001adc0a00e36 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1978,10 +1978,11 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx)
 
 		if (mmie_keyidx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS ||
 		    mmie_keyidx >= NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS +
-		    NUM_DEFAULT_BEACON_KEYS) {
-			cfg80211_rx_unprot_mlme_mgmt(rx->sdata->dev,
-						     skb->data,
-						     skb->len);
+				   NUM_DEFAULT_BEACON_KEYS) {
+			if (rx->sdata->dev)
+				cfg80211_rx_unprot_mlme_mgmt(rx->sdata->dev,
+							     skb->data,
+							     skb->len);
 			return RX_DROP_MONITOR; /* unexpected BIP keyidx */
 		}
 
@@ -2131,7 +2132,8 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx)
 	/* either the frame has been decrypted or will be dropped */
 	status->flag |= RX_FLAG_DECRYPTED;
 
-	if (unlikely(ieee80211_is_beacon(fc) && result == RX_DROP_UNUSABLE))
+	if (unlikely(ieee80211_is_beacon(fc) && result == RX_DROP_UNUSABLE &&
+		     rx->sdata->dev))
 		cfg80211_rx_unprot_mlme_mgmt(rx->sdata->dev,
 					     skb->data, skb->len);

즉, 해당 변수를 사용하는 함수를 호출하지만 값이 존재하지 않아서 Null Dereference 취약점이 발생한 것을 확인할 수 있습니다.

CVE-2022-41674

다음 내용을 보면 u8로 선언된 변수를 size_t로 변환한 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 5382fc2003db4..62f8c10412ad3 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -2279,7 +2279,7 @@ cfg80211_update_notlisted_nontrans(struct wiphy *wiphy,
 	size_t new_ie_len;
 	struct cfg80211_bss_ies *new_ies;
 	const struct cfg80211_bss_ies *old;
-	u8 cpy_len;
+	size_t cpy_len;
 
 	lockdep_assert_held(&wiphy_to_rdev(wiphy)->bss_lock);

cfg80211_update_notlisted_nontrans 함수의 패치전은 다음과 같이 구현되어 있습니다. 여기서 문제의 변수는 cpy_len인데 u8 자료형으로 선언되어 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void
cfg80211_update_notlisted_nontrans(struct wiphy *wiphy,
				   struct cfg80211_bss *nontrans_bss,
				   struct ieee80211_mgmt *mgmt, size_t len)
{
	u8 *ie, *new_ie, *pos;
	const struct element *nontrans_ssid;
	const u8 *trans_ssid, *mbssid;
	size_t ielen = len - offsetof(struct ieee80211_mgmt,
				      u.probe_resp.variable);
	size_t new_ie_len;
	struct cfg80211_bss_ies *new_ies;
	const struct cfg80211_bss_ies *old;
	u8 cpy_len;

	...

	/* copy the nontransmitted SSID */
	cpy_len = nontrans_ssid->datalen + 2;
	memcpy(pos, nontrans_ssid, cpy_len);
	pos += cpy_len;
	/* copy the IEs between SSID and MBSSID */
	cpy_len = trans_ssid[1] + 2;
	memcpy(pos, (trans_ssid + cpy_len), (mbssid - (trans_ssid + cpy_len)));
	pos += (mbssid - (trans_ssid + cpy_len));
	/* copy the IEs after MBSSID */
	cpy_len = mbssid[1] + 2;
	memcpy(pos, mbssid + cpy_len, ((ie + ielen) - (mbssid + cpy_len)));

	...
}

위의 코드에서 마지막에 있는 memcpy(pos, mbssid + cpy_len, ((ie + ielen) - (mbssid + cpy_len)))에서 취약점이 trigger됩니다.

cpy_len 자료형과 mbssid 자료형은 u8로 선언되어 있는데 cpy_len = mbssid[1] + 2가 실행되면서 integer overflow가 발생하게됩니다.

CVE-2022-42719

우선, 아래의 내용부터 살펴보면 ieee802_11_elems 구조체에 멤버 변수가 추가된 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 4e1d4c339f2de3..a842f2e1c23096 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1709,6 +1709,14 @@ struct ieee802_11_elems {
 
 	/* whether a parse error occurred while retrieving these elements */
 	bool parse_error;
+
+	/*
+	 * scratch buffer that can be used for various element parsing related
+	 * tasks, e.g., element de-fragmentation etc.
+	 */
+	size_t scratch_len;
+	u8 *scratch_pos;
+	u8 scratch[];
 };

그리고 ieee802_11_parse_elems_full 함수도 패치되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index f61289c5fed248..99e903299143e8 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1506,24 +1506,26 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
 	const struct element *non_inherit = NULL;
 	u8 *nontransmitted_profile;
 	int nontransmitted_profile_len = 0;
+	size_t scratch_len = params->len;
 
-	elems = kzalloc(sizeof(*elems), GFP_ATOMIC);
+	elems = kzalloc(sizeof(*elems) + scratch_len, GFP_ATOMIC);
 	if (!elems)
 		return NULL;
 	elems->ie_start = params->start;
 	elems->total_len = params->len;
-
-	nontransmitted_profile = kmalloc(params->len, GFP_ATOMIC);
-	if (nontransmitted_profile) {
-		nontransmitted_profile_len =
-			ieee802_11_find_bssid_profile(params->start, params->len,
-						      elems, params->bss,
-						      nontransmitted_profile);
-		non_inherit =
-			cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,
-					       nontransmitted_profile,
-					       nontransmitted_profile_len);
-	}
+	elems->scratch_len = scratch_len;
+	elems->scratch_pos = elems->scratch;
+
+	nontransmitted_profile = elems->scratch_pos;
+	nontransmitted_profile_len =
+		ieee802_11_find_bssid_profile(params->start, params->len,
+					      elems, params->bss,
+					      nontransmitted_profile);
+	elems->scratch_pos += nontransmitted_profile_len;
+	elems->scratch_len -= nontransmitted_profile_len;
+	non_inherit = cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,
+					     nontransmitted_profile,
+					     nontransmitted_profile_len);
 
 	elems->crc = _ieee802_11_parse_elems_full(params, elems, non_inherit);
 
@@ -1557,8 +1559,6 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
 	    offsetofend(struct ieee80211_bssid_index, dtim_count))
 		elems->dtim_count = elems->bssid_index->dtim_count;
 
-	kfree(nontransmitted_profile);
-
 	return elems;
 }

ieee802_11_elems 구조체에 선언된 scratch_len 길이 만큼 동적할당하는 크기가 증가했습니다. 그리고 nontransmitted_profile 변수가 가리키는 곳이 동적할당된 영역이 아닌, 새로 구조체에 선언된 elems->scratch_pos를 가리키게 변경되었습니다.

여기서 함수 로직만 보면 취약점이 보이지 않지만 만약 multi-BSSID로 요청을 보내게 된다면 함수가 return되기 이전에 nontransmitted_profile 변수가 해제되기 때문에 UAF 취약점이 발생하게 됩니다.

💡 multi-BSSID?
BSSID이란 와이파이 네트워크에서 특정 무선대역 또는 WLAN에 할당되는 MAC주소를 의미하고 멀티-BSSID는 말 그대로 한번에 다양한 BSSID를 사용하는 것을 의미합니다.

CVE-2022-42720

해당 취약점은 CVE-2022-42719 취약점과 동일하게 multi-BSSID 일때 발생하는 취약점입니다.

그리고 해당 취약점은 3개의 함수에서 발생한 문제때문에 CVE를 발급받았습니다. 우선, bss_ref_get 함수에서 bss 변수의 refcount를 증가시키기전에 bss를 초기화시키는 문제입니다. 따라서 다음과 같이 bss_from_pub 매크로를 통해서 패치되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index a183f2b758742d..249107212c099f 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -143,18 +143,12 @@ static inline void bss_ref_get(struct cfg80211_registered_device *rdev,
 	lockdep_assert_held(&rdev->bss_lock);
 
 	bss->refcount++;
-	if (bss->pub.hidden_beacon_bss) {
-		bss = container_of(bss->pub.hidden_beacon_bss,
-				   struct cfg80211_internal_bss,
-				   pub);
-		bss->refcount++;
-	}
-	if (bss->pub.transmitted_bss) {
-		bss = container_of(bss->pub.transmitted_bss,
-				   struct cfg80211_internal_bss,
-				   pub);
-		bss->refcount++;
-	}
+
+	if (bss->pub.hidden_beacon_bss)
+		bss_from_pub(bss->pub.hidden_beacon_bss)->refcount++;
+
+	if (bss->pub.transmitted_bss)
+		bss_from_pub(bss->pub.transmitted_bss)->refcount++;
 }

cfg80211_bss_update 함수에서 새롭게 new라는 변수를 kzalloc 함수를 통해서 할당합니다. 하지만, 패치전에는 pub.transmitted_bss 변수에 대해서 초기화하는 부분이 존재하지않아서 UAF 취약점이 발생하게 됩니다.

1
2
3
4
5
6
7
8
9
@@ -1741,6 +1735,8 @@ cfg80211_bss_update(struct cfg80211_registered_device *rdev,
		new = kzalloc(sizeof(*new) + rdev->wiphy.bss_priv_size,
			      GFP_ATOMIC);
		...
 		new->refcount = 1;
 		INIT_LIST_HEAD(&new->hidden_list);
 		INIT_LIST_HEAD(&new->pub.nontrans_list);
+		/* we'll set this later if it was non-NULL */
+		new->pub.transmitted_bss = NULL;

패치전 기준으로 __cfg80211_unlink_bss 함수를 통해서 연결을 해제하지만, 초기화하는 로직이 존재하지 않아서 취약점이 발생했습니다. 그래서 res 변수를 NULL로 초기화하는 부분이 추가되었고 해당 변수의 값이 NULL이기 때문에 return하는 로직도 새로 추가되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@@ -2023,10 +2019,15 @@ cfg80211_inform_single_bss_data(struct wiphy *wiphy,
 		spin_lock_bh(&rdev->bss_lock);
 		if (cfg80211_add_nontrans_list(non_tx_data->tx_bss,
 					       &res->pub)) {
-			if (__cfg80211_unlink_bss(rdev, res))
+			if (__cfg80211_unlink_bss(rdev, res)) {
 				rdev->bss_generation++;
+				res = NULL;
+			}
 		}
 		spin_unlock_bh(&rdev->bss_lock);
+
+		if (!res)
+			return NULL;
 	}
 
 	trace_cfg80211_return_bss(&res->pub);

CVE-2022-42721

해당 취약점은 nontrans_bss->nontrans_list의 값이 비어있을때 계속해서 list를 추가해 Infinite loop가 발생한 취약점입니다. 따라서, 다음과 같이 nontrans_bss->nontrans_list 변수에 대한 검증을 추가했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 249107212c099..703b05c6c43e7 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -423,6 +423,15 @@ cfg80211_add_nontrans_list(struct cfg80211_bss *trans_bss,
 
 	rcu_read_unlock();
 
+	/*
+	 * This is a bit weird - it's not on the list, but already on another
+	 * one! The only way that could happen is if there's some BSSID/SSID
+	 * shared by multiple APs in their multi-BSSID profiles, potentially
+	 * with hidden SSID mixed in ... ignore it.
+	 */
+	if (!list_empty(&nontrans_bss->nontrans_list))
+		return -EINVAL;
+
 	/* add to the list */
 	list_add_tail(&nontrans_bss->nontrans_list, &trans_bss->nontrans_list);
 	return 0;
This post is licensed under CC BY 4.0 by the author.