CVE: CVE-2021-0950

Tested Versions:

  • RQ1A.210205.004

Product URL(s):

Description of the vulnerability

An Out-Of-Bounds Write bug was found in nfc_nci_nxp.so. Specifically, in file "hardware/nxp/nfc/halimpl/hal/phNxpNciHal_ext.cc", function phNxpNciHal_write_ext, due to lack of proper validation of the length of supplied command prior to increasing length of it, leading to 3 bytes overflow problem. This vulnerability can be turned into a read past the end of a global buffer. An attacker can leverage this in conjunction with other vulnerabilities to execute arbitrary code in the context of NFC HIDL service.

The phNxpNciHal_write function (defined in "hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc") is responsible for writing NCI command/data from Device Host to NFC controller. To invoke this function from client, the bellow code can be use in client: hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc

#include <android/hardware/nfc/1.2/INfc.h>

using android::sp;
using INfcV1_2 = android::hardware::nfc::V1_2::INfc;

...
sp<INfcV1_2> mHal = INfcV1_2::getService();
...
mHal->write(...);

When the mHal->write() function is invoked from the client side, the phNxpNciHal_write in the service side is also invoked accordingly. From phNxpNciHal_write function, phNxpNciHal_write_internal function is invoked: hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc

930 int phNxpNciHal_write_internal(uint16_t data_len, const uint8_t* p_data) {
  
      /* ... */
  
936   if (data_len > NCI_MAX_DATA_LEN) {
937     NXPLOG_NCIHAL_E("cmd_len exceeds limit NCI_MAX_DATA_LEN");
938     android_errorWriteLog(0x534e4554, "121267042");
939     goto clean_and_return;
940   }
941   /* Create local copy of cmd_data */
942   memcpy(nxpncihal_ctrl.p_cmd_data, p_data, data_len);
943   nxpncihal_ctrl.cmd_len = data_len;
944 #ifdef P2P_PRIO_LOGIC_HAL_IMP
      /* ... */
954 #endif
955
956   /* Check for NXP ext before sending write */
957   status =
958       phNxpNciHal_write_ext(&nxpncihal_ctrl.cmd_len, nxpncihal_ctrl.p_cmd_data,
959                             &nxpncihal_ctrl.rsp_len, nxpncihal_ctrl.p_rsp_data);
      /* ... */
988 }

p_data is client-supplied command buffer and its length is data_len. The maximum length of p_data buffer is NCI_MAX_DATA_LEN (300 bytes). In phNxpNciHal_write_internal function, p_data is copied into a nxpncihal_ctrl.p_cmd_data buffer. nxpncihal_ctrl is a phNxpNciHal_Control_t global variables, p_cmd_data and p_rsp_data are 300 bytes buffer, are defined in "hardware/nxp/nfc/halimpl/hal/phNxpNciHal.h": hardware/nxp/nfc/halimpl/hal/phNxpNciHal.h

89  typedef struct phNxpNciHal_Control {
      /* ... */
120   uint16_t cmd_len;
121   uint8_t p_cmd_data[NCI_MAX_DATA_LEN];
122   uint16_t rsp_len;
123   uint8_t p_rsp_data[NCI_MAX_DATA_LEN];
      /* ... */
132 } phNxpNciHal_Control_t;

Dig into phNxpNciHal_write_ext function, which is defined in file "hardware/nxp/nfc/halimpl/hal/phNxpNciHal_ext.cc". There is 2 branchs: hardware/nxp/nfc/halimpl/hal/phNxpNciHal_ext.cc

613 NFCSTATUS phNxpNciHal_write_ext(uint16_t* cmd_len, uint8_t* p_cmd_data,
614                             uint16_t* rsp_len, uint8_t* p_rsp_data) {
        /* ... */
679     if (bEnableMfcReader && p_cmd_data[0] == 0x21 && p_cmd_data[1] == 0x00) {
680       NXPLOG_NCIHAL_D("Going through extns - Adding Mifare in RF Discovery");
681       p_cmd_data[2] += 3;
682       p_cmd_data[3] += 1;
683       p_cmd_data[*cmd_len] = 0x80;              // <-- Out-Of-Bounds Write
684       p_cmd_data[*cmd_len + 1] = 0x01;          // <-- Out-Of-Bounds Write
685       p_cmd_data[*cmd_len + 2] = 0x80;          // <-- Out-Of-Bounds Write
686       *cmd_len += 3;
687       status = NFCSTATUS_SUCCESS;
688       bEnableMfcExtns = false;
689       NXPLOG_NCIHAL_D(
690           "Going through extns - Adding Mifare in RF Discovery - END");
691     }    
        /* ... */
790     } else if (p_cmd_data[0] == 0x21 && p_cmd_data[1] == 0x00) {
791         NXPLOG_NCIHAL_D(
792             "> Going through workaround - Add Mifare Classic in Discovery Map");
793         p_cmd_data[*cmd_len] = 0x80;            // <-- Out-Of-Bounds Write
794         p_cmd_data[*cmd_len + 1] = 0x01;        // <-- Out-Of-Bounds Write
795         p_cmd_data[*cmd_len + 2] = 0x80;        // <-- Out-Of-Bounds Write
796         p_cmd_data[5] = 0x01;
797         p_cmd_data[6] = 0x01;
798         p_cmd_data[2] += 3;
799         p_cmd_data[3] += 1;
800         *cmd_len += 3;
801     }
    /* ... */
