CVE-2021-43267 번역
라온화이트햇 핵심연구팀 조진호
CVE-2021-43267 번역
Summary
리눅스 커널 5.10-rc1 ~ 5.15사이에서 터지는 힙 취약점 버그. 커널의 TPIC(Transparent Inter Process Communication)에서 취약점이 발생한다.
Executive Summary
먼저 취약점을 찾은 사람은 CodeQL을 이용해 취약점을 찾았다고 한다.
import cpp
from FunctionCall fc // 모든 함수 호출 로부터
where fc.getTarget().getName() = "kmalloc" // kmalloc 찾기
and fc.getArgument(0).getType().getSize() = 2 // kmalloc의 첫번쨰 파라미터가 16bit int
select fc, fc.getLocation()
kmalloc으로 할당하는 힙 중에서 16bit int만 찾는 이유는 32비트나 64비트보다 오버플로우 하기 쉬울거라고 생각했다고 한다.
총 60개의 결과중에 특별한 결과물이 하나 있다. (net/tipc/crypto.c
)
static bool tipc_crypto_key_rcv(struct tipc_crypto *rx, struct tipc_msg *hdr) // (1)
{
struct tipc_crypto *tx = tipc_net(rx->net)->crypto_tx;
struct tipc_aead_key *skey = NULL;
u16 key_gen = msg_key_gen(hdr);
u16 size = msg_data_sz(hdr); // (2)
u8 *data = msg_data(hdr);
/* ... */
/* Allocate memory for the key */
skey = kmalloc(size, GFP_ATOMIC); // (3)
if (unlikely(!skey)) {
pr_err("%s: unable to allocate memory for skey\n", rx->name);
goto exit;
}
/* Copy key from msg data */
skey->keylen = ntohl(*((__be32 *)(data + TIPC_AEAD_ALG_NAME))); // (4)
memcpy(skey->alg_name, data, TIPC_AEAD_ALG_NAME);
memcpy(skey->key, data + TIPC_AEAD_ALG_NAME + sizeof(__be32),
skey->keylen); // (5)
/* Sanity check */
if (unlikely(size != tipc_aead_key_size(skey))) { // (6)
kfree(skey);
skey = NULL;
goto exit;
}
/* ... */
}
이 함수는 받은 데이터를 파싱하는거 처럼 보이며 (2)에서 얻은 크기로 (3)에서 skey를 만들고 (4)와 (5)에서 아무런 사이즈 검증 절차가 없다. 따라서 skey를 오버플로우 할 수 있다.
Linux TIPC Protocol
TIPC프로토콜은 클러스터링 환경에서 많은 수의 노드간의 통신에 주로 사용한다. 프로토콜 처리의 대부분은 리눅스 커널 모듈 패키지에서 실행되며 사용자에게서 로드될 때 소켓과 같이 사용한다. 또한 권한이 없는 사용자는 netlink call로 사용할 수 있다고 한다.
TIPC는 이더넷과 UDP같은 방식을 설정할 수 있는데 (UDP는 포트 6118)권한 없는 사용자는 net관련 권한이 없을 수 있으니 LPE에는 UDP를 사용하면 된다.
TIPC는 분리된 주소 형태를 가질 수 있다. 또한 사용자에게서 투명한 작동 방식을 가지고 있다고 하는데 이는 아마 모든 과정이 커널 내부에서 이루어져서 이렇게 말하는 것 같다. 메시지 파싱 부분도 커널 내부에서 하는데 이런 모양이다.
여기서 exploit에 사용되는 부분은 Header size
와 Message size
이다.
Header size
가 헤더의 크기를 말하는 것이고 Message size
가 헤더를 포함한 전체 TIPC메시지 크기이다.
이 두개의 값은 tipc_msg_validate
에서 검증한다. (/net/tipc/msg.c
)
bool tipc_msg_validate(struct sk_buff **_skb)
{
struct sk_buff *skb = *_skb;
struct tipc_msg *hdr;
int msz, hsz;
/* ... */
hsz = msg_hdr_sz(buf_msg(skb));
if (unlikely(hsz < MIN_H_SIZE) || (hsz > MAX_H_SIZE))
return false;
/* ... */
hdr = buf_msg(skb);
/* ... */
msz = msg_size(hdr);
if (unlikely(msz < hsz))
return false;
if (unlikely((msz - hsz) > TIPC_MAX_USER_MSG_SIZE))
return false;
if (unlikely(skb->len < msz))
return false;
TIPC_SKB_CB(skb)->validated = 1;
return true;
}
Message size
가 Header size
보다 큰가? 를 검증하고 Header size
의 크기도 검증하고 있다.
TIPC 취약점
새로운 tipc message type인 MSG_CRYPTO
가 2020년에 나왔다.
struct tipc_aead_key {
char alg_name[TIPC_AEAD_ALG_NAME];
unsigned int keylen; /* in bytes */
char key[];
};
TIPC_AEAD_ALG_NAME
는 32다. 이 메시지가 들어오면 TIPC 커널 모듈은 이 정보를 노드 스토리지에 저장해야한다.
/* Allocate memory for the key */
skey = kmalloc(size, GFP_ATOMIC);
/* ... */
/* Copy key from msg data */
skey->keylen = ntohl(*((__be32 *)(data + TIPC_AEAD_ALG_NAME)));
memcpy(skey->alg_name, data, TIPC_AEAD_ALG_NAME);
memcpy(skey->key, data + TIPC_AEAD_ALG_NAME + sizeof(__be32),
skey->keylen);
Header size
와 Message size
는 위 validate에서 검증하지만 keylen을 만드는 MSG_CRYPTO와 TIPC_AEAD_ALG_NAME에 대해서는 검증을 하지 않는다. 공격자는 작은 Message size를 만들어 힙을 할당받고, keylen에 임의의 값을 넣어 힙 오버플로우 취약점을 트리거 할 수 있게된다.
Exploitability
취약점은 로컬과 리모트 둘다 되며, 로컬에서 힙 컨트롤을 더 잘 할 수 있어서 로컬이 더 잘된다.
오버플로우를 할 떄 메시지 사이즈 체크 부분을 보면 메시지 크기가 패킷보다 큰지만 체크해서 20바이트 크기의 패킷을 만들고 메시지 크기를 10바이트로 해도 정상적으로 사용 가능함을 알 수 있다.
if (unlikely(skb->len < msz))
return false;
Patch
net/tipc/crypto.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/net/tipc/crypto.c b/net/tipc/crypto.c
index c9391d38d..dc60c32bb 100644
--- a/net/tipc/crypto.c
+++ b/net/tipc/crypto.c
@@ -2285,43 +2285,53 @@ static bool tipc_crypto_key_rcv(struct tipc_crypto *rx, struct tipc_msg *hdr)
u16 key_gen = msg_key_gen(hdr);
u16 size = msg_data_sz(hdr);
u8 *data = msg_data(hdr);
+ unsigned int keylen;
+
+ /* Verify whether the size can exist in the packet */
+ if (unlikely(size < sizeof(struct tipc_aead_key) + TIPC_AEAD_KEYLEN_MIN)) {
+ pr_debug("%s: message data size is too small\n", rx->name);
+ goto exit;
+ }
+
+ keylen = ntohl(*((__be32 *)(data + TIPC_AEAD_ALG_NAME)));
+
+ /* Verify the supplied size values */
+ if (unlikely(size != keylen + sizeof(struct tipc_aead_key) ||
+ keylen > TIPC_AEAD_KEY_SIZE_MAX)) {
+ pr_debug("%s: invalid MSG_CRYPTO key size\n", rx->name);
+ goto exit;
+ }
spin_lock(&rx->lock);
if (unlikely(rx->skey || (key_gen == rx->key_gen && rx->key.keys))) {
pr_err("%s: key existed <%p>, gen %d vs %d\n", rx->name,
rx->skey, key_gen, rx->key_gen);
- goto exit;
+ goto exit_unlock;
}
/* Allocate memory for the key */
skey = kmalloc(size, GFP_ATOMIC);
if (unlikely(!skey)) {
pr_err("%s: unable to allocate memory for skey\n", rx->name);
- goto exit;
+ goto exit_unlock;
}
/* Copy key from msg data */
- skey->keylen = ntohl(*((__be32 *)(data + TIPC_AEAD_ALG_NAME)));
+ skey->keylen = keylen;
memcpy(skey->alg_name, data, TIPC_AEAD_ALG_NAME);
memcpy(skey->key, data + TIPC_AEAD_ALG_NAME + sizeof(__be32),
skey->keylen);
- /* Sanity check */
- if (unlikely(size != tipc_aead_key_size(skey))) {
- kfree(skey);
- skey = NULL;
- goto exit;
- }
-
rx->key_gen = key_gen;
rx->skey_mode = msg_key_mode(hdr);
rx->skey = skey;
rx->nokey = 0;
mb(); /* for nokey flag */
- exit:
+ exit_unlock:
spin_unlock(&rx->lock);
+ exit:
/* Schedule the key attaching on this crypto */
if (likely(skey && queue_delayed_work(tx->wq, &rx->work, 0)))
return true;
--
2.31.1
패치는 keylen
을 검증하는 코드가 추가되었다.