CVE-2026-55280 AI-generated analysis
platform/packages/modules/Nfc, libnfc-nci (NCI interface layer nfc_ncif.cc and NFA device manager nfa_dm_act.cc)
Zero-initializes the whole evt_data.activate structure in nfc_ncif_proc_activate() and clamps the NFCID1 and Kovio UID length fields in nfa_dm_notify_activation_status(). The goal is that a short or malformed RF_INTF_ACTIVATED_NTF from the NFCC can no longer feed uninitialized or oversized length values into a memcpy in the NFC process.
Root cause
In libnfc-nci/src/nfc/nfc/nfc_ncif.cc, nfc_ncif_proc_activate() declares a stack tNFC_DISCOVER evt_data but cleared only the intf_param sub-structure (memset(p_intf, 0, sizeof(tNFC_INTF_PARAMS))). The rf_tech_param union inside evt_data.activate stayed uninitialized. If the RF_INTF_ACTIVATED_NTF is too short (plen < 7, or plen < *p + 4), the function jumps to invalid_packet before nfc_ncif_decode_rf_params() fills rf_tech_param. Per the unit test ProcActivateInvalidPacketLength (per index search), the discovery callback is still called in that case with NFC_ACTIVATE_DEVT and NCI_STATUS_FAILED. Per the commit message, this uninitialized data is later used as a memcpy length and causes an out-of-bounds write in the NFC process. The fix also adds checks in nfa_dm_notify_activation_status() (libnfc-nci/src/nfa/dm/nfa_dm_act.cc), which reads the activation notification copied from nfa_dm_cb.p_activate_ntf (per index search). Before the fix, that function used the length fields nfcid1_len (POLL_A) and pk.uid_len (POLL_KOVIO) without bounds checks.
How the fix works
- libnfc-nci/src/nfc/nfc/nfc_ncif.cc:nfc_ncif_proc_activate(): memset(p_intf, 0, sizeof(tNFC_INTF_PARAMS)) is replaced by memset(&evt_data.activate, 0, sizeof(evt_data.activate)), which runs before any length validation. On early-exit paths, every activate field that has not been parsed yet, including the rf_tech_param union, is now zero instead of indeterminate stack data.
- libnfc-nci/src/nfa/dm/nfa_dm_act.cc:nfa_dm_notify_activation_status(): in the NFC_DISCOVERY_TYPE_POLL_A else-branch, after nfcid_len = p_tech_params->param.pa.nfcid1_len, a new check clamps nfcid_len to NCI_NFCID1_MAX_LEN. It writes the clamped value back into p_tech_params->param.pa.nfcid1_len and logs android_errorWriteLog(0x534e4554, "522373576").
- libnfc-nci/src/nfa/dm/nfa_dm_act.cc:nfa_dm_notify_activation_status(): in the NFC_DISCOVERY_TYPE_POLL_KOVIO branch, the same pattern clamps nfcid_len = p_tech_params->param.pk.uid_len to NFC_KOVIO_MAX_LEN, writes it back into pk.uid_len, and logs the same SafetyNet event.
- libnfc-nci/src/nfa/dm/nfa_dm_act.cc: adds #include <log/log.h> for android_errorWriteLog.
Completeness
- The memset runs at function entry, so on every invalid_packet exit, the activate fields not yet parsed are zero. Fields already parsed keep their values: on the plen < *p + 4 path, rf_disc_id, intf type, protocol and mode are already set.
- The clamp in nfa_dm_notify_activation_status() does not depend on the memset. It bounds nfcid1_len and uid_len whatever the source of the value is.
- Only the POLL_A (nfcid1) and POLL_KOVIO (uid) length fields are clamped. The POLL_B and ISO15693 (pi93.uid) branches in the same function are not modified. The material does not show how their nfcid_len values are derived.
- The first (if) branch of the POLL_A case, which passes p_nfcid and nfcid_len to another call, is not modified. The material does not show where its length comes from.
- The fix adds no validation inside nfc_ncif_decode_rf_params(). The material does not show whether that function bounds the nfcid1_len and uid_len values it parses.
Behavior changes
- On a short RF_INTF_ACTIVATED_NTF, the NFC_ACTIVATE_DEVT callback now receives zeros instead of indeterminate values in every activate field not yet parsed. On the plen < 7 path that includes rf_disc_id, protocol and rf_tech_param. The status is still NCI_STATUS_FAILED.
- An nfcid1_len above NCI_NFCID1_MAX_LEN, or a Kovio uid_len above NFC_KOVIO_MAX_LEN, is truncated to the maximum. The truncated length is written back into the tech parameters, and a SafetyNet log entry (tag 0x534e4554, id 522373576) is emitted.
- The diff adds no new error return. The clamped length replaces the original value.
Per major
- Android 14: Not affected per the bulletin. platform/packages/modules/Nfc is not present at android-security-14.0.0_r30.
- Android 15: Not affected per the bulletin. platform/packages/modules/Nfc is not present at android-security-15.0.0_r18.
- Android 16: Same fix: commit d6cb6aefb127 in android-security-16.0.0_r8 (cherry-pick of f974b0fee85f, Change-Id Iedd07da2dd238a27550c8d8bdfa9ab8d4cd1050f). At android-security-16.0.0_r9, all 8/8 lines of the patched region are present.
- Android 17: Same fix: commit 897466771c24 in android-security-17.0.0_r1, with the same changes and only different line offsets (nfa_dm_act.cc hunks at 1712/1725 instead of 1707/1720, nfc_ncif.cc hunk at 964 instead of 993). At android-security-17.0.0_r2, all 8/8 lines are present.
Unknowns
- The exact memcpy that performs the out-of-bounds write and its destination are not shown in the material. They are known only from the commit message.
- Whether a failed activation (status NCI_STATUS_FAILED) still leads to the notification being stored in nfa_dm_cb.p_activate_ntf and consumed by nfa_dm_notify_activation_status() is not shown.
- Whether nfc_ncif_decode_rf_params() bounds nfcid1_len and uid_len when it parses a well-formed packet is not shown.
- How the POLL_B and ISO15693 branches derive nfcid_len is not visible in the provided context.