diff options
| author | Doruk Tan Ozturk <doruk@0sec.ai> | 2026-07-09 16:12:29 +0300 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-08-27 15:35:23 +0300 |
| commit | 0cfbdb0e13ab5b0765d77f96af67eb879cbc9736 (patch) | |
| tree | 5a4eb370b1151c88d22629f6aac8c8ac3072c93a | |
| parent | 953963b9ac5eecbb316617d337bfaa3d731e3c5e (diff) | |
| download | linux-0cfbdb0e13ab5b0765d77f96af67eb879cbc9736.tar.xz | |
nfc: llcp: bound the connect_sn TLV walk to the skb
commit 55c68ac93e7dacc0f5f608b9c39dd4ff48cf28e8 upstream.
Commit 27256cdb290e ("nfc: llcp: bound SNL TLV parsing to the skb and
add length checks") fixed the unbounded TLV walk in nfc_llcp_recv_snl(),
and commit d8bd2dedbde5 ("nfc: llcp: fix OOB read and u8 offset wrap in
TLV parsers") subsequently bounded nfc_llcp_parse_gb_tlv() and
nfc_llcp_parse_connection_tlv(). One sibling parser sharing the same
pattern remains unbounded: nfc_llcp_connect_sn().
nfc_llcp_connect_sn() walks a TLV list, reading a two-byte header
(type, length) followed by length bytes of value, without checking that
the two header bytes or the declared length stay within the buffer. It
returns a pointer to a service name of up to 255 bytes that may point
past the end of the skb; it is subsequently consumed by memcmp() in
nfc_llcp_sock_from_sn(). In addition tlv_array_len was computed as
"skb->len - LLCP_HEADER_SIZE" in size_t, so a CONNECT/CC frame shorter
than the LLCP header underflows to a huge length and the walk runs far
past the buffer.
nfc_llcp_connect_sn() is reachable from nfc_llcp_recv_connect() and
nfc_llcp_recv_cc(), i.e. from received CONNECT and CC PDUs. A nearby
NFC device can reach this without authentication; LLCP link activation
happens automatically after NFC-DEP, and the nfc_llcp_rx_skb()
dispatcher applies no minimum-length guard.
Walk the TLV list by pointer, bounded by skb_tail_pointer(skb), and
validate each declared length before use, matching the approach already
used for nfc_llcp_recv_snl(). Starting the walk at
&skb->data[LLCP_HEADER_SIZE] against the tail pointer also removes the
size_t underflow for short frames.
Found by 0sec automated security-research tooling (https://0sec.ai).
Fixes: d646960f7986 ("NFC: Initial LLCP support")
Cc: stable@vger.kernel.org
Assisted-by: 0sec:claude-opus-4-8
Signed-off-by: Doruk Tan Ozturk <doruk@0sec.ai>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20260709131229.44477-1-doruk@0sec.ai
Signed-off-by: David Heidelberg <david@ixit.cz>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
| -rw-r--r-- | net/nfc/llcp_core.c | 10 |
1 files changed, 6 insertions, 4 deletions
diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index dc65c719f35f..5e1881199238 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -849,13 +849,16 @@ static struct nfc_llcp_sock *nfc_llcp_sock_get_sn(struct nfc_llcp_local *local, static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) { u8 type, length; - const u8 *tlv = &skb->data[2]; - size_t tlv_array_len = skb->len - LLCP_HEADER_SIZE, offset = 0; + const u8 *tlv = &skb->data[LLCP_HEADER_SIZE]; + const u8 *tlv_end = skb_tail_pointer(skb); - while (offset < tlv_array_len) { + while (tlv + 2 < tlv_end) { type = tlv[0]; length = tlv[1]; + if (tlv + 2 + length > tlv_end) + break; + pr_debug("type 0x%x length %d\n", type, length); if (type == LLCP_TLV_SN) { @@ -863,7 +866,6 @@ static const u8 *nfc_llcp_connect_sn(const struct sk_buff *skb, size_t *sn_len) return &tlv[2]; } - offset += length + 2; tlv += length + 2; } |
