Skip to content

Commit 13d51a5

Browse files
committed
Fixed FAST_READ command and handling of dynamic and static locks for NTG213 NTG215 and NTG216 tags
1 parent 97dfe5b commit 13d51a5

File tree

3 files changed

+242
-24
lines changed

3 files changed

+242
-24
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
33
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
44

55
## [unreleased][unreleased]
6+
- Fix for FAST_READ command for nfc - mf0 tags
7+
- Rewrite of the dynamic and static locks logic for NTAG213, NTAG215 and NTAG216
68
- Fix for static nested key recovery (@jekkos)
79

810
## [v2.1.0][2025-09-02]

firmware/application/src/rfid/nfctag/hf/nfc_14a.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,15 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
397397
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
398398
}
399399
return;
400+
case NFC_TAG_14A_CMD_REQA:
401+
case NFC_TAG_14A_CMD_WUPA:
402+
// Reader is re-sending REQA/WUPA while in READY state
403+
// This can happen if reader retries or if frame was received incorrectly
404+
// Respond with ATQA again and stay in READY state
405+
if (auto_coll_res != NULL) {
406+
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
407+
}
408+
return;
400409
default: {
401410
// After receiving the wrong level instruction, directly reset the status machine
402411
NRF_LOG_INFO("[MFEMUL_SELECT] Incorrect cascade level received: %02x", p_data[0]);

firmware/application/src/rfid/nfctag/hf/nfc_mf0_ntag.c

Lines changed: 231 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -675,20 +675,127 @@ static void handle_fast_read_command(uint8_t block_num, uint8_t end_block_num) {
675675

676676
int block_max = get_block_max_by_tag_type(m_tag_type, true);
677677

678-
if (block_num >= end_block_num || end_block_num >= block_max) {
678+
if (block_num > end_block_num || end_block_num >= block_max) {
679679
nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4);
680680
return;
681681
}
682682

683683
NRF_LOG_INFO("HANDLING FAST READ %02x %02x", block_num, end_block_num);
684+
// FAST_READ is inclusive: read from block_num to end_block_num (both included)
685+
handle_any_read(block_num, end_block_num - block_num + 1, block_max);
686+
}
687+
688+
// Call with:
689+
// - page: page number to check
690+
// - user_memory_end_page: page index of the first config page (end of user memory page index + 1), used to locate dynamic lock bytes
691+
// - dyn_lock_bit_page_cnt: 2 NTAG212/213, 16 for NTAG215/216
692+
static bool is_page_locked_ntag21x(uint16_t page,
693+
uint16_t user_memory_end_page,
694+
int dyn_lock_bit_page_cnt)
695+
{
696+
if (page < 3) {
697+
return true;
698+
}
699+
700+
if (page >= 3 && page <= 7) {
701+
uint8_t bit_index = (uint8_t)(page - 3); // 0 -> page3,4 -> page 7
702+
return (((m_tag_information->memory[2][2] >> 4 ) >> bit_index) & 0x1) != 0;
703+
}
704+
if (page >= 8 && page <= 15) {
705+
uint8_t bit_index = (uint8_t)(page - 8); // 0 -> page3, 12 -> page15
706+
return (((m_tag_information->memory[2][3] ) >> bit_index) & 0x1) != 0;
707+
}
708+
709+
// Determine the start and end of the dynamic-lock-covered user pages:
710+
const uint16_t dyn_start = 16;
711+
const uint16_t dyn_end = (uint16_t)(user_memory_end_page - 1); // inclusive
684712

685-
handle_any_read(block_num, end_block_num - block_num, block_max);
713+
if (page >= dyn_start && page <= dyn_end) {
714+
715+
// For NTAG21x the dynamic lock bytes are in bytes 0..2 of that page.
716+
// We'll interpret them as three bytes [b0, b1, b2].
717+
uint8_t b0 = m_tag_information->memory[user_memory_end_page][0];
718+
uint8_t b1 = m_tag_information->memory[user_memory_end_page][1];
719+
uint8_t b2 = m_tag_information->memory[user_memory_end_page][2];
720+
// Each dynamic lock bit covers 'dyn_lock_bit_page_cnt' pages.
721+
uint32_t relative = (uint32_t)(page - dyn_start);
722+
uint32_t block_index = relative / (uint32_t)dyn_lock_bit_page_cnt; // which bit overall
723+
724+
// block_index maps into b0..b2 (bits 0..23)
725+
uint8_t which_byte = (uint8_t)(block_index / 8);
726+
uint8_t which_bit = (uint8_t)(block_index % 8);
727+
728+
uint8_t target = 0;
729+
if (which_byte == 0) target = b0;
730+
else if (which_byte == 1) target = b1;
731+
else if (which_byte == 2) target = b2;
732+
else target = 0; // out-of-range -> treated as unlocked
733+
734+
return (((target >> which_bit) & 1) != 0);
735+
}
736+
return false;
686737
}
687738

688-
static bool check_ro_lock_on_page(int block_num) {
689-
if (block_num < 3) return true;
690-
else if (block_num == 3) return (m_tag_information->memory[2][2] & 9) != 0; // bits 0 and 3
691-
else if (block_num <= MF0ICU1_PAGES) {
739+
// Get user memory end page for NTAG21x tags, returns 0 for non-NTAG21x tags
740+
static int get_user_memory_end_page_by_tag_type(tag_specific_type_t tag_type) {
741+
switch (tag_type) {
742+
case TAG_TYPE_NTAG_213:
743+
return NTAG213_USER_MEMORY_END;
744+
case TAG_TYPE_NTAG_215:
745+
return NTAG215_USER_MEMORY_END;
746+
case TAG_TYPE_NTAG_216:
747+
return NTAG216_USER_MEMORY_END;
748+
default:
749+
return 0;
750+
}
751+
}
752+
753+
// Handle special write cases for dynamic lock bytes and config pages
754+
// Returns ACK_VALUE if handled, 0 if not a special case
755+
static int handle_special_page_write(uint8_t block_num, uint8_t *p_data) {
756+
int user_memory_end_page = get_user_memory_end_page_by_tag_type(m_tag_type);
757+
int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type);
758+
759+
// Handle dynamic lock bytes page (user_memory_end page) for NTAG21x tags
760+
if (user_memory_end_page > 0 && block_num == user_memory_end_page) {
761+
// Dynamic lock bytes are OR'ed just like static lock bytes
762+
// Only bytes 0-2 contain lock bits, byte 3 is RFU (reserved, typically 0x00)
763+
m_tag_information->memory[block_num][0] |= p_data[0];
764+
m_tag_information->memory[block_num][1] |= p_data[1];
765+
m_tag_information->memory[block_num][2] |= p_data[2];
766+
m_tag_information->memory[block_num][3] = p_data[3]; // Byte 3 is RFU, write it directly
767+
return ACK_VALUE;
768+
}
769+
770+
// Handle PWD and PACK pages (always writable even when CFGLCK is set)
771+
// PWD and PACK remain writable to allow password changes
772+
if (first_cfg_page > 0 && (block_num == first_cfg_page + 2 || block_num == first_cfg_page + 3)) {
773+
memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE);
774+
return ACK_VALUE;
775+
}
776+
777+
// Handle config pages (CFG0 and CFG1) - check CFGLCK protection
778+
if (first_cfg_page > 0 && (block_num == first_cfg_page || block_num == first_cfg_page + 1)) {
779+
// Check CFGLCK bit (bit 6 of CFG0 byte 0)
780+
// Per spec: CFGLCK permanently write-protects configuration pages
781+
uint8_t cfglck = m_tag_information->memory[first_cfg_page][0] & 0x40;
782+
if (cfglck != 0) {
783+
// Config pages are locked
784+
return NAK_INVALID_OPERATION_TBV;
785+
}
786+
// Not locked, allow write
787+
memcpy(m_tag_information->memory[block_num], p_data, NFC_TAG_MF0_NTAG_DATA_SIZE);
788+
return ACK_VALUE;
789+
}
790+
791+
// Not a special case
792+
return 0;
793+
}
794+
795+
// Old v2.1.0 algorithm for MF0ICU1, MF0ICU2, MF0UL11, MF0UL21 tags
796+
static bool check_ro_lock_on_page_mf0(int block_num) {
797+
// This function assumes block_num > 3 (pages 0-3 are handled by caller)
798+
if (block_num <= MF0ICU1_PAGES) {
692799
bool locked = false;
693800

694801
// check block locking bits
@@ -751,18 +858,6 @@ static bool check_ro_lock_on_page(int block_num) {
751858
user_memory_end = NTAG212_USER_MEMORY_END;
752859
dyn_lock_bit_page_cnt = 2;
753860
break;
754-
case TAG_TYPE_NTAG_213:
755-
user_memory_end = NTAG213_USER_MEMORY_END;
756-
dyn_lock_bit_page_cnt = 2;
757-
break;
758-
case TAG_TYPE_NTAG_215:
759-
user_memory_end = NTAG215_USER_MEMORY_END;
760-
dyn_lock_bit_page_cnt = 16;
761-
break;
762-
case TAG_TYPE_NTAG_216:
763-
user_memory_end = NTAG216_USER_MEMORY_END;
764-
dyn_lock_bit_page_cnt = 16;
765-
break;
766861
default:
767862
ASSERT(false);
768863
break;
@@ -790,14 +885,90 @@ static bool check_ro_lock_on_page(int block_num) {
790885
}
791886
}
792887

888+
// Check if a page is read-only locked
889+
// NOTE: This function is only called for block_num >= 3
890+
// Blocks 0-2, dynamic locks, and config pages are handled elsewhere
891+
static bool check_ro_lock_on_page(int block_num) {
892+
// Page 3 (OTP/CC page) - check lock bits
893+
if (block_num == 3) {
894+
return (m_tag_information->memory[2][2] & 9) != 0; // bits 0 and 3
895+
}
896+
897+
// For MF0ICU1, MF0ICU2, MF0UL11, MF0UL21, NTAG_210 and NTAG_212 tags - use old v2.1.0 algorithm. TODO try to merge the two
898+
switch (m_tag_type) {
899+
case TAG_TYPE_MF0ICU1:
900+
case TAG_TYPE_MF0ICU2:
901+
case TAG_TYPE_MF0UL11:
902+
case TAG_TYPE_MF0UL21:
903+
case TAG_TYPE_NTAG_210:
904+
case TAG_TYPE_NTAG_212:
905+
return check_ro_lock_on_page_mf0(block_num);
906+
default:
907+
break;
908+
}
909+
910+
// For NTAG21x tags
911+
int user_memory_end = get_user_memory_end_page_by_tag_type(m_tag_type);
912+
if (user_memory_end == 0) {
913+
ASSERT(false);
914+
return false;
915+
}
916+
917+
if (block_num < user_memory_end) {
918+
919+
switch (m_tag_type) {
920+
case TAG_TYPE_NTAG_213: {
921+
return is_page_locked_ntag21x(block_num,
922+
NTAG213_USER_MEMORY_END, 2);
923+
}
924+
case TAG_TYPE_NTAG_215: {
925+
return is_page_locked_ntag21x(block_num,
926+
NTAG215_USER_MEMORY_END, 16);
927+
}
928+
case TAG_TYPE_NTAG_216: {
929+
return is_page_locked_ntag21x(block_num,
930+
NTAG216_USER_MEMORY_END, 16);
931+
}
932+
default:
933+
ASSERT(false);
934+
break;
935+
}
936+
return false;
937+
938+
} else {
939+
// Pages beyond user memory end (password, pack, etc.) are always writable
940+
// Note: Config pages and dynamic lock bytes are handled by handle_special_page_write()
941+
// before this function is ever called
942+
return false;
943+
}
944+
}
945+
793946
static int handle_write_command(uint8_t block_num, uint8_t *p_data) {
794947
int block_max = get_block_max_by_tag_type(m_tag_type, false);
795-
796-
if (block_num >= block_max) {
948+
int first_cfg_page = get_first_cfg_page_by_tag_type(m_tag_type);
949+
uint8_t cfglck = m_tag_information->memory[first_cfg_page][0] & 0x40;
950+
951+
// Check if block_num is within valid range
952+
// Note: block_max already takes AUTH0 protection into account
953+
//TODO Check if other tag types need special handling here
954+
bool is_config_page = ((m_tag_type == TAG_TYPE_NTAG_213)
955+
|| (m_tag_type == TAG_TYPE_NTAG_215) || (m_tag_type == TAG_TYPE_NTAG_216))
956+
&& (block_num >= first_cfg_page) && (block_num <= first_cfg_page + 1);
957+
bool is_beyond_user_memory = (block_num >= block_max);
958+
bool config_locked = (cfglck != 0);
959+
960+
// Reject out-of-bounds writes (except config pages)
961+
if (is_beyond_user_memory && !is_config_page) {
797962
NRF_LOG_ERROR("Write failed: block_num %08x >= block_max %08x", block_num, block_max);
798963
return NAK_INVALID_OPERATION_TBV;
799964
}
800965

966+
// Reject writes to locked config pages
967+
if (config_locked && is_config_page) {
968+
NRF_LOG_ERROR("Write failed: config pages locked (cfglck=0x40)");
969+
return NAK_INVALID_OPERATION_TBV;
970+
}
971+
801972
// Handle writing based on the current write mode
802973
if (m_tag_information->config.mode_block_write == NFC_TAG_MF0_NTAG_WRITE_DENIED) {
803974
// In this mode, reject all write operations
@@ -816,6 +987,19 @@ static int handle_write_command(uint8_t block_num, uint8_t *p_data) {
816987
return ACK_VALUE;
817988
}
818989

990+
switch (m_tag_type) {
991+
case TAG_TYPE_NTAG_213:
992+
case TAG_TYPE_NTAG_215:
993+
case TAG_TYPE_NTAG_216:
994+
// Check if this is a special page (dynamic lock bytes or config pages) and handle it
995+
int special_result = handle_special_page_write(block_num, p_data);
996+
if (special_result != 0) {
997+
return special_result;
998+
}
999+
default:
1000+
break;
1001+
}
1002+
8191003
switch (block_num) {
8201004
case 0:
8211005
case 1:
@@ -872,6 +1056,13 @@ static void handle_read_cnt_command(uint8_t index) {
8721056
static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) {
8731057
uint8_t ctr_page_off;
8741058
uint8_t ctr_page_end;
1059+
uint8_t first_index;
1060+
uint8_t *cnt_data;
1061+
uint32_t incr_value;
1062+
uint32_t cnt;
1063+
1064+
first_index = 0;
1065+
8751066
switch (m_tag_type) {
8761067
case TAG_TYPE_MF0UL11:
8771068
ctr_page_off = MF0UL11_PAGES;
@@ -881,20 +1072,36 @@ static void handle_incr_cnt_command(uint8_t block_num, uint8_t *p_data) {
8811072
ctr_page_off = MF0UL21_PAGES;
8821073
ctr_page_end = ctr_page_off + MF0ULx1_NUM_CTRS;
8831074
break;
1075+
case TAG_TYPE_NTAG_213:
1076+
ctr_page_off = NTAG213_PAGES;
1077+
ctr_page_end = ctr_page_off + NTAG_NUM_CTRS;
1078+
first_index = 2;
1079+
break;
1080+
case TAG_TYPE_NTAG_215:
1081+
ctr_page_off = NTAG215_PAGES;
1082+
ctr_page_end = ctr_page_off + NTAG_NUM_CTRS;
1083+
first_index = 2;
1084+
break;
1085+
case TAG_TYPE_NTAG_216:
1086+
ctr_page_off = NTAG216_PAGES;
1087+
ctr_page_end = ctr_page_off + NTAG_NUM_CTRS;
1088+
first_index = 2;
1089+
break;
8841090
default:
8851091
if (is_ntag()) nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4);
8861092
return;
8871093
}
8881094

8891095
// check that counter index is in bounds
890-
if (block_num >= (ctr_page_end - ctr_page_off)) {
1096+
// NTAG cards expect counter address 2, so adjust for internal storage at offset 0
1097+
if ((block_num < first_index) || ((block_num - first_index) >= (ctr_page_end - ctr_page_off))) {
8911098
nfc_tag_14a_tx_nbit(NAK_INVALID_OPERATION_TBV, 4);
8921099
return;
8931100
}
8941101

895-
uint8_t *cnt_data = m_tag_information->memory[ctr_page_off + block_num];
896-
uint32_t incr_value = ((uint32_t)p_data[0] << 16) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2]);
897-
uint32_t cnt = ((uint32_t)cnt_data[0] << 16) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2]);
1102+
cnt_data = m_tag_information->memory[ctr_page_off + block_num - first_index];
1103+
incr_value = ((uint32_t)p_data[0] << 16) | ((uint32_t)p_data[1] << 8) | ((uint32_t)p_data[2]);
1104+
cnt = ((uint32_t)cnt_data[0] << 16) | ((uint32_t)cnt_data[1] << 8) | ((uint32_t)cnt_data[2]);
8981105

8991106
if ((0xFFFFFF - cnt) < incr_value) {
9001107
// set tearing event flag

0 commit comments

Comments
 (0)