2023.09.07

CVE-2021-39629 A-197353344 EoP 9、10、11、12

patch

分析

漏洞点位于phTmlNfc_Init函数,该函数会malloc一个chunk给上下文变量gpphTmlNfc_Context,然后初始化其中的一些成员变量。当TML层初始化失败时,只调用**phTmlNfc_CleanUp**函数来清除初始化过程中打开的handles,而已经初始化的一些接口并没有被清除掉,导致UAF

patch是把phTmlNfc_CleanUp换成phTmlNfc_Shutdown_CleanUp。看下面的代码,区别是后者会先调用phTmlNfc_Shutdown(),然后再调用phTmlNfc_CleanUp。所以前者就是缺少了shutdown的过程。
那么shutdown和cleanup有什么区别呢?按照对这两个函数的描述来看,phTmlNfc_Shutdown()用于清除已经初始化的一些接口(猜测主要是在关闭gpphTmlNfc_Context->readerThreadgpphTmlNfc_Context->writerThread这两个线程的时候?),而且该函数里还会销毁互斥锁。而phTmlNfc_CleanUp()函数主要用于清除初始化过程中打开的handles。

此外,shutdown会对gpphTmlNfc_Context的成员变量进行sem_post操作,而cleanup会对gpphTmlNfc_Context的成员变量进行sem_destroy操作。

sem_post和sem_destroy两者的区别?
sem_post和sem_destroy都是POSIX信号量操作函数。
sem_post函数用于释放信号量,即增加指定信号量的计数值,使其变为可用状态。这通常用于释放由信号量控制的某个资源,使其他等待该资源的线程可以继续执行。如果没有线程在等待,则信号量的值仅增加1。
sem_destroy函数用于销毁指定的信号量。它会彻底释放信号量所占用的资源,并将信号量的状态设置为未初始化的状态。当一个信号量被销毁后,它不能再被使用。在调用sem_destroy之后,如果再次对信号量进行操作,将会产生未定义的行为。通常在不再需要使用信号量时,会调用sem_destroy函数来释放相关的资源。
综上所述,sem_post用于释放信号量,即增加信号量的计数值,使其变为可用状态。而sem_destroy用于销毁信号量,即彻底释放信号量的资源,并将其恢复到未初始化的状态。

phTmlNfc_Init

