A secure element host library bug

As part of our mission to bring state of the art security to embedded devices, Teserakt occasionally works with customers to review the security of their products. With our cryptographic expertise we are often asked to look at the design of cryptographic solutions, but we also review code for potential security-sensitive issues.

One of our customers had chosen to use the A71CH Secure Element, which at its simplest is a smartcard element for embedded circuitry capable of public key cryptography on behalf of the host MCU. Such devices, like smartcards, store public-private keypairs in such a way that the private component is next-to-impossible to extract (in theory), as such, for high-assurance systems they are hugely important.

The host MCU, the device to which the secure element is connected, speaks to the A71CH device using the GlobalPlatformPro smartcard standards, specifically SCP11. To facilitate this, NXP provide a host library that can be linked with embedded firmware to talk to the device.

During our review, we were asked to look specifically at components of the NXP library code to ensure this was also free of potential errors. Here we found some classic overflows. First, the deprecated code for selecting an applet is as follows:

U32 GP_SelectApplet(U8 * pAppletName, U8 appletNameLength, U8 * pResponse, U32 * pResponseLength)
{
U32 st = 0;
U8 txBuf[128];
U8 len = appletNameLength;
txBuf[0] = CLA_ISO7816;
txBuf[1] = INS_GP_SELECT;
txBuf[2] = 0x04; txBuf[3] = 0x00;
txBuf[4] = len;
// AV: buffer overflow here. max(U8)=255, sizeof txBuf=128.
// fix this with:
// #DEFINE TRANS_APPLET_OFFSET 5
// memcpy(&txBuf[TRANS_APPLET_OFFSET], pAppletName,min(appletNameLength, sizeof(txBuf) - TRANS_APPLET_OFFSET));
memcpy(&txBuf[5], pAppletName, len);
txBuf[5+ len] = 0x00;
assert(pAppletName != NULL);
st = smCom_TransceiveRaw(txBuf, 6+len, pResponse, pResponseLength);
return st;
}

We have included our review comments. The problem should be obvious to anyone familiar with secure coding in C: the pAppletName buffer can contain more than 128 bytes of data, which will overflow into the return address and onto the stack.

Having seen this, we next looked at the non-deprecated code for applet selection. This starts at:

U16 GP_Select(U8 *appletName, U16 appletNameLen, U8 *responseData, U16 *responseDataLen)
{
U16 rv = 0;
apdu_t apdu;
apdu_t * pApdu = (apdu_t *) &apdu;
U8 isOk = 0x00;
assert(appletName != NULL);
assert(responseData != NULL);
pApdu->cla = CLA_ISO7816;
pApdu->ins = INS_GP_SELECT;
pApdu->p1 = 0x04;
pApdu->p2 = 0x00;
AllocateAPDUBuffer(pApdu);
SetApduHeader(pApdu, USE_STANDARD_APDU_LEN);
smApduAppendCmdData(pApdu, appletName, appletNameLen);
rv = (U16)scp_Transceive(pApdu, SCP_MODE);
if (rv == SMCOM_OK) {
rv = smGetSw(pApdu, &isOk);
if (isOk) {
rv = smApduGetResponseBody(pApdu, responseData, responseDataLen);
}
}
FreeAPDUBuffer(pApdu); return rv;
}

So this code is building an apdu_t data type (application protocol data unit), which is defined as follows

typedef struct
{
U8 cla;
U8 ins;
U8 p1;
U8 p2;
U8* pBuf;
U16 buflen;
U16 rxlen;
U8 extendedLength;
U8 hasData;
U16 lc;
U8 lcLength;
U8 hasLe;
U16 le;
U8 leLength;
U16 offset;
} apdu_t;

The interesting part here is pBuf and how it is manipulated, as this is where any payload data, such as the applet name, will go. Here is the implementation of this function:

// defined elsewhere:
define MAX_APDU_BUF_LENGTH 1454
// defined in sm_apdu.c:
ifndef USE_MALLOC_FOR_APDU_BUFFER
static U8 sharedApduBuffer[MAX_APDU_BUF_LENGTH];
endif
U8 AllocateAPDUBuffer(apdu_t * pApdu)
{
// AV: again, no null pointer checks.
assert(pApdu);
// In case of e.g. TGT_A7, pApdu is pointing to a structure defined on the stack
// so pApdu->pBuf contains random data
#ifdef USE_MALLOC_FOR_APDU_BUFFER
pApdu->pBuf = (U8*) malloc(MAX_APDU_BUF_LENGTH);
#else
pApdu->pBuf = sharedApduBuffer;
#endif
return 0;
}