879 }

There is no bound check when increasing cmd_len lead to 3 byte 0x80, 0x01, 0x80 can be written out of p_cmd_data buffer. Look at the phNxpNciHal_Control_t object definition, rsp_len is defined following p_cmd_data buffer. It leads to rsp_len variable could be overwritten by 3 bytes in phNxpNciHal_write_ext function.

Build fingerprint

$ getprop ro.build.fingerprint
google/sargo/sargo:11/RQ1A.210205.004/7038034:user/release-keys

Proof of Concept

Turning 3 bytes Out-Of-Bounds Write to Infomation Disclosure.

To successful exploit the 3 bytes Out-Of-Bounds Write vulnerability and turn it into a Infomation Disclosure, we must combine this vulnerability with a race condition, details are as follow:

phNxpNciHal_client_thread (defined in "hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc") is a thread handler which handles all TML and NCI messages. NCI is sent to this thread by invoking phTmlNfc_DeferredCall function (defined in "hardware/nxp/nfc/halimpl/tml/phTmlNfc.cc").

In phNxpNciHal_write_ext function, there are many branches which lead to set return value to NFCSTATUS_FAILED, for example: hardware/nxp/nfc/halimpl/hal/phNxpNciHal_ext.cc

  613 NFCSTATUS phNxpNciHal_write_ext(uint16_t* cmd_len, uint8_t* p_cmd_data,
  614                             uint16_t* rsp_len, uint8_t* p_rsp_data) {
          /* ... */
  623     if (p_cmd_data[0] == PROPRIETARY_CMD_FELICA_READER_MODE &&
  624         p_cmd_data[1] == PROPRIETARY_CMD_FELICA_READER_MODE &&
  625         p_cmd_data[2] == PROPRIETARY_CMD_FELICA_READER_MODE) {
  626         NXPLOG_NCIHAL_D("Received proprietary command to set Felica Reader mode:%d",
  627                         p_cmd_data[3]);
  628         gFelicaReaderMode = p_cmd_data[3];
  629         /* frame the dummy response */
  630         *rsp_len = 4;
  631         p_rsp_data[0] = 0x00;
  632         p_rsp_data[1] = 0x00;
  633         p_rsp_data[2] = 0x00;
  634         p_rsp_data[3] = 0x00;
  635         status = NFCSTATUS_FAILED;
  636     }
          /* ... */
  878     return status;
  879 }

If the phNxpNciHal_write_ext function return NFCSTATUS_FAILED, the phTmlNfc_DeferredCall is invoked to send a message to phNxpNciHal_client_thread thread: hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc

  930 int phNxpNciHal_write_internal(uint16_t data_len, const uint8_t* p_data) {
          /* ... */
  
  957     status =
  958         phNxpNciHal_write_ext(&nxpncihal_ctrl.cmd_len, nxpncihal_ctrl.p_cmd_data,
  959                                 &nxpncihal_ctrl.rsp_len, nxpncihal_ctrl.p_rsp_data);    
  960     if (status != NFCSTATUS_SUCCESS) {
  961         /* Do not send packet to PN54X, send response directly */
  962         msg.eMsgType = NCI_HAL_RX_MSG;
  963         msg.pMsgData = NULL;
  964         msg.Size = 0;
  965
  966         phTmlNfc_DeferredCall(gpphTmlNfc_Context->dwCallbackThreadId,
  967                           (phLibNfc_Message_t*)&msg);
  968         goto clean_and_return;
  969     }
      
          /* ... */
  988 }

The NCI_HAL_RX_MSG message is processed in phNxpNciHal_client_thread function as following: hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc

  171 static void* phNxpNciHal_client_thread(void* arg) {
          /* ... */
  
  270     case NCI_HAL_RX_MSG: {
  271         REENTRANCE_LOCK();
  272         if (nxpncihal_ctrl.p_nfc_stack_data_cback != NULL) {
  273           (*nxpncihal_ctrl.p_nfc_stack_data_cback)(nxpncihal_ctrl.rsp_len,
  274                                                    nxpncihal_ctrl.p_rsp_data);
  275         }
  276         REENTRANCE_UNLOCK();
  277         break;
  278     }
  
          /* ... */
  285 }

Although lock operation is performing here, with the primitive that overwriting the rsp_len variable without lock operation by exploit 3 bytes out-of-bound write vulnerability above, we can trigger race condition.

The size of p_rsp_data buffer is 300 bytes and with the out-of-bound write bug, we can overwrite the value of rsp_len variable to 0x180 = 384. Combining 2 vulnerabilities, we can leak the 84 bytes memory behind the p_rsp_data buffer.