/*******************************************************************************
**
** Function         phTmlNfc_Init
**
** Description      Provides initialization of TML layer and hardware interface
**                  Configures given hardware interface and sends handle to the
**                  caller
** 该函数是TML(Transport Layer Manager)层的初始化函数。
** TML层用于提供与硬件接口的交互,包括配置硬件接口和发送句柄给调用者。
**
** Parameters       pConfig - TML configuration details as provided by the upper
**                            layer 参数pConfig是TML配置的详细信息,由上层提供。
**
** Returns          NFC status:
**                  NFCSTATUS_SUCCESS - initialization successful
**                  NFCSTATUS_INVALID_PARAMETER - at least one parameter is
**                                                invalid
**                  NFCSTATUS_FAILED - initialization failed (for example,
**                                     unable to open hardware interface)
**                  NFCSTATUS_INVALID_DEVICE - device has not been opened or has
**                                             been disconnected
** 返回NFC状态,包括成功初始化、无效参数、初始化失败和设备未打开或已断开连接等
**
*******************************************************************************/
NFCSTATUS phTmlNfc_Init(pphTmlNfc_Config_t pConfig) {
  NFCSTATUS wInitStatus = NFCSTATUS_SUCCESS;
  /* Check if TML layer is already Initialized */
  // 检查TML层是否已经初始化,如果已经初始化则返回错误码NFCSTATUS_ALREADY_INITIALISED
  if (NULL != gpphTmlNfc_Context) {
    /* TML initialization is already completed */
    wInitStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_ALREADY_INITIALISED);
  }
  /* Validate Input parameters */
  // 验证输入参数,如果参数为空或者dwGetMsgThreadId参数为默认值(PH_TMLNFC_RESET_VALUE),
  // 则返回错误码NFCSTATUS_INVALID_PARAMETER。
  else if ((NULL == pConfig) ||
           (PH_TMLNFC_RESET_VALUE == pConfig->dwGetMsgThreadId)) {
    /*Parameters passed to TML init are wrong */
    wInitStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_INVALID_PARAMETER);
  } else {
    /* Allocate memory for TML context */
    // 为TML上下文分配内存
    gpphTmlNfc_Context =
        (phTmlNfc_Context_t*)malloc(sizeof(phTmlNfc_Context_t));
      // 如果内存分配失败,则返回错误码NFCSTATUS_FAILED
    if (NULL == gpphTmlNfc_Context) {
      wInitStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_FAILED);
    } else {
      /* Initialise all the internal TML variables */
      // 初始化TML的所有内部变量,将上下文内存区域清零,并设置线程标志位为1,表示线程已完成。
      memset(gpphTmlNfc_Context, PH_TMLNFC_RESET_VALUE,
             sizeof(phTmlNfc_Context_t));
      /* Make sure that the thread runs once it is created */
      gpphTmlNfc_Context->bThreadDone = 1;
      /* Open the device file to which data is read/written */
      // 打开设备文件并进行配置,返回状态码给wInitStatus。
      wInitStatus = phTmlNfc_i2c_open_and_configure(
          pConfig, &(gpphTmlNfc_Context->pDevHandle));
      // 如果打开设备失败,则返回错误码NFCSTATUS_INVALID_DEVICE。
      if (NFCSTATUS_SUCCESS != wInitStatus) {
        wInitStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_INVALID_DEVICE);
        gpphTmlNfc_Context->pDevHandle = NULL;
      // 如果打开成功,则初始化读写信息的其他变量,包括是否启用读写线程和线程忙标志位等。
      } else {
        gpphTmlNfc_Context->tReadInfo.bEnable = 0;
        gpphTmlNfc_Context->tWriteInfo.bEnable = 0;
        gpphTmlNfc_Context->tReadInfo.bThreadBusy = false;
        gpphTmlNfc_Context->tWriteInfo.bThreadBusy = false;
        // 初始化互斥锁和信号量。如果初始化失败,则返回错误码NFCSTATUS_FAILED。
        if (pthread_mutex_init(&gpphTmlNfc_Context->readInfoUpdateMutex,
                               NULL) != 0) {
          wInitStatus = NFCSTATUS_FAILED;
        } else if (0 != sem_init(&gpphTmlNfc_Context->rxSemaphore, 0, 0)) {
          wInitStatus = NFCSTATUS_FAILED;
        } else if (0 != sem_init(&gpphTmlNfc_Context->txSemaphore, 0, 0)) {
          wInitStatus = NFCSTATUS_FAILED;
        } else if (0 != sem_init(&gpphTmlNfc_Context->postMsgSemaphore, 0, 0)) {
          wInitStatus = NFCSTATUS_FAILED;
        } else {
          // 发送信号量postMsgSemaphore,表示TML线程已开始运行。
          sem_post(&gpphTmlNfc_Context->postMsgSemaphore);
          /* Start TML thread (to handle write and read operations) */
          // 启动TML线程来处理写和读操作。如果线程启动失败,则返回错误码NFCSTATUS_FAILED
          if (NFCSTATUS_SUCCESS != phTmlNfc_StartThread()) {
            wInitStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_FAILED);
          } else {
            /* Create Timer used for Retransmission of NCI packets */
            // 创建用于重传NCI数据包的定时器。
            gpphTmlNfc_Context->dwTimerId = phOsalNfc_Timer_Create();
            // 如果定时器创建成功,则存储线程标识符dwCallbackThreadId和配置信息eConfig。
            if (PH_OSALNFC_TIMER_ID_INVALID != gpphTmlNfc_Context->dwTimerId) {
              /* Store the Thread Identifier to which Message is to be posted */
              gpphTmlNfc_Context->dwCallbackThreadId =
                  pConfig->dwGetMsgThreadId;
              /* Enable retransmission of Nci packet & set retry count to
               * default */
              gpphTmlNfc_Context->eConfig = phTmlNfc_e_DisableRetrans;
              /* Retry Count = Standby Recovery time of NFCC / Retransmission
               * time + 1 */
              // 设置重试计数bRetryCount,计算公式为(2000 / PHTMLNFC_MAXTIME_RETRANSMIT)+ 1
              gpphTmlNfc_Context->bRetryCount =
                  (2000 / PHTMLNFC_MAXTIME_RETRANSMIT) + 1;
              gpphTmlNfc_Context->bWriteCbInvoked = false;
            } else {
              wInitStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_FAILED);
            }
          }
        }
      }
    }
  }
  /* Clean up all the TML resources if any error */
  // 如果初始化过程中发生错误,则清理TML的所有资源。
  if (NFCSTATUS_SUCCESS != wInitStatus) {
    /* Clear all handles and memory locations initialized during init */
    phTmlNfc_CleanUp(); // 【漏洞点】
  }
  return wInitStatus; // 返回初始化状态码wInitStatus。
}

