CVE-2026-55285 AI-generated analysis
platform/hardware/nxp/secure_element, NXP eSE HAL (pn8x and snxxx HIDL/AIDL SecureElement and VirtualISO services)
Adds a range check on the channel number the Secure Element returns in the MANAGE CHANNEL OPEN response, before that number is used as an index into mOpenedChannels. The check is added to the ten openLogicalChannel implementations the commit touches in the NXP secure_element HAL (pn8x 1.0/1.1, plus snxxx 1.0/1.1/1.2/aidl SecureElement and VirtualISO). The fix also makes pn8x closeChannel return immediately on an invalid channel number.
Root cause
In each openLogicalChannel, phNxpEse_Transceive sends the MANAGE CHANNEL OPEN command. When it succeeded with status word 0x90 0x00, the first response byte rspApdu.p_data[0] was copied into resApduBuff.channelNumber and used directly as the index in `mOpenedChannels[resApduBuff.channelNumber] = true`. Nothing compared that byte against the array capacity. The capacity is MAX_LOGICAL_CHANNELS for pn8x and mMaxChannelCount for snxxx; in snxxx 1.2 and aidl, mOpenedChannels is resized to mMaxChannelCount. A Secure Element that returned a channel byte at or above that bound caused an out-of-bounds boolean write. Separately, in pn8x closeChannel the invalid-channel branch only set sestatus = FAILED and did not return, so execution continued past the if/else chain. The commit message calls this a missing early return.
How the fix works
- pn8x/1.0/SecureElement.cpp and pn8x/1.1/SecureElement.cpp :: SecureElement::openLogicalChannel: the 0x9000 success branch is wrapped in `if (rspApdu.p_data[0] < MAX_LOGICAL_CHANNELS && rspApdu.p_data[0] > 0)`. Only inside that condition are channelNumber, mOpenedchannelCount++ and mOpenedChannels[...] = true applied, with SUCCESS. Otherwise sestatus = SecureElementStatus::CHANNEL_NOT_AVAILABLE, and the pn8x else branch does not modify resApduBuff.channelNumber.
- pn8x/1.0/SecureElement.cpp and pn8x/1.1/SecureElement.cpp :: SecureElement::closeChannel: in the `(channelNumber >= MAX_LOGICAL_CHANNELS) || (mOpenedChannels[channelNumber] == false)` branch, `sestatus = SecureElementStatus::FAILED;` is replaced by `return SecureElementStatus::FAILED;`, so the rest of the function does not run for an invalid channel.
- snxxx/1.0, 1.1 and 1.2 SecureElement.cpp and VirtualISO.cpp :: openLogicalChannel: the same guard, using `rspApdu.p_data[0] < mMaxChannelCount && rspApdu.p_data[0] > 0`. The else branch sets resApduBuff.channelNumber = 0xff and sestatus = SecureElementStatus::CHANNEL_NOT_AVAILABLE.
- snxxx/aidl/SecureElement.cpp and snxxx/aidl/VirtualISO.cpp :: openLogicalChannel: the same guard against mMaxChannelCount. The else branch sets resApduBuff.channelNumber = -1 and sestatus = ISecureElement::CHANNEL_NOT_AVAILABLE.
- All variants reuse the existing post-processing. A non-SUCCESS sestatus goes down the existing failure path: seHalDeInit runs when mOpenedchannelCount == 0 (pn8x also requires isSeInitialized()), then the callback fires and the function returns.
Completeness
- Covers the ten openLogicalChannel implementations the commit touches: pn8x 1.0/1.1, and snxxx 1.0/1.1/1.2/aidl for both SecureElement and VirtualISO. Each uses the bound for that variant: the MAX_LOGICAL_CHANNELS constant for pn8x, mMaxChannelCount for snxxx. mMaxChannelCount matches the mOpenedChannels.resize(mMaxChannelCount, false) seen in snxxx 1.2 and aidl.
- The `> 0` condition also rejects a returned channel number of 0 (the basic channel), so the SE cannot mark the basic-channel slot as opened through this path.
- The closeChannel early return is added only in the pn8x variants. This commit does not touch snxxx closeChannel, and the material does not show whether those implementations needed the change.
- The fix validates only rspApdu.p_data[0]. After a successful transceive, the surrounding code still indexes rspApdu.p_data[rspApdu.len - 2] and [rspApdu.len - 1] with no visible check that rspApdu.len >= 2. The material does not show whether phNxpEse_Transceive guarantees a length of at least 2.
- openBasicChannel and other HAL entry points are not part of this commit, and the material does not show their code.
Behavior changes
- A MANAGE CHANNEL OPEN response with SW 0x9000 whose first byte is 0, or at or above the channel bound, now returns CHANNEL_NOT_AVAILABLE instead of SUCCESS. The channel count is not incremented and no channel is marked opened.
- In that case the snxxx HIDL variants report channelNumber 0xff, the AIDL variants report -1, and the pn8x variants leave resApduBuff.channelNumber at its prior value.
- Because sestatus is not SUCCESS, the existing failure path runs: if mOpenedchannelCount == 0, the HAL calls seHalDeInit before returning.
- pn8x closeChannel now returns FAILED immediately for an out-of-range or not-opened channel instead of continuing through the rest of the function.
Per major
- Android 14: Not affected (Android 14 is not listed by the bulletin). The patched region is present (8/8 lines) at android-security-14.0.0_r29. The material does not identify the commit that brought it there.
- Android 15: Not affected (Android 15 is not listed by the bulletin). The patched region is present (8/8 lines) at android-security-15.0.0_r17. The material does not identify the commit that brought it there.
- Android 16: Same fix: commit 466c2e78559e in android-security-16.0.0_r8, a cherry-pick of 8ca0d691fa4b with Change-Id I54a5cb16202b49d0929e6306626ef11e2aa329b8. Patched region present (8/8 lines).
- Android 17: Same fix: commit addc064ca4ec in android-security-17.0.0_r1, with identical hunks. The snxxx/aidl pre-image blob hashes differ from Android 16 (ca80d6b/73563de vs 4ad9388/5808238), and the snxxx/aidl/SecureElement.cpp hunk starts at line 414 instead of 418. Patched region present (8/8 lines).
Unknowns
- What code follows the invalid-channel branch in pn8x closeChannel. The diff shows only the changed line, so the exact effect of the previously missing return is not visible.
- How mOpenedChannels is declared and sized in pn8x and in snxxx 1.0/1.1. The resize to mMaxChannelCount is shown only for snxxx 1.2 and aidl.
- Whether phNxpEse_Transceive guarantees rspApdu.len >= 2 on success, which the unchanged status-word indexing relies on.
- Whether other openLogicalChannel implementations exist in the repository beyond the ten files this commit changes.
- Why the bulletin lists only Android 16 and later while the patched region is present in the 14 and 15 security tags.