Here we see that there are two options depending on whether dynamic allocation is supported on the target platform. If it is, a buffer of 1454 bytes is allocated dynamically. If it isn’t, a static buffer is provided as part of the firmware image for use.

Now if we go back to GP_Select, we see that it calls smApduAppendCmdData to add the applet name to the apdu. Let’s look at the details of this:

U16 smApduAppendCmdData(apdu_t *pApdu, const U8 *data, U16 dataLen)
{
// If this is the first commmand data section added to the buffer, we needs to ensure
// the correct offset is used writing the data. This depends on
// whether the APDU is a standard or an extended APDU.
if (pApdu->hasData == 0)
{
pApdu->hasData = 1;
ReserveLc(pApdu);
}
pApdu->lc += dataLen; // Value
memcpy(&pApdu->pBuf[pApdu->offset], data, dataLen);
pApdu->offset += dataLen; // adapt length
pApdu->buflen = pApdu->offset;
return pApdu->offset;
}

Here again, we have a problem. We know that pBuf is 1454 bytes, but dataLen can be set up to 65536 bytes. memcpy will copy regardless of the value of any byte, unlike strcpy, which terminates on null characters. The only difference here is that we will overwrite either the heap, or somewhere higher than the allocated location of the static buffer, depending on the configuration at compile time.

The most obvious question is: how exploitable is this? The answer really is: it depends. In the use case we were considering, the applet name was always the hardcoded string “a71ch”, although NXP code often derived the length of this using strlen. It would therefore require an attacker to overwrite these values in order to achieve any meaningful exploit.

In more complicated scenarios, this could easily be exploitable. This is especially true as in an embedded context, the many exploit mitigations present in modern operating systems do not exist; the operating system itself may be very barebones and all code is running in ARM’s supervisor context (equivalent to x86’s “ring 0”).

We also noted a number of “assert” null pointer checks during this review. These are “no-ops” unless code is built specifically to be debugged and so do not perform any kind of safety check at all. Again, if you are familiar with computer security you will already understand why this is a problem; if not, the quick explanation is that null pointers indicate memory at “address 0”. An attacker can control a program simply by writing to this well-known address, using a condition where a null pointer is present. Most modern desktop operating systems deliberately allocate the null page, with permissions set to deny any kind of access. This causes applications to crash when access using a null pointer is attempted. Embedded systems may not include such defences.

On behalf of our customer, we reported all of these issues to the vendor NXP and the issues have since been fixed in the latest 1.06 build of the host library. Firstly, in smApduAppendCmdData a maximum payload length is computed

// The maximum amount of data payload depends on (whichever is smaller) 
// - STD-APDU (MAX=255 byte) / EXTENDED-APDU (MAX=65536 byte)
// - size of pApdu->pBuf (MAX_APDU_BUF_LENGTH)
// Standard Length APDU's:
// There is a pre-processor macro in place that ensures 'pApdu->pBuf' is of sufficient size
// Extended Length APDU's (not used by A71CH):
// APDU payload restricted by buffersize of 'pApdu->pBuf'
U16 maxPayload_noLe;
if (pApdu->extendedLength) {
maxPayload_noLe = MAX_APDU_BUF_LENGTH - EXT_CASE4_APDU_OVERHEAD;
}
else
{
maxPayload_noLe = APDU_HEADER_LENGTH + APDU_STD_MAX_DATA;
}

Armed with this information, the length is later checked:

// Value
if (dataLen <= (maxPayload_noLe - pApdu->offset))
{
memcpy(&pApdu->pBuf[pApdu->offset], data, dataLen);
pApdu->offset += dataLen;
}
else
{
return ERR_INTERNAL_BUF_TOO_SMALL;
}

Which prevents data being copied into the buffer unless it is within bounds. The deprecated code has been removed entirely.

We believe this should serve as a cautionary tale about the state of security of embedded devices, or “IoT”. With a lot of old code in use, and code written in memory-unsafe languages such as C, it is difficult to build high-assurance hardware. Indeed, it is very easy for even the best of programmers to write code containing such bugs.

This post does however have a silver lining. We have only good things to say about NXP’s response to our submission. NXP patched and issued a new release well within the standard 90 day disclosure deadline, and kept us updated throughout the process. NXP PSIRT’s response should be held up as an example of how companies should accept security reports

Details:

  • Affected version: 1.05 (prior versions may be affected).
  • Fixed version: 1.06
  • Reported to NXP: 2019-02-25
  • Fixed (software available): 2019-03-18
  • Software download: here.