phTmlNfc_CleanUp - Free

/*******************************************************************************
**
** Function         phTmlNfc_CleanUp
**
** Description      Clears all handles opened during TML initialization
**                  清除所有TML层初始化时打开的handles
**
** Parameters       None
**
** Returns          None
**
*******************************************************************************/
void phTmlNfc_CleanUp(void) {
  // 检查gpphTmlNfc_Context是否为NULL。
  // 如果为NULL,表示TML上下文尚未初始化,直接返回
  if (NULL == gpphTmlNfc_Context) {
    return;
  }
  // 检查gpphTmlNfc_Context->pDevHandle是否为NULL。
  // 如果不为NULL,表示有设备handle存在。
  if (NULL != gpphTmlNfc_Context->pDevHandle) {
    // 调用phTmlNfc_i2c_reset函数重置I2C通信,并忽略其返回值
    (void)phTmlNfc_i2c_reset(gpphTmlNfc_Context->pDevHandle, 0);
    // 将gpphTmlNfc_Context->bThreadDone的值设为0,用于终止线程
    gpphTmlNfc_Context->bThreadDone = 0;
  }
  // 销毁下面三个信号量
  sem_destroy(&gpphTmlNfc_Context->rxSemaphore);
  sem_destroy(&gpphTmlNfc_Context->txSemaphore);
  sem_destroy(&gpphTmlNfc_Context->postMsgSemaphore);
  // 调用phTmlNfc_i2c_close函数关闭I2C通信
  phTmlNfc_i2c_close(gpphTmlNfc_Context->pDevHandle);
  // 将gpphTmlNfc_Context->pDevHandle的值设为NULL
  gpphTmlNfc_Context->pDevHandle = NULL;
  /* Clear memory allocated for storing Context variables */
  // 释放存储上下文变量的内存空间
  free((void*)gpphTmlNfc_Context);
  /* Set the pointer to NULL to indicate De-Initialization */
  // 将gpphTmlNfc_Context的值设NULL,表示已经完成了销毁
  gpphTmlNfc_Context = NULL;
  return;
}

phTmlNfc_Shutdown_CleanUp - Free

/*******************************************************************************
**
** Function         phTmlNfc_Shutdown_CleanUp
**
** Description      wrapper function  for shutdown  and cleanup of resources
**
** Parameters       None
**
** Returns          NFCSTATUS
**
*******************************************************************************/
NFCSTATUS phTmlNfc_Shutdown_CleanUp() {
  NFCSTATUS wShutdownStatus = phTmlNfc_Shutdown();
  phTmlNfc_CleanUp();
  return wShutdownStatus;
}

phTmlNfc_Shutdown

