CVE-2026-49880 AI-generated analysis
platform/packages/modules/Nfc, libnfc-nci NFA T4T NFCEE (ndefnfcee/t4t/nfa_nfcee_act.cc)
Adds an allocated-size field (p_dataBuf_len) to the T4T NFCEE control block and checks rd_offset + p_data->len against it before the memcpy in nfa_t4tnfcee_store_rx_buf, plus length guards in nfa_t4tnfcee_check_sw and nfa_t4tnfcee_store_cc_info and NULL checks on the two nfa_mem_co_alloc results.
Root cause
nfa_t4tnfcee_store_rx_buf copied p_data->len bytes of received T4T NFCEE data into nfa_t4tnfcee_cb.p_dataBuf at nfa_t4tnfcee_cb.rd_offset and then advanced rd_offset, with no knowledge of the buffer's allocated size. The buffer is allocated earlier with a fixed size: lenDataToBeRead in nfa_t4tnfcee_handle_file_operations, or p_data->len in nfa_t4tnfcee_store_cc_info. Incoming data whose cumulative length exceeds that allocation therefore overflows the heap buffer. The control block had no field recording the allocation size, so no check was possible. Adjacent issues: nfa_t4tnfcee_check_sw computed p += (p_r_apdu->len - T4T_RSP_STATUS_WORDS_SIZE) without checking that len is at least T4T_RSP_STATUS_WORDS_SIZE, so short responses underflow the pointer. The else branch of nfa_t4tnfcee_store_cc_info used p_data->len without checking that it covers 0x07 plus the status bytes (a uint16 underflow per the commit message). Neither nfa_mem_co_alloc result was checked for NULL.
How the fix works
- libnfc-nci/src/nfa/include/nfa_nfcee_int.h:tNFA_T4TNFCEE_CB: adds uint32_t p_dataBuf_len, which records the allocated size of p_dataBuf.
- nfa_nfcee_act.cc:nfa_t4tnfcee_handle_file_operations: after nfa_mem_co_alloc(lenDataToBeRead), a NULL result now sets p_dataBuf_len = 0 and status = NFC_STATUS_FAILED, calls nfa_t4tnfcee_notify_rx_evt() and breaks. Otherwise it records p_dataBuf_len = lenDataToBeRead before calling RW_T4tNfceeReadFile.
- nfa_nfcee_act.cc:nfa_t4tnfcee_store_cc_info (allocating branch): sets p_dataBuf_len = p_data->len and does the memcpy only if the allocation succeeded; otherwise sets p_dataBuf_len = 0.
- nfa_nfcee_act.cc:nfa_t4tnfcee_store_cc_info (else branch): rejects p_data->len < (0x07 + T4TNFCEE_SIZEOF_STATUS_BYTES) with an error log and an early return, before ccInfo is computed with jumpToFirstTLV.
- nfa_nfcee_act.cc:nfa_t4tnfcee_store_rx_buf: does the memcpy and rd_offset += p_data->len only if ((uint32_t)rd_offset + p_data->len) <= p_dataBuf_len, with the sum and the comparison done as unsigned 32-bit. Otherwise it logs, sets nfa_t4tnfcee_cb.status = NFA_STATUS_FAILED and emits android_errorWriteLog(0x534e4554, "508390497").
- nfa_nfcee_act.cc:nfa_t4tnfcee_check_sw: if p_r_apdu->len < T4T_RSP_STATUS_WORDS_SIZE, sets p_rwData->raw_frame.status = NFC_STATUS_FAILED, logs and returns before the pointer arithmetic.
- nfa_nfcee_act.cc:nfa_t4tnfcee_free_rx_buf and nfa_t4tnfcee_initialize_data: reset p_dataBuf_len to 0 so the size field stays consistent with the buffer lifecycle. <log/log.h> is included for android_errorWriteLog.
Completeness
- Covers the write site in nfa_t4tnfcee_store_rx_buf. Both allocation sites of p_dataBuf in the diff (nfa_t4tnfcee_handle_file_operations and nfa_t4tnfcee_store_cc_info) now record p_dataBuf_len, and both reset paths (free_rx_buf, initialize_data) clear it.
- In the overflow case, store_rx_buf sets status to NFA_STATUS_FAILED but does not return a value or change rw_state. The material does not show how the caller reacts to that status.
- rd_offset is still uint16_t. The check ensures the sum fits within p_dataBuf_len (uint32_t), but if p_dataBuf_len could exceed 0xFFFF, the stored rd_offset could still be truncated. The material does not show the type or range of lenDataToBeRead, so this cannot be judged.
- The allocating branch of nfa_t4tnfcee_store_cc_info still allocates and copies p_data->len bytes without a minimum-length check. Only the else branch gained the 0x07 + status-bytes check. The material does not show whether a short input on that branch is harmful.
- If allocation fails in store_cc_info, p_dataBuf_len is 0, so any later non-empty store into p_dataBuf would fail the bounds check. No explicit error notification is added on that path.
Behavior changes
- Received data that would exceed p_dataBuf_len is dropped. The drop is logged, sets nfa_t4tnfcee_cb.status = NFA_STATUS_FAILED and emits an android_errorWriteLog event (0x534e4554, "508390497").
- Responses shorter than T4T_RSP_STATUS_WORDS_SIZE now make nfa_t4tnfcee_check_sw set raw_frame.status = NFC_STATUS_FAILED instead of parsing status words.
- In the else branch of nfa_t4tnfcee_store_cc_info, data shorter than 0x07 + T4TNFCEE_SIZEOF_STATUS_BYTES is ignored with an error log.
- An allocation failure during a file read in nfa_t4tnfcee_handle_file_operations now reports NFC_STATUS_FAILED through nfa_t4tnfcee_notify_rx_evt, instead of starting the read with a NULL p_dataBuf.
Per major
- Android 14: not affected per bulletin; packages/modules/Nfc not present in android-security-14.0.0_r30 security tags
- Android 15: not affected per bulletin; packages/modules/Nfc not present in android-security-15.0.0_r18 security tags
- Android 16: same fix: commit 4f214cca6dff cherry-picked into android-security-16.0.0_r8 (Merged-In I6c39b1f2...), patched region fully present at android-security-16.0.0_r9
- Android 17: same fix: commit ebcfc2564b95 in android-security-17.0.0_r1 with an identical diff, patched region fully present at android-security-17.0.0_r2
Unknowns
- The type and maximum value of lenDataToBeRead, and how the caller of nfa_t4tnfcee_store_rx_buf handles nfa_t4tnfcee_cb.status = NFA_STATUS_FAILED, are not shown.
- The diff context does not include the exact uint16 underflow expression in nfa_t4tnfcee_store_cc_info that the new length check protects, nor the condition that selects between its two branches.
- The material does not show which party (remote tag or reader, NFCEE firmware, local app) can influence the chunk lengths.
- Which rw_state values route received data to store_cc_info and store_rx_buf is not shown.