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 --- 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