/*******************************************************************************
**
** Function         phTmlNfc_Shutdown
**
** Description      Uninitializes TML layer and hardware interface
**                  清除TML层和hardware的接口
**
** Parameters       None
**
** Returns          NFC status:
**                  NFCSTATUS_SUCCESS - TML configuration released successfully
**                  NFCSTATUS_INVALID_PARAMETER - at least one parameter is
**                                                invalid
**                  NFCSTATUS_FAILED - un-initialization failed (example: unable
**                                     to close interface)
**
*******************************************************************************/
NFCSTATUS phTmlNfc_Shutdown(void) {
  NFCSTATUS wShutdownStatus = NFCSTATUS_SUCCESS;
  /* Check whether TML is Initialized */
  // 检查gpphTmlNfc_Context是否为NULL。
  // gpphTmlNfc_Context是一个指向TML上下文的指针,用于判断TML是否已初始化
  if (NULL != gpphTmlNfc_Context) {
    /* Reset thread variable to terminate the thread */
    // 将gpphTmlNfc_Context->bThreadDone的值设为0,用于终止线程
    gpphTmlNfc_Context->bThreadDone = 0;
    usleep(1000); // 暂停1毫秒
    /* Clear All the resources allocated during initialization */
    // 清除所有初始化时分配的资源
    // 调用sem_post函数,释放gpphTmlNfc_Context->rxSemaphore信号量
    sem_post(&gpphTmlNfc_Context->rxSemaphore);
    usleep(1000);
    // 调用sem_post函数,释放gpphTmlNfc_Context->txSemaphore信号量
    sem_post(&gpphTmlNfc_Context->txSemaphore);
    usleep(1000);
    // 调用sem_post函数,释放gpphTmlNfc_Context->postMsgSemaphore信号量
    sem_post(&gpphTmlNfc_Context->postMsgSemaphore);
    usleep(1000);
    // 这里为啥重复调用?
    sem_post(&gpphTmlNfc_Context->postMsgSemaphore);
    usleep(1000);
    // 销毁gpphTmlNfc_Context->readInfoUpdateMutex互斥锁
    pthread_mutex_destroy(&gpphTmlNfc_Context->readInfoUpdateMutex);
    // pthread_join函数用于等待指定的线程终止,并获取其返回值
    // 下面这两个if是用来确保readerThread线程和writerThread线程已经成功终止
      
    // 检查gpphTmlNfc_Context->readerThread线程的终止状态,
    // 如果pthread_join函数返回值不为0(即线程终止失败)
    // 则打印错误日志
    if (0 != pthread_join(gpphTmlNfc_Context->readerThread, (void**)NULL)) {
      NXPLOG_TML_E("Fail to kill reader thread!");
    }
    // 检查gpphTmlNfc_Context->writerThread线程的终止状态
    if (0 != pthread_join(gpphTmlNfc_Context->writerThread, (void**)NULL)) {
      NXPLOG_TML_E("Fail to kill writer thread!");
    }
    NXPLOG_TML_D("bThreadDone == 0");
  } else {
    // 如果ghTmlNfc_Context为NULL,则将wStatus的值设为PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_NOT_INITIALISED)。
    // PHNFCSTVAL是一个用于创建NFC错误的宏
    wShutdownStatus = PHNFCSTVAL(CID_NFC_TML, NFCSTATUS_NOT_INITIALISED);
  }
  // 返回wShutdownStatus作为函数的结果
  return wShutdownStatus;
}

phTmlNfc_TmlThread - Use