Look at declaration of nxpprofile_ctrl variable in file "hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc": hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc

  60 /* NCI HAL Control structure */
  61 phNxpNciHal_Control_t nxpncihal_ctrl;
  62 
  63 /* NXP Poll Profile structure */
  64 phNxpNciProfile_Control_t nxpprofile_ctrl;
  65
  66 /* TML Context */
  67 extern phTmlNfc_Context_t* gpphTmlNfc_Context;
  68 extern void phTmlNfc_set_fragmentation_enabled(
  69     phTmlNfc_i2cfragmentation_t result);
  70 /* global variable to get FW version from NCI response*/
  71 uint32_t wFwVerRsp;
  72 EseAdaptation *gpEseAdapt = NULL;

gpEseAdapt looks interesting, it is a pointer to a heap memory in HAL service. This variable is set in phNxpNciHal_MinOpen function. In file "hardware/nxp/nfc/halimpl/hal/phNxpNciHal.cc", phNxpNciHal_MinOpen function:

  550 int phNxpNciHal_MinOpen (){
          ...
  605     gpEseAdapt = &EseAdaptation::GetInstance();
          ...
  771 }

EseAdaptation::GetInstance function is defined in file "hardware/nxp/nfc/halimpl/src/adaptation/EseAdaptation.cpp": hardware/nxp/nfc/halimpl/src/adaptation/EseAdaptation.cpp

  98  EseAdaptation& EseAdaptation::GetInstance() {
  99      AutoThreadMutex a(sLock);
  100 
  101     if (!mpInstance) mpInstance = new EseAdaptation;    <-- allocation memory in heap.
  102     return *mpInstance;
  103 }

Confirm this thing by look at library file "/vendor/lib64/nfc_nci_nxp.so" which NFC HAL code is compiled into (this library is fetched from Pixel 3a phone, firmware version: RQ1A.210205.004, md5sum: b8b1d55f451117d5b6c67de7bd662883). Open this binary in IDA:

  .bss:0000000000030610 byte_30610      % 0x12C      <-- the nxpncihal_ctrl.p_rsp_data buffer
  .bss:000000000003073C word_3073C      % 2                     ; DATA XREF: phNxpNciHal_MinOpen(void)+51C↑r
  .bss:000000000003073C                                         ; phNxpNciHal_write_unlocked(ushort,uchar const*)+48↑w ...
  .bss:000000000003073E byte_3073E      % 1                     ; DATA XREF: sub_1A2BC+38↑r
  .bss:000000000003073E                                         ; sub_1A2BC+44↑w
  .bss:000000000003073F byte_3073F      % 1                     ; DATA XREF: phNxpNciHal_MinOpen(void)+358↑w
  .bss:000000000003073F                                         ; phNxpNciHal_MinOpen(void)+530↑r ...
  .bss:0000000000030740 byte_30740      % 1                     ; DATA XREF: sub_1A2BC+46C↑r
  .bss:0000000000030740                                         ; sub_1A2BC+480↑w ...
  .bss:0000000000030741                 ALIGN 4
  .bss:0000000000030744 dword_30744     % 4                     ; DATA XREF: sub_1A2BC:loc_1A578↑r
  .bss:0000000000030744                                         ; sub_1AD2C+118↑w ...
  .bss:0000000000030748 word_30748      % 2                     ; DATA XREF: sub_1A2BC+334↑w
  .bss:0000000000030748                                         ; sub_1F4C8+98↑r ...
  .bss:000000000003074A                 ALIGN 4
  .bss:000000000003074C byte_3074C      % 1                     ; DATA XREF: phNxpNciHal_MinOpen(void)+4C↑w
  .bss:000000000003074C                                         ; phNxpNciHal_MinOpen(void)+6B0↑w ...
  .bss:000000000003074D                 ALIGN 0x10
  .bss:0000000000030750 nxpprofile_ctrl % 8                     ; DATA XREF: LOAD:0000000000001E38↑o
  .bss:0000000000030750                                         ; phNxpNciHal_MinOpen(void)+268↑w ...
  .bss:0000000000030758                 EXPORT wFwVerRsp
  .bss:0000000000030758 wFwVerRsp       % 4                     ; DATA XREF: LOAD:0000000000001358↑o
  .bss:0000000000030758                                         ; phNxpNciHal_MinOpen(void)+80C↑r ...
  .bss:000000000003075C                 ALIGN 0x20
  .bss:0000000000030760                 EXPORT gpEseAdapt
  .bss:0000000000030760 gpEseAdapt      % 8

The offset between gpEseAdapt variable and nxpncihal_ctrl.p_rsp_data buffer is (0x30760 - 0x30610) = 336 bytes, so it is able to leak value of gpEseAdapt variable.

Since gpEseAdapt point to an allocation memory chunk in heap memory of NFC HAL service, it can be used to chain with another vulnerability to execute code in NFC HAL service context.

Timeline

  • 2021-03-05 Reported to Vendor
  • 2021-12-02 Vendor assign CVE