Security tokens: bound response chaining and EMV directory reads

ApduProcessData sent GET RESPONSE for as long as the card answered
0x61XX, allowing an endless loop and unbounded response growth. Limit
each chain to 256 follow-up commands and erase the partial response
if the limit is exceeded.

PSE directory enumeration had a separate unbounded loop with an
eight-bit record counter. Read only records 1 through 254, stop on an
empty or unsuccessful response, and limit the accumulated directory
data to 254 short records before parsing. Malformed TLV responses
consume this budget too, so they cannot bypass either limit.
This commit is contained in:
Mounir IDRASSI
2026-10-15 07:57:12 +02:00
parent 1ac1c891a6
commit 204d663952
2 changed files with 26 additions and 4 deletions
+16 -4
View File
@@ -252,13 +252,21 @@ namespace VeraCrypt
{
sfi = sfiNode->Value->at(0);
uint8 rec = 1;
do
// Record numbers 0 and 255 are reserved. Use a wider counter
// so a full directory cannot wrap back to the first record.
const unsigned int maxDirectoryRecords = 254;
// Also bound the total data retained across all short records.
size_t remainingDirectoryBytes = maxDirectoryRecords * SCardReader::shortAPDUMaxTransSize;
for (unsigned int rec = 1; rec <= maxDirectoryRecords; ++rec)
{
command = CommandAPDU(CLA_ISO7816, INS_READ_RECORD, rec++, (sfi << 3) | 4, SCardReader::shortAPDUMaxTransSize);
command = CommandAPDU(CLA_ISO7816, INS_READ_RECORD, rec, (sfi << 3) | 4, SCardReader::shortAPDUMaxTransSize);
m_reader->ApduProcessData(command, response);
if (response.getSW() == SW_NO_ERROR && response.getData().size() > 0)
{
if (response.getData().size() > remainingDirectoryBytes)
throw PCSCException(SCARD_E_UNEXPECTED);
remainingDirectoryBytes -= response.getData().size();
try
{
templateNode = TLVParser::TLV_Parse(response.getData().data(), response.getData().size());
@@ -278,7 +286,11 @@ namespace VeraCrypt
continue;
}
}
} while (response.getData().size() > 0);
else
{
break;
}
}
}
}
}
+10
View File
@@ -590,8 +590,18 @@ namespace VeraCrypt
// Send get response to get the rest as long as we receive SW == 0x61XX
// In case of PACE, this is never the case
// A GET RESPONSE returns at most 256 bytes and no response exceeds 65536 bytes,
// so stop a card that keeps answering 0x61XX instead of looping without end
uint32 getResponseCount = 0;
while ((lRet == SCARD_S_SUCCESS) && ((BYTE)(SW >> 8) == (BYTE)(SW_BYTES_REMAINING_00 >> 8))) // 0x61
{
if (++getResponseCount > extendedAPDUMaxTransSize / shortAPDUMaxTransSize)
{
responseAPDU.clear();
lRet = SCARD_E_UNEXPECTED;
break;
}
// GET RESPONSE APDU
pbSendBuffer[0] = commandAPDU.getCLA();
pbSendBuffer[1] = INS_GET_RESPONSE;