From: "Lekë Hapçiu" <snowwlake@icloud.com>
To: netdev@vger.kernel.org
Cc: linux-nfc@lists.01.org, stable@vger.kernel.org,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, "Lekë Hapçiu" <snowwlake@icloud.com>
Subject: [PATCH net 2/3] nfc: llcp: fix TLV parsing OOB and length underflow in nfc_llcp_recv_snl
Date: Fri, 10 Apr 2026 01:35:15 +0200 [thread overview]
Message-ID: <20260409233517.1891497-3-snowwlake@icloud.com> (raw)
In-Reply-To: <20260409233517.1891497-1-snowwlake@icloud.com>
nfc_llcp_recv_snl() contains four distinct vulnerabilities.
Issue 1 - missing minimum-length guard on skb:
nfc_llcp_dsap() and nfc_llcp_ssap() access pdu->data[0] and pdu->data[1]
unconditionally. The subsequent computation:
tlv_len = skb->len - LLCP_HEADER_SIZE; /* LLCP_HEADER_SIZE = 2 */
truncates to u16. If skb->len < 2, the unsigned subtraction wraps at
unsigned int width and the truncation to u16 yields up to 65534, causing
the while loop to iterate far beyond the skb data. No guard exists at
the dispatch path to prevent this.
Fix: add `if (skb->len < LLCP_HEADER_SIZE) return;` before any skb->data
access, matching the pattern already used in nfc_llcp_recv_agf().
Issue 2 - missing per-iteration TLV header guard:
The loop reads tlv[0] and tlv[1] with no prior check that two bytes
remain. When one byte remains, tlv[1] is one byte past the array end.
Fix: `if (tlv_len - offset < 2) break;`
Issue 3 - peer-controlled `length` field advances tlv past skb end:
`length` (tlv[1]) is advanced unconditionally into `offset` and `tlv`
without verifying that `length` bytes of TLV value exist. A malicious
peer sets `length` large enough that `offset` remains below `tlv_len` on
the next iteration while `tlv` points into adjacent kernel heap.
Fix: `if (tlv_len - offset - 2 < length) break;`
Issue 4 - per-type minimum-length hazards:
LLCP_TLV_SDREQ: `service_name_len = length - 1` is u8 arithmetic. When
length == 0 this wraps to 255, causing a 255-byte kernel memory scan via
strncmp. tlv[2] (tid) is also accessed unconditionally.
Fix: require length >= 1 before the tid/service_name access.
LLCP_TLV_SDRES: tlv[2] and tlv[3] are accessed without verifying
length >= 2. Unlike the GB/connection parsers, SDREQ/SDRES are not
processed via llcp_tlv8/16, so the llcp_tlv_length[] table provides no
protection here.
Fix: require length >= 2 before the tlv[2]/tlv[3] accesses.
In both cases a `break` from the inner switch falls through to the
unconditional `offset += length + 2; tlv += length + 2` at the loop
tail, correctly advancing past the malformed TLV. The outer two guards
break from the while loop entirely.
Reachability: SNL PDUs are processed during LLCP service discovery, before
any connection is established, from any NFC peer within ~4 cm with no
authentication or pairing.
Fixes: 7a06f0ee2823 ("NFC: llcp: Service Name Lookup implementation")
Cc: stable@vger.kernel.org
Signed-off-by: Lekë Hapçiu <snowwlake@icloud.com>
---
net/nfc/llcp_core.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index db5bc6a87..16acf7c2b 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -1284,6 +1284,11 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local,
size_t sdres_tlvs_len;
HLIST_HEAD(nl_sdres_list);
+ if (skb->len < LLCP_HEADER_SIZE) {
+ pr_err("Malformed SNL PDU\n");
+ return;
+ }
+
dsap = nfc_llcp_dsap(skb);
ssap = nfc_llcp_ssap(skb);
@@ -1300,11 +1305,17 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local,
sdres_tlvs_len = 0;
while (offset < tlv_len) {
+ if (tlv_len - offset < 2)
+ break;
type = tlv[0];
length = tlv[1];
+ if (tlv_len - offset - 2 < length)
+ break;
switch (type) {
case LLCP_TLV_SDREQ:
+ if (length < 1)
+ break;
tid = tlv[2];
service_name = (char *) &tlv[3];
service_name_len = length - 1;
@@ -1369,6 +1380,8 @@ static void nfc_llcp_recv_snl(struct nfc_llcp_local *local,
break;
case LLCP_TLV_SDRES:
+ if (length < 2)
+ break;
mutex_lock(&local->sdreq_lock);
pr_debug("LLCP_TLV_SDRES: searching tid %d\n", tlv[2]);
--
2.51.0
next prev parent reply other threads:[~2026-04-09 23:36 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-09 23:35 [PATCH net 0/3] nfc: llcp: fix OOB reads in TLV parsers and PDU handlers Lekë Hapçiu
2026-04-09 23:35 ` [PATCH net 1/3] nfc: llcp: add TLV length bounds checks in parse_gb_tlv and parse_connection_tlv Lekë Hapçiu
2026-04-09 23:35 ` Lekë Hapçiu [this message]
2026-04-09 23:35 ` [PATCH net 3/3] nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm() Lekë Hapçiu
2026-04-14 8:11 ` [PATCH net 0/3] nfc: llcp: fix OOB reads in TLV parsers and PDU handlers Paolo Abeni
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260409233517.1891497-3-snowwlake@icloud.com \
--to=snowwlake@icloud.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=linux-nfc@lists.01.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox