[PATCH] nfc: llcp: fix u8 offset truncation in LLCP TLV parsers

Lekë Hapçiu
2026-04-05T10:59:53Z

From: Lekë Hapçiu <framemain@outlook.com>

nfc_llcp_parse_gb_tlv() and nfc_llcp_parse_connection_tlv() declare
'offset' as u8, but compare it against a u16 tlv_array_len:

u8 type, length, offset = 0;
while (offset < tlv_array_len) { /* tlv_array_len is u16 */
...
offset += length + 2; /* wraps at 256 */
tlv += length + 2;
}

When tlv_array_len > 255 -- possible in nfc_llcp_parse_connection_tlv()
when the peer has negotiated MIUX = 0x7FF (MIU = 2175 bytes), so that
a CONNECT PDU can carry a TLV array of up to 2173 bytes -- the u8
offset wraps back below tlv_array_len after every 128 zero-length TLV
entries and the loop never terminates. The 'tlv' pointer meanwhile
advances without bound into adjacent kernel heap, causing:

* an OOB read of kernel heap content past the skb end;
* a kernel page fault / oops once 'tlv' leaves mapped memory.

This is reachable from any NFC P2P peer device within ~4 cm without
requiring compromised NFCC firmware.

Fix: promote 'offset' from u8 to u16 in both parsers, matching the
type of their tlv_array_len parameter.

nfc_llcp_parse_gb_tlv() takes GB bytes from the ATR_RES (max 44 bytes),
so the wrap cannot occur in practice there. Change it anyway for
correctness and to prevent copy-paste reintroduction.

Cc: stable@vger.kernel.org
Signed-off-by: Lekë Hapçiu <framemain@outlook.com>
---
net/nfc/llcp_commands.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c
index 291f26fac..6937dcb3b 100644
--- a/net/nfc/llcp_commands.c
+++ b/net/nfc/llcp_commands.c
@@ -193,7 +193,8 @@ int nfc_llcp_parse_gb_tlv(struct nfc_llcp_local *local,
const u8 *tlv_array, u16 tlv_array_len)
{
const u8 *tlv = tlv_array;
- u8 type, length, offset = 0;
+ u8 type, length;
+ u16 offset = 0;

pr_debug("TLV array length %d\n", tlv_array_len);

@@ -243,7 +244,8 @@ int nfc_llcp_parse_connection_tlv(struct nfc_llcp_sock *sock,
const u8 *tlv_array, u16 tlv_array_len)
{
const u8 *tlv = tlv_array;
- u8 type, length, offset = 0;
+ u8 type, length;
+ u16 offset = 0;

pr_debug("TLV array length %d\n", tlv_array_len);

--
2.51.0