Thursday, February 11, 2021

A Practical Approach To Attacking IoT Embedded Designs (I)

by Ruben Santamarta

The booming IoT ecosystem has meant massive growth in the embedded systems market due to the high demand for connected devices. Nowadays, designing embedded devices is perhaps easier than ever thanks to the solutions, kits, chips, and code that semiconductor manufacturers provide to help developers cope with the vast number of heterogeneous requirements IoT devices should comply with.

This never-ending race to come up with new features within tight deadlines comes at a cost, which usually is paid in the security posture of the commercialized device. 

Let's assume a product vendor has implemented security best practices and everything has been locked down properly. Our goal is to compromise the device, but we don't have access to any of the custom code developed for that specific device (not even in binary form). What about the code semiconductor vendors provide? How secure is the code in all those SDKs and middleware that IoT devices rely on?

I performed a manual code review of some of the most widely used IoT SDKs in order to try to answer this question and found multiple vulnerabilities in the code provided by leading semiconductor vendors, such as Texas Instruments, Nordic, and Qualcomm. 

As usual, IOActive followed a responsible disclosure process, notifying the affected vendors and coordinating with them to determine the proper time to disclose issues. In general terms, most vendors properly handled the disclosure process.

At the time of publishing this blog post, the latest versions of the affected SDKs contain fixes for the vulnerabilities. Please note that IOActive has not verified these patches.

Introduction

Embedded IoT systems need to be designed for specific functions. As a result, we can't use a single reference design; however, it is possible to summarize the most common architectures.

SoC 

These designs rely on an SoC that combines the MCU and communications transceiver into a single-chip solution. Thus, the wireless software stack and the application software run in the same component.

     


MCU + Transceiver

In these designs, the MCU is in charge of running the required software, including the applications and even part of the wireless stack, and the transceiver usually handles the physical and data link layers. In addition, there is a host interface (HIF), which is responsible for handling communication between the MCU (Host) and the transceiver, usually over a serial bus (e.g. SPI, UART, or I2C).

    

 

MCU + Network Co-Processor

These designs are a variant of the previous one, where the transceiver is swapped for a network co-processor (NCP), which runs the entire communications stack. In this architecture, the application is still running in the MCU (Host), while the network stack operations are entirely offloaded to the NCP. The HIF is still necessary to enable communication between the Host and NCP.


 

Attack Surface

As we have just outlined, the components within an embedded IoT design do not operate in isolation. They interact with the outside world via wireless and at the intra-board level with other chips.

1. Intra-board 

Most current high-end MCUs and SoCs support some kind of secure boot or code integrity mechanism. Assuming a worst-case scenario (from an attacker’s perspective) where the vendor has closed the usual doors before deploying the product, we may face a pure black-box scenario, where we can’t dump or access the firmware.

In order to turn this situation to our favor, we can focus on the HIF code usually found in the SDKs from semiconductor vendors.  

The advantage we have in this scenario is that, despite the fact that the firmware running in the Host MCU (Host from now on) may be unknown, analysis of HIF communications may reveal that the Host firmware has been compiled with potentially vulnerable SDK code. 

 A successful exploit targeting the HIF implementation could open the door to execute code in the Host if a local attacker has the ability to impersonate the transceiver/NCP over the HIF’s bus (SPI, UART, I2C, USB, etc.). At IOActive, we have been exploiting this attack vector for years in order to compromise among other devices, smart meters, which usually implement either an ‘MCU + NCP’ or ‘MCU + Transceiver’ design. 

The vulnerabilities described in this post are intended to illustrate common patterns in the HIF layer implementation across different vendors, all of which lacked proper validation features. One of the advantages of intra-board attacks is that they can be used not only to discover memory corruption vulnerabilities, but much more interesting logic flaws, as Host firmware may not account for an attacker hijacking intra-board communication.

2. Wireless

From an attack surface perspective, the situation is similar to intra-board attacks. An attacker may lack access to the firmware, but it still possible to target the semiconductor vendor’s stack, usually provided in the corresponding component SDK. 

This research focused on BLE and Zigbee stacks, which are some of the most common wireless communication interfaces used in the IoT ecosystem. The second part of this blog post series will cover these vulnerabilities.


Intra-board Vulnerabilities

Affected vendors

  • Texas Instruments
  • Qualcomm
  • Silicon Labs
  • Zephyr OS
  • Microchip
  • Infineon


Texas Instruments  - www.ti.com


Vulnerability 

Memory corruption via ‘NPITLSPI_CallBack’ 

Affected Products

CC1350 SDK, BLE-STACK (SDK v4.10.01 and prior versions)
CC26x0 BLE-STACK (v2.2.4 and prior versions)

Texas Instruments Advisory: https://www.ti.com/lit/an/swra684/swra684.pdf 

Background 

TI’s Network Processor Interface (NPI) is used for establishing a serial data link between a TI SoC and external MCUs or PCs. It is an abstraction layer above the serial interface (UART or SPI) that handles sending / receiving data power management, and data parsing It is mainly used by TI’s network processor solutions.” Texas Instruments Website



Impact

A local attacker able to interfere with the physical SPI bus between the Host and NCP could send a malformed UNPI packet that corrupts dynamic memory in the Host, potentially achieving code execution.

Technical Details

When ‘NPITLSPI_CallBack’ parses the UNPI packet coming from the Slave, it does not properly verify whether the 16-bit length is within the bounds of ‘npiRxBuf’.  

At lines 210-211, ‘readLen’ is directly calculated from the untrusted input coming from the slave.

Assuming that the local attacker will either guess or force a deterministic FCS by taking into account the malloc implementation, the memory corruption will take place at line 221. 

