From 204d6639528f9015f5fb2c15410efc74ae14cd9a Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Thu, 15 Oct 2026 02:42:55 +0200 Subject: [PATCH] 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. --- src/Common/EMVCard.cpp | 20 ++++++++++++++++---- src/Common/SCardReader.cpp | 10 ++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Common/EMVCard.cpp b/src/Common/EMVCard.cpp index 70725003..bc7c764b 100644 --- a/src/Common/EMVCard.cpp +++ b/src/Common/EMVCard.cpp @@ -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; + } + } } } } diff --git a/src/Common/SCardReader.cpp b/src/Common/SCardReader.cpp index 67bf31e5..a8bdfba7 100644 --- a/src/Common/SCardReader.cpp +++ b/src/Common/SCardReader.cpp @@ -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;