/*******************************************************************************
**
** Function         phTmlNfc_TmlThread
**
** Description      Read the data from the lower layer driver
**
** Parameters       pParam  - parameters for Writer thread function
**
** Returns          None
**
*******************************************************************************/
static void* phTmlNfc_TmlThread(void* pParam) {
  NFCSTATUS wStatus = NFCSTATUS_SUCCESS;
  int32_t dwNoBytesWrRd = PH_TMLNFC_RESET_VALUE;
  uint8_t temp[260];
  uint8_t readRetryDelay = 0;
  /* Transaction info buffer to be passed to Callback Thread */
  static phTmlNfc_TransactInfo_t tTransactionInfo;
  /* Structure containing Tml callback function and parameters to be invoked
     by the callback thread */
  static phLibNfc_DeferredCall_t tDeferredInfo;
  /* Initialize Message structure to post message onto Callback Thread */
  static phLibNfc_Message_t tMsg;
  UNUSED(pParam);
  NXPLOG_TML_D("PN54X - Tml Reader Thread Started................\n");
  /* Writer thread loop shall be running till shutdown is invoked */
  while (gpphTmlNfc_Context->bThreadDone) {
    /* If Tml write is requested */
    /* Set the variable to success initially */
    wStatus = NFCSTATUS_SUCCESS;
    sem_wait(&gpphTmlNfc_Context->rxSemaphore);
    /* If Tml read is requested */
    if (1 == gpphTmlNfc_Context->tReadInfo.bEnable) {
      NXPLOG_TML_D("PN54X - Read requested.....\n");
      /* Set the variable to success initially */
      wStatus = NFCSTATUS_SUCCESS;
      /* Variable to fetch the actual number of bytes read */
      dwNoBytesWrRd = PH_TMLNFC_RESET_VALUE;
      /* Read the data from the file onto the buffer */
      if (NULL != gpphTmlNfc_Context->pDevHandle) {
        NXPLOG_TML_D("PN54X - Invoking I2C Read.....\n");
        dwNoBytesWrRd =
            phTmlNfc_i2c_read(gpphTmlNfc_Context->pDevHandle, temp, 260);
        if (-1 == dwNoBytesWrRd) {
          NXPLOG_TML_E("PN54X - Error in I2C Read.....\n");
          if (readRetryDelay < MAX_READ_RETRY_DELAY_IN_MILLISEC) {
            /*sleep for 30/60/90/120/150 msec between each read trial incase of
             * read error*/
            readRetryDelay += 30;
          }
          usleep(readRetryDelay * 1000);
          sem_post(&gpphTmlNfc_Context->rxSemaphore);
        } else if (dwNoBytesWrRd > 260) {
          NXPLOG_TML_E("Numer of bytes read exceeds the limit 260.....\n");
          readRetryDelay = 0;
          sem_post(&gpphTmlNfc_Context->rxSemaphore);
        } else {
          pthread_mutex_lock(&gpphTmlNfc_Context->readInfoUpdateMutex);
          memcpy(gpphTmlNfc_Context->tReadInfo.pBuffer, temp, dwNoBytesWrRd);
          readRetryDelay = 0;
          NXPLOG_TML_D("PN54X - I2C Read successful.....\n");
          /* This has to be reset only after a successful read */
          gpphTmlNfc_Context->tReadInfo.bEnable = 0;
          if ((phTmlNfc_e_EnableRetrans == gpphTmlNfc_Context->eConfig) &&
              (0x00 != (gpphTmlNfc_Context->tReadInfo.pBuffer[0] & 0xE0))) {
            NXPLOG_TML_D("PN54X - Retransmission timer stopped.....\n");
            /* Stop Timer to prevent Retransmission */
            uint32_t timerStatus =
                phOsalNfc_Timer_Stop(gpphTmlNfc_Context->dwTimerId);
            if (NFCSTATUS_SUCCESS != timerStatus) {
              NXPLOG_TML_E("PN54X - timer stopped returned failure.....\n");
            } else {
              gpphTmlNfc_Context->bWriteCbInvoked = false;
            }
          }
          if (gpphTmlNfc_Context->tWriteInfo.bThreadBusy) {
            NXPLOG_TML_D("Delay Read if write thread is busy");
            usleep(2000); /*2ms delay to give prio to write complete */
          }
          /* Update the actual number of bytes read including header */
          gpphTmlNfc_Context->tReadInfo.wLength = (uint16_t)(dwNoBytesWrRd);
          phNxpNciHal_print_packet("RECV",
                                   gpphTmlNfc_Context->tReadInfo.pBuffer,
                                   gpphTmlNfc_Context->tReadInfo.wLength);
          dwNoBytesWrRd = PH_TMLNFC_RESET_VALUE;
          /* Fill the Transaction info structure to be passed to Callback
           * Function */
          tTransactionInfo.wStatus = wStatus;
          tTransactionInfo.pBuff = gpphTmlNfc_Context->tReadInfo.pBuffer;
          /* Actual number of bytes read is filled in the structure */
          tTransactionInfo.wLength = gpphTmlNfc_Context->tReadInfo.wLength;
          /* Read operation completed successfully. Post a Message onto Callback
           * Thread*/
          /* Prepare the message to be posted on User thread */
          tDeferredInfo.pCallback = &phTmlNfc_ReadDeferredCb;
          tDeferredInfo.pParameter = &tTransactionInfo;
          tMsg.eMsgType = PH_LIBNFC_DEFERREDCALL_MSG;
          tMsg.pMsgData = &tDeferredInfo;
          tMsg.Size = sizeof(tDeferredInfo);
          pthread_mutex_unlock(&gpphTmlNfc_Context->readInfoUpdateMutex);
          NXPLOG_TML_D("PN54X - Posting read message.....\n");
          phTmlNfc_DeferredCall(gpphTmlNfc_Context->dwCallbackThreadId, &tMsg);
        }
      } else {
        NXPLOG_TML_D("PN54X -gpphTmlNfc_Context->pDevHandle is NULL");
      }
    } else {
      NXPLOG_TML_D("PN54X - read request NOT enabled");
      usleep(10 * 1000);
    }
  } /* End of While loop */
  return NULL;
}