File: ble_sdk_2_02_04_06/src/components/npi/src/unified/npi_tl_spi.c
190: // -----------------------------------------------------------------------------
191: //! \brief      This callback is invoked on transmission completion
192: //!
193: //! \param[in]  handle - handle to the SPI port
194: //! \param[in]  objTransaction    - handle for SPI transmission
195: //!
196: //! \return     void
197: // -----------------------------------------------------------------------------
198: static void NPITLSPI_CallBack(SPI_Handle handle, SPI_Transaction *objTransaction)
199: {
200:     uint16_t i;
201:     uint16_t readLen = 0;
202:     uint16_t storeWriteLen;
203:     
204:     // Check if a valid packet was found
205:     // SOF:
206:     if (npiRxBuf[NPI_SPI_MSG_SOF_IDX] == NPI_SPI_MSG_SOF &&
207:             objTransaction->count)
208:     {
209:         // Length:
210:         readLen = npiRxBuf[NPI_SPI_MSG_LEN_LSB_IDX];
211:         readLen += ((uint16)npiRxBuf[NPI_SPI_MSG_LEN_MSB_IDX] << 8);
212:         readLen += NPI_SPI_MSG_HDR_NOSOF_LEN; // Include the header w/o SOF
213:         
214:         // FCS:
215:         if (npiRxBuf[readLen + 1] == 
216:               NPITLSPI_calcFCS(&npiRxBuf[NPI_SPI_MSG_LEN_LSB_IDX],readLen))
217:         {
218:             // Message is valid. Shift bytes to remove SOF
219:             for (i = 0 ; i < readLen; i++)
220:             {
221:                 npiRxBuf[i] = npiRxBuf[i + 1];
222:             }
223:         }
224:         else
225:         {
226:             // Invalid FCS. Discard message
227:             readLen = 0;
228:         }
229:     }
230:     
231:     //All bytes in TxBuf must be sent by this point
232:     storeWriteLen = tlWriteLen;
233:     tlWriteLen = 0; 
234:     RxActive = FALSE;  
235:     
236:     if (npiTransmitCB)
237:     {
238:         npiTransmitCB(readLen,storeWriteLen);
239:     }
240: }

npiRxBuf’ is initialized at line 193, using the default value of 530 bytes.

