水一篇分析文章,站在巨人的肩膀上。。

漏洞分析

CVE-2020-12351,该漏洞CVSS 评分为8.3 分,是一个基于堆的类型混淆(type confusion) 漏洞。在受害者蓝牙范围内的远程攻击者在指导目标设备的bd 地址的情况下就可以利用该漏洞。攻击者可以通过发送恶意l2cap包的方式来触发该漏洞,引发DoS 或kernel 权限的任意代码执行。谷歌安全研究人员称该漏洞是一个零点击漏洞,也就是说利用的过程中无需任何的用户交互。

A heap-based 类型混淆 affecting Linux kernel 4.8 and higher was discovered in net/bluetooth/l2cap_core.c.

当 CID 不是 L2CAP_CID_SIGNALING, L2CAP_CID_CONN_LESS 或 L2CAP_CID_LE_SIGNALING时, l2cap_recv_frame 会调用 l2cap_data_channel() 。在l2cap_data_channel()中需要关注的是第8行的第27行的这两个分支。

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
///net/bluetooth/l2cap_core.c
static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk_buff *skb)
{
struct l2cap_chan *chan;
chan = l2cap_get_chan_by_scid(conn, cid);
if (!chan) {
if (cid == L2CAP_CID_A2MP) {
chan = a2mp_channel_create(conn, skb); //here
if (!chan) {
kfree_skb(skb);
return;
}

l2cap_chan_lock(chan);
} else {
BT_DBG("unknown cid 0x%4.4x", cid);
/* Drop packet and return */
kfree_skb(skb);
return;
}
}
...
switch (chan->mode) {
...
case L2CAP_MODE_ERTM:
case L2CAP_MODE_STREAMING:
l2cap_data_rcv(chan, skb); //here
goto done;
...
}

drop:
kfree_skb(skb);

done:
l2cap_chan_unlock(chan);
}

第27行:在 l2cap_data_channel 函数里如果 channel 的 mode 是 L2CAP_MODE_ERTM 或 L2CAP_MODE_STREAMING, 就会调用 l2cap_data_rcv()。

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
///net/bluetooth/l2cap_core.c
static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
{
struct l2cap_ctrl *control = &bt_cb(skb)->l2cap;
u16 len;
u8 event;

__unpack_control(chan, skb);

len = skb->len;

/*
* We can just drop the corrupted I-frame here.
* Receiver will miss it and start proper recovery
* procedures and ask for retransmission.
*/
if (l2cap_check_fcs(chan, skb))
goto drop;

if (!control->sframe && control->sar == L2CAP_SAR_START)
len -= L2CAP_SDULEN_SIZE;

if (chan->fcs == L2CAP_FCS_CRC16)
len -= L2CAP_FCS_SIZE;

if (len > chan->mps) {
l2cap_send_disconn_req(chan, ECONNRESET);
goto drop;
}

if ((chan->mode == L2CAP_MODE_ERTM ||
chan->mode == L2CAP_MODE_STREAMING) && sk_filter(chan->data, skb)) //here
goto drop;
...
}

当packet的 checksum 被验证通过 , 继续调用 sk_filter()//sk_filter是对sk_filter_trim_cap的简单封装。

第8行: l2cap_data_channel 函数里 当使用的 CID 是 L2CAP_CID_A2MP 并且还没建立一个channel时 , a2mp_channel_create() 将被调用。

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
///net/bluetooth/a2mp.c
static struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked)
{
struct amp_mgr *mgr;
struct l2cap_chan *chan;

mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);
if (!mgr)
return NULL;

BT_DBG("conn %p mgr %p", conn, mgr);

mgr->l2cap_conn = conn;

chan = a2mp_chan_open(conn, locked); //here
if (!chan) {
kfree(mgr);
return NULL;
}

mgr->a2mp_chan = chan;
chan->data = mgr;
...
return mgr;
}

a2mp_chan_open 创建了一个 channel 并且把 mode 初试化为 L2CAP_MODE_ERTM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static struct l2cap_chan *a2mp_chan_open(struct l2cap_conn *conn, bool locked)
{
struct l2cap_chan *chan;
int err;

chan = l2cap_chan_create();
if (!chan)
return NULL;

BT_DBG("chan %p", chan);

chan->chan_type = L2CAP_CHAN_FIXED;
chan->scid = L2CAP_CID_A2MP;
chan->dcid = L2CAP_CID_A2MP;
...
chan->mode = L2CAP_MODE_ERTM;
...
return chan;
}

!!!问题在这里:

amp_mgr_create()里 chan->data 的类型是struct amp_mgr*

1
2
3
4
5
6
7
static struct amp_mgr *amp_mgr_create(struct l2cap_conn *conn, bool locked)
{
struct amp_mgr *mgr;
...
chan->data = mgr;
...
}

在l2cap_data_rcv()调用了sk_filter(chan->data, skb),定义是这样的 sk_filter(struct sock sk, struct sk_buff *skb); chan->data被转换成了struct sock\类型,类型混淆在此产生。

1
2
3
4
5
6
7
8
9
10
11
 static int l2cap_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
{
...
if ((chan->mode == L2CAP_MODE_ERTM ||
chan->mode == L2CAP_MODE_STREAMING) && sk_filter(chan->data, skb))
goto drop;
...
}

int sk_filter(struct sock *sk, struct sk_buff *skb);
{}

POC

https://github.com/google/security-research/security/advisories/GHSA-h637-c88j-47wq

mode:a2mp_chan_open 创建 channel的时候已把 mode 初试化为 L2CAP_MODE_ERTM。

cid:不应是 L2CAP_CID_SIGNALING, L2CAP_CID_CONN_LESS 或 L2CAP_CID_LE_SIGNALING,这里选择L2CAP_CID_A2MP。

1
2
3
4
5
6
7
8
9
10
#define L2CAP_CID_SIGNALING	0x0001
#define L2CAP_CID_CONN_LESS 0x0002
#define L2CAP_CID_A2MP 0x0003
#define L2CAP_CID_ATT 0x0004
#define L2CAP_CID_LE_SIGNALING 0x0005
#define L2CAP_CID_SMP 0x0006
#define L2CAP_CID_SMP_BREDR 0x0007
#define L2CAP_CID_DYN_START 0x0040
#define L2CAP_CID_DYN_END 0xffff
#define L2CAP_CID_LE_DYN_END 0x007f

crash了

exp

笑死,根本写不出来

参考

https://mp.weixin.qq.com/s/sl9-2GZaJfqGwoHAHG8onQ