Skip to content

Commit 3b6ae05

Browse files
committed
Check that a capability container fits before trusting its block count
1 parent 4160eff commit 3b6ae05

2 files changed

Lines changed: 49 additions & 24 deletions

File tree

src/nfc/layer/v/nfc_layer_v.cpp

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -625,25 +625,48 @@ bool NFCLayerV::reset_to_ready(const PICC* picc)
625625
return rx[0] == 0x00;
626626
}
627627

628-
void NFCLayerV::probe_memory_layout(m5::nfc::v::PICC& picc)
628+
// Read one block while addressing the PICC by UID, which needs nothing the inventory did not give
629+
// us. Blocks past the first 256 need the extended command, which not every PICC carries.
630+
bool NFCLayerV::probe_read_block(const m5::nfc::v::PICC& picc, const uint16_t block, uint8_t* rx, uint16_t& rx_len)
629631
{
630-
// Read block zero while addressing the PICC by UID, which needs nothing we do not have yet
632+
if (block > 0xFF) {
633+
uint8_t tmp[32]{};
634+
if (!read_block_ext(tmp, picc, block)) {
635+
return false;
636+
}
637+
rx_len = std::min<uint16_t>(rx_len, sizeof(tmp));
638+
memcpy(rx, tmp, rx_len);
639+
return true;
640+
}
641+
631642
uint8_t frame[2 + 8 + 1]{};
632643
make_frame(frame, address_flag | data_rate_flag, m5::stl::to_underlying(Command::ReadSingleBlock), &picc);
633-
frame[10] = 0x00;
644+
frame[10] = static_cast<uint8_t>(block);
634645

635646
uint8_t rbuf[32 + 1]{};
636-
uint16_t rx_len = sizeof(rbuf);
637-
const bool read =
638-
_impl->transceive(rbuf, rx_len, frame, sizeof(frame), TIMEOUT_READ_SINGLE_BLOCK, modulationMode()) &&
639-
rx_len > 1 && rbuf[0] == 0x00;
647+
uint16_t len = sizeof(rbuf);
648+
if (!_impl->transceive(rbuf, len, frame, sizeof(frame), TIMEOUT_READ_SINGLE_BLOCK, modulationMode()) || len < 2 ||
649+
rbuf[0] != 0x00) {
650+
return false;
651+
}
652+
// The answer carries the flags byte and then the block itself
653+
rx_len = std::min<uint16_t>(rx_len, len - 1);
654+
memcpy(rx, rbuf + 1, rx_len);
655+
return true;
656+
}
640657

641-
// The answer carries the flags byte and then one block, so its length gives the block size away
658+
void NFCLayerV::probe_memory_layout(m5::nfc::v::PICC& picc)
659+
{
660+
uint8_t cc[8]{};
661+
uint16_t got = sizeof(cc);
662+
const bool read = probe_read_block(picc, 0, cc, got);
663+
664+
// One block came back, so its length gives the block size away
642665
if (!picc.block_size) {
643666
if (!read) {
644667
M5_LIB_LOGW("Could not read block 0; assuming %u byte blocks", DEFAULT_BLOCK_SIZE);
645668
}
646-
picc.block_size = read ? static_cast<uint8_t>(rx_len - 1) : DEFAULT_BLOCK_SIZE;
669+
picc.block_size = read ? static_cast<uint8_t>(got) : DEFAULT_BLOCK_SIZE;
647670
}
648671

649672
if (picc.blocks) {
@@ -654,29 +677,30 @@ void NFCLayerV::probe_memory_layout(m5::nfc::v::PICC& picc)
654677
// eight when MLEN needs more room than one byte, which it signals by leaving the short MLEN
655678
// zero. The second half then lives in the next block unless the blocks are big enough to hold
656679
// it all. MLEN measures the memory in units of 8 bytes (NFC Forum T5T 4.3.1.17).
657-
uint8_t cc[8]{};
658-
uint32_t cc_len{};
659-
if (read) {
660-
cc_len = std::min<uint32_t>(rx_len - 1, sizeof(cc));
661-
memcpy(cc, rbuf + 1, cc_len);
662-
}
680+
uint16_t cc_len = read ? got : 0;
663681
if (cc_len >= 4 && (cc[0] == 0xE1 || cc[0] == 0xE2) && !cc[2] && cc_len < sizeof(cc)) {
664-
frame[10] = 0x01;
665-
uint16_t len = sizeof(rbuf);
666-
const bool more =
667-
_impl->transceive(rbuf, len, frame, sizeof(frame), TIMEOUT_READ_SINGLE_BLOCK, modulationMode()) &&
668-
len > 1 && rbuf[0] == 0x00;
669-
if (more) {
670-
const uint32_t take = std::min<uint32_t>(len - 1, sizeof(cc) - cc_len);
671-
memcpy(cc + cc_len, rbuf + 1, take);
672-
cc_len += take;
682+
uint16_t more = sizeof(cc) - cc_len;
683+
if (probe_read_block(picc, 1, cc + cc_len, more)) {
684+
cc_len += more;
673685
}
674686
}
675687

676688
if (cc_len >= 4 && (cc[0] == 0xE1 || cc[0] == 0xE2)) {
677689
const uint32_t mlen = cc[2] ? cc[2] : (cc_len >= 8 ? (((uint32_t)cc[6] << 8) | cc[7]) : 0U);
678690
picc.blocks = static_cast<uint16_t>(mlen * 8U / picc.block_size);
679691
}
692+
693+
// A container can be stale, or written by something that got the units wrong, so make sure the
694+
// last block it claims really answers before taking its word for it
695+
if (picc.blocks) {
696+
uint8_t tmp[32]{};
697+
uint16_t tmp_len = sizeof(tmp);
698+
if (!probe_read_block(picc, picc.blocks - 1, tmp, tmp_len)) {
699+
M5_LIB_LOGW("The capability container claims %u blocks but the last one does not answer", picc.blocks);
700+
picc.blocks = 0;
701+
}
702+
}
703+
680704
if (!picc.blocks) {
681705
// Neither the PICC nor a capability container said how big it is, so the caller gets a guess
682706
M5_LIB_LOGW("Assuming %u blocks of %u bytes; reads past the end of %s will fail", DEFAULT_BLOCKS,

src/nfc/layer/v/nfc_layer_v.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ class NFCLayerV : public NFCLayerInterface {
243243
bool get_system_information(m5::nfc::v::PICC& picc);
244244
bool get_system_information_ext(m5::nfc::v::PICC& picc);
245245
void probe_memory_layout(m5::nfc::v::PICC& picc);
246+
bool probe_read_block(const m5::nfc::v::PICC& picc, const uint16_t block, uint8_t* rx, uint16_t& rx_len);
246247
bool read_block_ext(uint8_t rx[32], const m5::nfc::v::PICC& picc, const uint16_t block);
247248
bool write_block_ext(const m5::nfc::v::PICC& picc, const uint16_t block, const uint8_t* tx, const uint8_t tx_len);
248249
bool reset_to_ready(const m5::nfc::v::PICC* picc);

0 commit comments

Comments
 (0)