File: source/ti/npi/npi_task.c
198: /* Default NPI parameters structure */
199: const NPI_Params NPI_defaultParams = {
200:     .stackSize          = 1024,
201:     .bufSize            = 530,



File: source/ti/npi/npi_task.c
511:     // Initialize Transport Layer
512:     transportParams.npiTLBufSize = params->bufSize;



File: ble_sdk_2_02_04_06/src/components/npi/src/unified/npi_tl.c
178: //!
179: //! \param[in]  params - Transport Layer parameters
180: //!
181: //! \return     void
182: // -----------------------------------------------------------------------------
183: void NPITL_openTL(NPITL_Params *params)
184: {
185:     _npiCSKey_t key;
186:     key = NPIUtil_EnterCS();
187:     
188:     // Set NPI Task Call backs
189:     memcpy(&taskCBs, &params->npiCallBacks, sizeof(params->npiCallBacks));
190:     
191:     // Allocate memory for Transport Layer Tx/Rx buffers
192:     npiBufSize = params->npiTLBufSize;
193:     npiRxBuf = NPIUTIL_MALLOC(params->npiTLBufSize);


Qualcomm  - www.qualcomm.com

Vulnerability

Multiple buffer overflows when parsing malformed WMI packets in the ‘Wifi_qca’ middleware

Affected Products

Products that use the Qualcomm Atheros WIFI_QCA middleware: https://www.qualcomm.com/products/qca4004

Background 

"The Wireless Module Interface (WMI) is a  communication protocol for QCA wireless components. It defines a set of commands that can be issued to the target firmware or that the target firmware can send back to the host for processing. This WMI communication is happening over the defined HIF layer."

Impact

A local attacker able to interfere with the physical SPI bus between the Host and target QCA SoC could send a malformed WMI packet that corrupts kernel memory in the Host, thus potentially achieving local code execution with kernel privileges.

Technical Details

There are multiple places in the QCA middleware where WMI messages coming from the device are not properly validated.

#1 ‘WMI_GET_CHANNEL_LIST_CMDID’

When processing ‘WMI_GET_CHANNEL_LIST_CMDID’ at ‘wmi_control_rx’ there is no sanity check for the attacker-controlled value ‘CHAN_EV->numChannels’.

File: middleware/wifi_qca/common_src/wmi/wmi.c
667:     switch (id)
668:     {
669:         case (WMI_GET_CHANNEL_LIST_CMDID):
670:             A_WMI_CHANNELLIST_RX(wmip->wmi_devt, devId, (int8_t)CHAN_EV->numChannels, (void*)CHAN_EV->channelList);
671:             break;
672:         case (WMI_GET_BITRATE_CMDID):

Api_ChannelListEvent’ then uses ‘numChan’ to calculate the number of bytes, without performing any bounds checking, that will be copied into the fixed buffer ‘pDCxt->ChannelList’.

File: middleware/wifi_qca/common_src/api_interface/api_wmi_rx.c
119: void Api_ChannelListEvent(void *pCxt, uint8_t devId, int8_t numChan, void *chanList)
120: {
121:     uint16_t i;
122:     A_DRIVER_CONTEXT *pDCxt = GET_DRIVER_COMMON(pCxt);
123:     UNUSED_ARGUMENT(devId);
124:     A_MEMCPY(pDCxt->channelList, chanList, numChan * sizeof(uint16_t));
125:     pDCxt->numChannels = numChan;
126:     /* convert each channel to proper endianness */
127:     for (i = 0; i < pDCxt->numChannels; i++)
128:     {
129:         pDCxt->channelList[i] = A_LE2CPU16(pDCxt->channelList[i]);
130:     }
131: 
132:     API_CHANNEL_LIST_EVENT(pCxt, pDCxt->numChannels, pDCxt->channelList);
133: }
134:

This buffer is defined within the Driver Context structure at line 256. In terms of exploitability, an attacker could easily overwrite a function pointer at line 272 with controlled values.

File: middleware/wifi_qca/common_src/include/driver_cxt.h
56: #define MAX_NUM_CHANLIST_CHANNELS (16)

File:  middleware/wifi_qca/common_src/include/driver_cxt.h
265:     uint16_t channelList[MAX_NUM_CHANLIST_CHANNELS];
266: 
267:     uint32_t regCode;
268:     int8_t rssi;
269:     int32_t bitRate;
270:     boolean wmmEnabled;
271:     boolean tx_complete_pend; /* tracks new tx completes for task sync */
272:     TEMP_FUNC_T asynchRequest;

#2 ‘WMI_STORERECALL_STORE_EVENTID’

File:  middleware/wifi_qca/common_src/wmi/wmi.c
775:         case (WMI_STORERECALL_STORE_EVENTID):
776:             A_WMI_STORERECALL_EVENT_RX(wmip->wmi_devt, devId, datap, len, osbuf);
777:             break;

When processing ‘WMI_STORERECALL_STORE_EVENTID’ at ‘wmi_control_rx’, there is no sanity check for the ‘len’ value, in order to verify it is not larger than ‘pDCxt->tempStorageLength’. As a result, an overly large WMI packet could corrupt the fixed-length buffer pointed to by ‘strrclData’. This buffer is initialized at ‘Custom_Driver_ContextInit’.

File: middleware/wifi_qca/common_src/api_interface/api_wmi_rx.c
882: void Api_StoreRecallEvent(void *pCxt, uint8_t devId, uint8_t *datap, int32_t len, void *pReq)
883: {
884:     WMI_STORERECALL_STORE_EVENT *pEv;
885:     A_DRIVER_CONTEXT *pDCxt = GET_DRIVER_COMMON(pCxt);
886:     uint8_t *strrclData = pDCxt->tempStorage;
887: 
888:     UNUSED_ARGUMENT(devId);
889: 
890:     switch (pDCxt->strrclState)
891:     {
892:         case STRRCL_ST_START:
893:             A_MEMCPY(strrclData, datap, len);

This buffer is initialized at ‘Custom_Driver_ContextInit’.

File: middleware/wifi_qca/custom_src/driver/cust_driver_main.c
150: Custom_Driver_ContextInit(void *pCxt)
151: {
152:     uint32_t tempStorageLen = 0;
153:     A_DRIVER_CONTEXT *pDCxt = GET_DRIVER_COMMON(pCxt);
154: 
155:     /*Allocate the temporary storage buffer. This may be shared by multiple modules.
156:       If store recall is enabled, it may use this buffer for storing target data.
157:       Will also be shared by scan module to store scan results*/
158:     tempStorageLen = max((STORE_RECALL_BUF_SIZE), (ATH_MAX_SCAN_BUFFERS * sizeof(QCA_SCAN_INFO)));
159:     tempStorageLen = max(tempStorageLen, (ATH_MAX_SCAN_BUFFERS * sizeof(ATH_SCAN_EXT)));
160: 
161:     if (tempStorageLen)
162:     {
163:         if (NULL == (pDCxt->tempStorage = A_MALLOC(tempStorageLen, 0)))
164:         {
165:             return A_NO_MEMORY;
166:         }
167:         pDCxt->tempStorageLength = tempStorageLen;

#3 ‘WMI_HOST_DSET_STORE_EVENTID’

File: middleware/wifi_qca/common_src/wmi/wmi.c
783:         case (WMI_HOST_DSET_STORE_EVENTID):
784:             A_WMI_HOST_DSET_EVENT_STORE_RX(wmip->wmi_devt, devId, datap, len, osbuf);

At ‘Api_HostDsetStoreEvent’,’dset_length’ is an attacker-controlled value. At line 418, there is an integer overflow which could bypass the ‘if’ condition. As a result, ‘dset_length’ can still be larger than ‘pDCxt->tempStorageLength’, leading to memory corruption.

File: middleware/wifi_qca/common_src/api_interface/api_wmi_rx.c
403: int dset_length;
404: 
405: void Api_HostDsetStoreEvent(void *pCxt, uint8_t devId, uint8_t *datap, int32_t len, void *pReq)
406: {
407:     A_DRIVER_CONTEXT *pDCxt = GET_DRIVER_COMMON(pCxt);
408:     uint8_t *strrclData = pDCxt->tempStorage;
409:     uint16_t cur_len = pDCxt->strrclCxtLen;
410: 
411:     WMI_HOST_DSET_STORE_EVENT *pDsetEvent = (WMI_HOST_DSET_STORE_EVENT *)datap;
412:     UNUSED_ARGUMENT(devId);
413: 
414:     dset_length = pDsetEvent->length;
415: 
416:     strrclData = pDCxt->tempStorage + cur_len;
417: 
418:     if (cur_len + dset_length <= pDCxt->tempStorageLength)
419:     {
420:         A_MEMCPY(strrclData, pDsetEvent->data, dset_length);
421:     }
422:     else
423:     {
424:         A_ASSERT(0);
425:     }
426:     pDCxt->strrclCxtLen += dset_length;
427: }

Any other code relying on ‘dset_length’ may also be vulnerable (e.g. ‘Api_HostDsetReadEvent’).

#4 ‘WMI_P2P_NODE_LIST_EVENTID’

File:  middleware/wifi_qca/common_src/wmi/wmi.c
834:         case WMI_P2P_NODE_LIST_EVENTID:
835:             status = wmi_p2p_node_list_event_rx(wmip, devId, datap, len);
836:             break;

When processing ‘WMI_P2P_NODE_LIST_EVENTID’ messages coming from the device, the attacker-controlled value ‘num_p2p_dev’ is not sanitized. As a result, at line 1399 it is possible to corrupt the fixed-length buffer pointed to by ‘tmpBuf’, which is ‘pCxt->pScanOut’ (actually, it is ‘pDCxt->tempStorage’, which has been previously explained).

File: middleware/wifi_qca/common_src/api_interface/api_wmi_rx.c

1382: void Api_p2p_node_list_event(void *pCxt, uint8_t devId, uint8_t *datap, uint32_t len)
1383: {
1384:     uint32_t evt_id = 0;
1385:     uint8_t *tmpBuf;
1386:     evt_id = WMI_P2P_NODE_LIST_EVENTID;
1387:     WMI_P2P_NODE_LIST_EVENT *handleP2PDev = (WMI_P2P_NODE_LIST_EVENT *)datap;
1388:     A_DRIVER_CONTEXT *pDCxt = GET_DRIVER_COMMON(pCxt);
1389: 
1390:     tmpBuf = GET_DRIVER_COMMON(pCxt)->pScanOut;
1391:     DRIVER_SHARED_RESOURCE_ACCESS_ACQUIRE(pCxt);
1392:     {
1393:         A_MEMZERO(GET_DRIVER_COMMON(pCxt)->pScanOut, pDCxt->tempStorageLength);
1394:         A_MEMCPY(GET_DRIVER_COMMON(pCxt)->pScanOut, &evt_id, sizeof(uint32_t));
1395:         tmpBuf += sizeof(uint32_t);
1396:         *tmpBuf = handleP2PDev->num_p2p_dev;
1397:         tmpBuf++;
1398: 
1399:         A_MEMCPY((uint8_t *)tmpBuf, ((uint8_t *)(handleP2PDev->data)),
1400:                  (sizeof(P2P_DEVICE_LITE) * (handleP2PDev->num_p2p_dev)));
1401: 
1402:         GET_DRIVER_COMMON(pCxt)->p2pEvtState = false;
1403:     }
1404:     DRIVER_SHARED_RESOURCE_ACCESS_RELEASE(pCxt);
1405: 
1406:     DRIVER_WAKE_USER(pCxt);
1407: }


Silicon Labs - www.silabs.com


Vulnerability

Buffer overflow in ‘sl_wfx_get_pmk

Affected Products

Silicon Labs’ FMAC WFx driver: https://github.com/SiliconLabs/wfx-fullMAC-driver/

Background

The WFx FMAC driver is a software resource meant to allow a host to communicate with the WFx Wi-Fi transceiver. The API exposed by the driver gives control over the WFx Wi-Fi capabilities. In addition, the API enables data transfer at the IP level. This means that the host requires an IP stack if it wants to send/receive Ethernet frames.”   

https://docs.silabs.com/wifi/wf200/rtos/latest/wfx-fmac-driver

Impact

A local attacker able to interfere with the physical SPI/SDIO bus between the Host and the Silicon Labs NCP could forge a malformed WFx response frame that corrupts memory in the Host, thus potentially achieving local code execution.

Technical Details

sl_wfx_get_pmk’ does not sanitize ‘reply->body.password_length’, by either comparing it to ‘password_length’ value or checking against SL_WFX_PASSWORD_SIZE, before copying it (line 1119) into the provided buffer. As a result, there is no guarantee that the buffer pointed by ‘password’ can safely receive the length specified in the response.

File: wfx-fullMAC-driver-master/wfx_fmac_driver/sl_wfx.c
1097: /**************************************************************************//**
1098:  * @brief Get the PMK used to connect to the current secure network
1099:  *
1100:  * @param password is the current Pairwise Master Key
1101:  * @param password_length is its length in bytes
1102:  * @param interface is the interface used to send the request.
1103:  *   @arg         SL_WFX_STA_INTERFACE
1104:  *   @arg         SL_WFX_SOFTAP_INTERFACE
1105:  * @returns SL_STATUS_OK if the command has been sent correctly,
1106:  * SL_STATUS_FAIL otherwise
1107:  *****************************************************************************/
1108: sl_status_t sl_wfx_get_pmk(uint8_t *password,
1109:                            uint32_t *password_length,
1110:                            sl_wfx_interface_t interface)
1111: {
1112:   sl_status_t     result;
1113:   sl_wfx_get_pmk_cnf_t *reply = NULL;
1114: 
1115:   result = sl_wfx_send_command(SL_WFX_GET_PMK_REQ_ID, NULL, 0, interface, (sl_wfx_generic_confirmation_t **)&reply);
1116: 
1117:   if (result == SL_STATUS_OK) {
1118:     result = sl_wfx_get_status_code(sl_wfx_htole32(reply->body.status), SL_WFX_GET_PMK_REQ_ID);
1119:     memcpy(password, reply->body.password, sl_wfx_htole32(reply->body.password_length));
1120:     *password_length = sl_wfx_htole32(reply->body.password_length);
1121:   }
1122: 
1123:   return result;
1124: }


Vulnerability

Kernel memory corruption when decoding ‘Secure Channel’ HIF frame

Affected Products

Silicon Labs’ WFx Linux driver: https://github.com/SiliconLabs/wfx-linux-driver

Background

Silicon Labs’ WF(M)200 chips have the ability to encrypt the SPI or SDIO serial link between the Host and the device. 

Impact

A local attacker able to interfere with the physical SPI/SDIO bus between the Host and the Silicon Labs NCP could send a malformed HIF frame that corrupts kernel memory in the Host, thus potentially achieving local code execution with kernel privileges.

Technical Details

The driver handles attacker-controlled inputs (lines 78-80) when the HIF protocol is using the ‘secure channel functionality,’ even before the proper sanity check on the ‘hif->len’ field is performed (at line 94). As a result, the computed length for the HIF frame would be different from the actual ‘read_len’.

File: wfx-linux-driver-master/bh.c
051: static int rx_helper(struct wfx_dev *wdev, size_t read_len, int *is_cnf)
052: {
053: 	struct sk_buff *skb;
054: 	struct hif_msg *hif;
055: 	size_t alloc_len;
056: 	size_t computed_len;
057: 	int release_count;
058: 	int piggyback = 0;
059: 
060: 	WARN(read_len < 4, "corrupted read");
061: 	WARN(read_len > round_down(0xFFF, 2) * sizeof(u16),
062: 	     "%s: request exceed WFx capability", __func__);
063: 
064: 	// Add 2 to take into account piggyback size
065: 	alloc_len = wdev->hwbus_ops->align_size(wdev->hwbus_priv, read_len + 2);
066: 	skb = dev_alloc_skb(alloc_len);
067: 	if (!skb)
068: 		return -ENOMEM;
069: 
070: 	if (wfx_data_read(wdev, skb->data, alloc_len))
071: 		goto err;
072: 
073: 	piggyback = le16_to_cpup((__le16 *)(skb->data + alloc_len - 2));
074: 	_trace_piggyback(piggyback, false);
075: 
076: 	hif = (struct hif_msg *)skb->data;
077: 	WARN(hif->encrypted & 0x1, "unsupported encryption type");
078: 	if (hif->encrypted == 0x2) {
079: 		if (wfx_sl_decode(wdev, (void *)hif)) {
080: 			dev_kfree_skb(skb);
081: 			// If frame was a confirmation, expect trouble in next
082: 			// exchange. However, it is harmless to fail to decode
083: 			// an indication frame, so try to continue. Anyway,
084: 			// piggyback is probably correct.
085: 			return piggyback;
086: 		}
087: 		computed_len =
088: 			round_up(le16_to_cpu(hif->len) - sizeof(hif->len), 16) +
089: 			sizeof(struct hif_sl_msg) +
090: 			sizeof(struct hif_sl_tag);
091: 	} else {
092: 		computed_len = round_up(le16_to_cpu(hif->len), 2);
093: 	}
094: 	if (computed_len != read_len) {
095: 		dev_err(wdev->dev, "inconsistent message length: %zu != %zu\n",
096: 			computed_len, read_len);
097: 		print_hex_dump(KERN_INFO, "hif: ", DUMP_PREFIX_OFFSET, 16, 1,
098: 			       hif, read_len, true);
099: 		goto err;
100: 	}

clear_len’ is controlled by the attacker, so ‘payload_len’, ‘tag’ and ‘output’ are also indirectly controlled to some extent (lines 51-54). At line 69, ‘mbedtls_ccm_auth_decrypt’ is invoked to decrypt the HIF payload using ‘payload_len’ with the ‘skb->data’ buffer as output. As the length of ‘skb->data’ may be different than ‘payload_len’, it is possible to corrupt that memory chunk. 

File: wfx-linux-driver-master/secure_link.c
48: int wfx_sl_decode(struct wfx_dev *wdev, struct hif_sl_msg *m)
49: {
50: 	int ret;
51: 	size_t clear_len = le16_to_cpu(m->len);
52: 	size_t payload_len = round_up(clear_len - sizeof(m->len), 16);
53: 	u8 *tag = m->payload + payload_len;
54: 	u8 *output = (u8 *)m;
55: 	u32 nonce[3] = { };
56: 
57: 	WARN(m->hdr.encrypted != 0x02, "packet is not encrypted");
58: 
59: 	// Other bytes of nonce are 0
60: 	nonce[1] = m->hdr.seqnum;
61: 	if (wdev->sl.rx_seqnum != m->hdr.seqnum)
62: 		dev_warn(wdev->dev, "wrong encrypted message sequence: %d != %d\n",
63: 				m->hdr.seqnum, wdev->sl.rx_seqnum);
64: 	wdev->sl.rx_seqnum = m->hdr.seqnum + 1;
65: 	if (wdev->sl.rx_seqnum == slk_renew_period)
66: 		schedule_work(&wdev->sl.key_renew_work);
67: 
68: 	memcpy(output, &m->len, sizeof(m->len));
69: 	ret = mbedtls_ccm_auth_decrypt(&wdev->sl.ccm_ctxt, payload_len,
70: 			(u8 *)nonce, sizeof(nonce), NULL, 0,
71: 			m->payload, output + sizeof(m->len),
72: 			tag, sizeof(struct hif_sl_tag));
73: 	if (ret) {
74: 		dev_err(wdev->dev, "mbedtls error: %08x\n", ret);
75: 		return -EIO;
76: 	}
77: 	if (memzcmp(output + clear_len, payload_len + sizeof(m->len) - clear_len))
78: 		dev_warn(wdev->dev, "padding is not 0\n");
79: 	return 0;
80: }

The actual memory corruption happens at line 146 in CTR_CRYPT as ‘dst’ is pointing to HIF’s payload.

File:  wfx-linux-driver-master/mbedtls/library/ccm.c
131: /*
132:  * Encrypt or decrypt a partial block with CTR
133:  * Warning: using b for temporary storage! src and dst must not be b!
134:  * This avoids allocating one more 16 bytes buffer while allowing src == dst.
135:  */
136: #define CTR_CRYPT( dst, src, len  )                                            \
137:     do                                                                  \
138:     {                                                                   \
139:         if( ( ret = mbedtls_cipher_update( &ctx->cipher_ctx, ctr,       \
140:                                            16, b, &olen ) ) != 0 )      \
141:         {                                                               \
142:             return( ret );                                              \
143:         }                                                               \
144:                                                                         \
145:         for( i = 0; i < (len); i++ )                                    \
146:             (dst)[i] = (src)[i] ^ b[i];                                 \
147:     } while( 0 )

Zephyr OS - www.zephyrproject.org

Vulnerability

Multiple buffer overflows in the ‘Zephyr’ eswifi driver

Affected Products

Zephyr RTOS 2.3.0:

  • - https://github.com/zephyrproject-rtos/zephyr
  • - https://www.zephyrproject.org/

Impact

A local attacker able to interfere with the physical SPI bus between the Host and target controller could send a malformed SPI response that corrupts kernel memory in the Host, thus potentially achieving local code execution with kernel privileges.

Technical Details

#1 ‘__parse_ipv4_address’ buffer overflow

This function does not properly verify that ‘byte’ is within IP’s bounds (4 bytes) when parsing the IPv4 address. As a result, a malformed IP string with an overly large number of ‘dots’ will corrupt the ‘ip’ buffer. 

File: zephyr-master/drivers/wifi/eswifi/eswifi_core.c
179: static int __parse_ipv4_address(char *str, char *ssid, uint8_t ip[4])
180: {
181: 	unsigned int byte = -1;
182: 
183: 	/* fmt => [JOIN   ] SSID,192.168.2.18,0,0 */
184: 	while (*str) {
185: 		if (byte == -1) {
186: 			if (!strncmp(str, ssid, strlen(ssid))) {
187: 				byte = 0U;
188: 				str += strlen(ssid);
189: 			}
190: 			str++;
191: 			continue;
192: 		}
193: 
194: 		ip[byte++] = atoi(str);
195: 		while (*str && (*str++ != '.')) {
196: 		}
197: 	}
198: 
199: 	return 0;
200: }

This vulnerability results in a stack overflow at lines 243 and 286.

File:zephyr-master/drivers/wifi/eswifi/eswifi_core.c
240: static int eswifi_connect(struct eswifi_dev *eswifi)
241: {
242: 	char connect[] = "C0\r";
243: 	struct in_addr addr;

SNIP

284: 	/* Any IP assigned ? (dhcp offload or manually) */
285: 	err = __parse_ipv4_address(rsp, eswifi->sta.ssid,
286: 				   (uint8_t *)&addr.s4_addr);
287: 	if (err < 0) {
288: 		LOG_ERR("Unable to retrieve IP address");
289: 		goto error;
290: 	}

#2 ‘__parse_ssid’ buffer overflow 

A similar situation can be found in ‘__parse_ssid’, which extracts and then copies the quoted SSID coming from the SPI response without checking its length. 


File:
zephyr-master/drivers/wifi/eswifi/eswifi_core.c 54: static inline int __parse_ssid(char *str, char *ssid) 55: { 56: /* fnt => '"SSID"' */ 57: 58: if (!*str || (*str != '"')) { 59: return -EINVAL; 60: } 61: 62: str++; 63: while (*str && (*str != '"')) { 64: *ssid++ = *str++; 65: } 66: 67: *ssid = '\0'; 68: 69: if (*str != '"') { 70: return -EINVAL; 71: } 72: 73: return -EINVAL; 74: } 75:

This vulnerability also ends up in a stack overflow scenario according to the following vulnerable path:

1.

File: zephyr-master/drivers/wifi/eswifi/eswifi_core.c
202: static void eswifi_scan(struct eswifi_dev *eswifi)
203: {
204: 	char cmd[] = "F0\r";
205: 	char *data;
206: 	int i, ret;
207: 
208: 	LOG_DBG("");
209: 
210: 	eswifi_lock(eswifi);
211: 
212: 	ret = eswifi_at_cmd_rsp(eswifi, cmd, &data);
213: 	if (ret < 0) {
214: 		eswifi->scan_cb(eswifi->iface, -EIO, NULL);
215: 		eswifi_unlock(eswifi);
216: 		return;
217: 	}
218: 
219: 	for (i = 0; i < ret; i++) {
220: 		if (data[i] == '#') {
221: 			struct wifi_scan_result res = {0};
222: 
223: 			__parse_scan_res(&data[i], &res);
224: 
225: 			eswifi->scan_cb(eswifi->iface, 0, &res);
226: 			k_yield();
227: 
228: 			while (data[i] && data[i] != '\n') {
229: 				i++;
230: 			}
231: 		}
232: 	}


2.

File: zephyr-master/include/net/wifi_mgmt.h
84: /* Each result is provided to the net_mgmt_event_callback
85:  * via its info attribute (see net_mgmt.h)
86:  */
87: struct wifi_scan_result {
88: 	uint8_t ssid[WIFI_SSID_MAX_LEN];
89: 	uint8_t ssid_length;
90: 
91: 	uint8_t channel;
92: 	enum wifi_security_type security;
93: 	int8_t rssi;
94: };

3.

File: zephyr-master/drivers/wifi/eswifi/eswifi_core.c
76: static void __parse_scan_res(char *str, struct wifi_scan_result *res)
77: {
78: 	int field = 0;
79: 
80: 	/* fmt => #001,"SSID",MACADDR,RSSI,BITRATE,MODE,SECURITY,BAND,CHANNEL */
81: 
82: 	while (*str) {
83: 		if (*str != ',') {
84: 			str++;
85: 			continue;
86: 		}
87: 
88: 		if (!*++str) {
89: 			break;
90: 		}
91: 
92: 		switch (++field) {
93: 		case 1: /* SSID */
94: 			__parse_ssid(str, res->ssid);

Microchip - www.microchip.com

Vulnerability

Multiple vulnerabilities in ‘CryptoAuth Lib’  3.2.2

Affected Products

Background

"Designed to work with CryptoAuthentication devices such as the ATECC608B, ATSHA204A or ATSHA206A and to simplify your development, the CryptoAuthLib is a software support library written in C code. It is a component of any application or device driver that requires crypto services from the CryptoAuthentication devices. It works on a variety of platforms including Arm® Cortex®-M based or PIC® microcontrollers, PCs running the Windows® operating system or an embedded Linux® platform"

Microchip Website

Impact

A local attacker able to partially emulate a malicious of Crypto Authentication device through USB could send malformed KIT protocol packets that corrupt memory in the Host, thus potentially achieving code execution.

Technical Details

When ‘kit_phy_receive’ is receiving the KIT packet from the device, it does not properly verify whether the total amount of bytes received is within the bounds of ‘rxdata’.  

The reading loop’s condition is merely ‘continue_read == true’ without taking into account ‘rxsize’, at line 324.

At line 308, we can see how the logic constantly tries to read a fixed amount of one byte from the USB device.

At line 316, ‘total_bytes_read’ is incremented by ‘bytes_read’. As the attacker controls the input, it is possible to evade the check for ‘\n’ at line 319. As a result, ‘total_bytes_read’ will be incremented beyond ‘rxsize’, thus overflowing ‘rxdata’ at line 308 during the call to ‘fread’. 


File:
secure_elements/lib/hal/hal_linux_kit_hid.c 287: ATCA_STATUS kit_phy_receive(ATCAIface iface, uint8_t* rxdata, int* rxsize) 288: { 289: ATCAIfaceCfg *cfg = atgetifacecfg(iface); 290: atcahid_t* pHid = (atcahid_t*)atgetifacehaldat(iface); 291: bool continue_read = true; 292: size_t bytes_read = 0; 293: size_t total_bytes_read = 0; 294: 295: if ((rxdata == NULL) || (rxsize == NULL) || (cfg == NULL) || (pHid == NULL)) 296: { 297: return ATCA_BAD_PARAM; 298: } 299: 300: if (pHid->kits[cfg->atcahid.idx].read_handle == NULL) 301: { 302: return ATCA_COMM_FAIL; 303: } 304: 305: // Receive the data from the kit USB device 306: do 307: { 308: bytes_read = fread(&rxdata[total_bytes_read], sizeof(uint8_t), 1, 309: pHid->kits[cfg->atcahid.idx].read_handle); 310: if (ferror(pHid->kits[cfg->atcahid.idx].read_handle) != 0) 311: { 312: clearerr(pHid->kits[cfg->atcahid.idx].read_handle); 313: return ATCA_RX_FAIL; 314: } 315: 316: total_bytes_read += bytes_read; 317: 318: // Check if the kit protocol message has been received 319: if (strstr((char*)rxdata, "\n") != NULL) 320: { 321: continue_read = false; 322: } 323: } 324: while (continue_read == true); 325: 326: // Save the total bytes read 327: *rxsize = total_bytes_read; 328: 329: return ATCA_SUCCESS; 


A similar issue can also be found in ‘kit_phy_receive’, although in this case instead of just one byte, it is reading CDC_BUFFER_MAX to a local stack buffer at line 263. ‘bytes_to_cpy’ is used to increment the offset (‘total_bytes’) (line 288) where the bytes will be copied (line 287). 


File:
secure_elements/lib/hal/hal_linux_kit_cdc.c 232: ATCA_STATUS kit_phy_receive(ATCAIface iface, char* rxdata, int* rxsize) 233: { 234: ATCA_STATUS status = ATCA_SUCCESS; 235: ATCAIfaceCfg *cfg = atgetifacecfg(iface); 236: int cdcid = cfg->atcauart.port; 237: atcacdc_t* pCdc = (atcacdc_t*)atgetifacehaldat(iface); 238: uint8_t buffer[CDC_BUFFER_MAX] = { 0 }; 239: bool continue_read = true; 240: int bytes_read = 0; 241: uint16_t total_bytes = 0; 242: char* location = NULL; 243: int bytes_remain = 0; 244: int bytes_to_cpy = 0; 245: 246: do 247: { 248: // Verify the input variables 249: if ((rxdata == NULL) || (rxsize == NULL) || (pCdc == NULL)) 250: { 251: status = ATCA_BAD_PARAM; 252: break; 253: } 254: // Verify the write handle 255: if (pCdc->kits[cdcid].read_handle == INVALID_HANDLE_VALUE) 256: { 257: status = ATCA_COMM_FAIL; 258: break; 259: } 260: // Read all of the bytes 261: while (continue_read == true) 262: { 263: bytes_read = read(pCdc->kits[cdcid].read_handle, buffer, CDC_BUFFER_MAX); 264: 265: // Find the location of the '\n' character in read buffer 266: // todo: generalize this read... it only applies if there is an ascii protocol with an <eom> of \n and if the <eom> exists 267: location = strchr((char*)&buffer[0], '\n'); 268: if (location == NULL) 269: { 270: // Copy all of the bytes 271: bytes_to_cpy = bytes_read; 272: } 273: else 274: { 275: // Copy only the bytes remaining in the read buffer to the <eom> 276: bytes_to_cpy = (uint8_t)(location - (char*)buffer) + 1; 277: // The response has been received, stop receiving more data 278: continue_read = false; 279: } 280: // Protect rxdata from overwriting, this will have the result of truncating the returned bytes 281: // Remaining space in rxdata 282: //bytes_remain = (*rxsize - total_bytes); 283: // Use the minimum between number of bytes read and remaining space 284: //bytes_to_cpy = min(bytes_remain, bytes_to_cpy); 285: 286: // Copy the received data 287: memcpy(&rxdata[total_bytes], &buffer[0], bytes_to_cpy); 288: total_bytes += bytes_to_cpy; 289: } 290: 291: } 292: while (0); 293: 294: *rxsize = total_bytes; 295: #ifdef KIT_DEBUG 296: printf("<-- %s", rxdata); 297: #endif 298: return status; 299: } 300:

A similar situation is found below, where the function is constantly trying to read ‘cfg->atcahid.packetsize + 1)’ and then copy to ‘rxdata’ at lines 365 without performing any bounds checking.

File: secure_elements/lib/hal/hal_win_kit_hid.c
333:     while (continue_read == true)
334:     {
335:         result = ReadFile(pHid->kits[hidid].read_handle, buffer, (cfg->atcahid.packetsize + 1), &bytes_read, NULL);
336:         if (result == false)
337:         {
338:             return ATCA_RX_FAIL;
339:         }
340:         // Find the location of the '\n' character in read buffer
341:         // todo: generalize this read...  it only applies if there is an ascii protocol with an <eom> of \n and if the <eom> exists
342:         location = strchr((char*)&buffer[1], '\n');
343:         if (location == NULL)
344:         {
345:             // Copy all of the bytes
346:             bytes_to_cpy = bytes_read;
347:             // An extra byte is prepended to HID communication
348:             size_adjust = 1;
349:         }
350:         else
351:         {
352:             // Copy only the bytes remaining in the read buffer to the <eom>
353:             bytes_to_cpy = (uint8_t)(location - (char*)buffer);
354:             size_adjust = 0;
355:             // The response has been received, stop receiving more data
356:             continue_read = false;
357:         }
358:         // Protect rxdata from overwriting, this will have the result of truncating the returned bytes
359:         // Remaining space in rxdata
360:         //bytes_remain = (*rxsize - total_bytes);
361:         // Use the minimum between number of bytes read and remaining space
362:         //bytes_to_cpy = min(bytes_remain, bytes_to_cpy);
363: 
364:         // Copy the received data
365:         memcpy(&rxdata[total_bytes], &buffer[1], bytes_to_cpy);
366:         total_bytes += bytes_to_cpy - size_adjust;
367:     }

Infineon - www.infineon.com

Vulnerability

Memory corruption via ‘DtlsRL_Record_ProcessRecord’ 

Affected Products

Optiga Trust X DTLS: https://github.com/Infineon/optiga-trust-x/tree/master/optiga/dtls

Background

Infineon Website

Impact

A local attacker able to interfere with the physical I2C bus between the Host and Optiga Trust X security chip could send a malformed DTLS record that corrupts heap memory in the Host, thus potentially achieving local code execution.

Technical Details

During the DTLS handshake, the fragment length field of a DTLS record is not properly sanitized. 

File: optiga_trust_x/optiga/dtls/DtlsRecordLayer.c
159:         wRecvFragLen = Utility_GetUint16(PpsBlobRecord->prgbStream + OFFSET_RL_FRAG_LENGTH);

wRecvFragLen’ is an attacker-controlled 16-bit value, which is used in the subsequent record processing logic without being sanitized. As a result, it is possible to trigger memory corruption at line 235 

File: optiga_trust_x/optiga/dtls/DtlsRecordLayer.c
233:             PpsRecData->bContentType = bContentType;
234:             //No Decryption, just copy the data
235:             Utility_Memmove(PpsRecData->psBlobInOutMsg->prgbStream, \
236:                     PpsBlobRecord->prgbStream + OFFSET_RL_FRAGMENT, \
237:                     wRecvFragLen);
238:             PpsRecData->psBlobInOutMsg->wLen = wRecvFragLen;
239:             i4Status = OCP_RL_OK;

PpsRecData->psBlobInOutMsg->prgbStream’ points to a dynamically allocated buffer whose size is fixed (TLBUFFER_SIZE 1500 bytes).

File: optiga_trust_x/optiga/dtls/DtlsHandshakeProtocol.c 
1361:     sMessageLayer.sTLMsg.prgbStream = (uint8_t*)OCP_MALLOC(TLBUFFER_SIZE);

The vulnerable path would be as follows: 

  1. DtlsHS_Handshake 
  2. DtlsHS_ReceiveFlightMessage
  3. DtlsRL_Recv
  4. DtlsCheckReplay 
  5. DtlsRL_CallBack_ValidateRec
  6. DtlsRL_Record_ProcessRecord

Vulnerability

Memory corruption via ‘CmdLib_CalcHash’ 

Affected Products

Optiga Trust X: https://github.com/Infineon/optiga-trust-x/

Impact

A local attacker able to interfere with the physical I2C bus between the Host and Optiga Trust X security chip could send a malformed ‘CmdLib_CalcHash’ response that corrupts memory in the Host, thus potentially achieving local code execution.

Technical Details

In the ‘CmdLib_CalcHash’ function, a potentially untrusted length field is used without being sanitized. 

The tag length is directly obtained from the response buffer at line 1757, which could contain a value larger than ‘PpsCalcHash->sOutHash.wBufferLength’.Then at line 1758, this length is used to perform a ‘memcpy’ operation that will trigger the memory corruption. The same issue applies to lines 1772 and 1773

File: optiga_trust_x/optiga/cmd/CommandLib.c
1748:         if((TAG_HASH_OUTPUT == (*(sApduData.prgbRespBuffer + LEN_APDUHEADER))) && (sApduData.wResponseLength != 0))
1749:         {
1750:             //Length check for sOutData
1751:             if((psHashinfo->bHashLen) > PpsCalcHash->sOutHash.wBufferLength)
1752:             {
1753:                 i4Status = (int32_t)CMD_LIB_INSUFFICIENT_MEMORY;
1754:                 break;
1755:             }
1756: 
1757:             PpsCalcHash->sOutHash.wRespLength = Utility_GetUint16(sApduData.prgbRespBuffer + LEN_APDUHEADER + BYTES_SEQ);
1758:             OCP_MEMCPY(PpsCalcHash->sOutHash.prgbBuffer, (sApduData.prgbRespBuffer + CALC_HASH_FIXED_OVERHEAD_SIZE), PpsCalcHash->sOutHash.wRespLength);
1759:         }
1760: 
1761:         //Validate the Context buffer size if the 0x06 context data tag is there in response and 
1762:         //copy the context data to pbContextData buffer
1763:         if((TAG_CONTEXT_OUTPUT == (*(sApduData.prgbRespBuffer + LEN_APDUHEADER))) && (sApduData.wResponseLength != 0))
1764:         {
1765:             //Length check for Context Data
1766:             if((psHashinfo->wHashCntx) > PpsCalcHash->sContextInfo.dwContextLen)
1767:             {
1768:                 i4Status = (int32_t)CMD_LIB_INSUFFICIENT_MEMORY;
1769:                 break;
1770:             }
1771: 
1772:             PpsCalcHash->sContextInfo.dwContextLen = Utility_GetUint16(sApduData.prgbRespBuffer + LEN_APDUHEADER + BYTES_SEQ);
1773:             OCP_MEMCPY(PpsCalcHash->sContextInfo.pbContextData, (sApduData.prgbRespBuffer + CALC_HASH_FIXED_OVERHEAD_SIZE), PpsCalcHash->sContextInfo.dwContextLen);

 

Vulnerability

Multiple memory corruption issues in ‘Optiga_cmd.c’  

Affected Products

Optiga Trust M: https://github.com/Infineon/optiga-trust-m/

Background


Impact

A local attacker able to interfere with the physical I2C bus between the Host and Optiga Trust M security chip could send a malformed response that corrupts memory in the Host, thus potentially achieving local code execution.

Technical Details

In the ‘optiga_cmd_gen_keypair_handler’ function, a potentially untrusted length field is used without being sanitized. 

The private key tag length is directly obtained from the response buffer at line 2576, which could contain a value larger than the buffer pointed by ‘p_optiga_ecc_gen_keypair->private_key’. Then at line 2579, this length is used to perform a ‘memcpy’ operation that will trigger the memory corruption. 

File: infineon/optiga_cmd.c
2573:                 else if (CMD_GEN_KEY_PAIR_PRIVATE_KEY_TAG == me->p_optiga->optiga_comms_buffer[OPTIGA_CMD_APDU_INDATA_OFFSET +
2574:                                                              header_offset])
2575:                 {
2576:                     optiga_common_get_uint16(&me->p_optiga->optiga_comms_buffer[OPTIGA_CMD_APDU_INDATA_OFFSET + header_offset
2577:                                              + OPTIGA_CMD_NO_OF_BYTES_IN_TAG], &private_key_length);
2578: 
2579:                     pal_os_memcpy(p_optiga_ecc_gen_keypair->private_key,
2580:                                   &me->p_optiga->optiga_comms_buffer[OPTIGA_CMD_APDU_INDATA_OFFSET + header_offset +
2581:                                   OPTIGA_CMD_UINT16_SIZE_IN_BYTES+ OPTIGA_CMD_NO_OF_BYTES_IN_TAG], private_key_length);
2582:                     header_offset +=  OPTIGA_CMD_UINT16_SIZE_IN_BYTES+ OPTIGA_CMD_NO_OF_BYTES_IN_TAG + private_key_length;
2583:                     return_status = OPTIGA_LIB_SUCCESS;

The same issue applies to lines 3068 and 3072 while processing the ‘Calc_Hash’ response in function ‘optiga_cmd_calc_hash_handler’.

File:  infineon/optiga_cmd.c
3052:         case OPTIGA_CMD_EXEC_PROCESS_RESPONSE:
3053:         {
3054:             OPTIGA_CMD_LOG_MESSAGE("Processing response for calculate hash command...");
3055:             // check if the write was successful
3056:             if (OPTIGA_CMD_APDU_FAILURE == me->p_optiga->optiga_comms_buffer[OPTIGA_COMMS_DATA_OFFSET])
3057:             {
3058:                 OPTIGA_CMD_LOG_MESSAGE("Error in processing calculate hash response...");
3059:                 SET_DEV_ERROR_NOTIFICATION(OPTIGA_CMD_EXIT_HANDLER_CALL);
3060:                 break;
3061:             }
3062:             if (OPTIGA_CRYPT_HASH_FINAL == p_optiga_calc_hash->hash_sequence)
3063:             {
3064:                 if (OPTIGA_CRYPT_HASH_DIGEST_OUT != me->p_optiga->optiga_comms_buffer[OPTIGA_CMD_APDU_INDATA_OFFSET])
3065:                 {
3066:                    break;
3067:                 }
3068:                 optiga_common_get_uint16(&me->p_optiga->optiga_comms_buffer[OPTIGA_CMD_APDU_INDATA_OFFSET +
3069:                                          OPTIGA_CMD_NO_OF_BYTES_IN_TAG], &out_data_size);
3070: 
3071:                 pal_os_memcpy(p_optiga_calc_hash->p_out_digest,
3072:                               &me->p_optiga->optiga_comms_buffer[OPTIGA_CMD_APDU_INDATA_OFFSET + OPTIGA_CMD_UINT16_SIZE_IN_BYTES +
3073:                               OPTIGA_CMD_NO_OF_BYTES_IN_TAG], out_data_size);
3074:             }


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.