public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths
@ 2026-04-24 18:01 Lekë Hapçiu
  2026-04-24 18:13 ` [PATCH net v4 3/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl Lekë Hapçiu
  2026-04-28 12:55 ` [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Simon Horman
  0 siblings, 2 replies; 6+ messages in thread
From: Lekë Hapçiu @ 2026-04-24 18:01 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, krzk, horms, linux-kernel, stable,
	Lekë Hapçiu

This series fixes five out-of-bounds / underflow bugs in the kernel NFC
stack.  All are reachable from a remote NFC peer that the local stack
has already associated with; in the LLCP cases the peer only needs to
send a malformed frame.

  1/5  nci: u8 underflow in nci_store_general_bytes_nfc_dep() lets the
       attacker-controlled atr_res_len skip the GT-offset subtraction
       and cause an OOB read/write against general_bytes[].
  2/5  llcp: parse_gb_tlv() / parse_connection_tlv() trust the TLV
       length byte without checking remaining buffer, and the tlv16
       accessors read past the end when length < 2.
  3/5  llcp: nfc_llcp_recv_snl() has the same TLV-length trust bug, and
       its SDRES handler uses an unbounded "%.16s" pr_debug() that
       walks past service_name_len.
  4/5  llcp: nfc_llcp_recv_dm() reads skb->data[3] without checking
       skb->len, giving a 1-byte heap OOB read.
  5/5  llcp: nfc_llcp_connect_sn() walks the TLV array with no length
       validation; a crafted CONNECT frame drops it into OOB reads /
       an unbounded service-name pointer.

The series applies on top of net/main.

Lekë Hapçiu (5):
  nfc: nci: fix u8 underflow in nci_store_general_bytes_nfc_dep
  nfc: llcp: fix TLV parsing in parse_gb_tlv and parse_connection_tlv
  nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl
  nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm
  nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn

 net/nfc/llcp_commands.c | 24 ++++++++++++++++++++++--
 net/nfc/llcp_core.c     | 35 ++++++++++++++++++++++++++++++++---
 net/nfc/nci/ntf.c       |  6 ++++++
 3 files changed, 60 insertions(+), 5 deletions(-)

-- 
2.51.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH net v4 3/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl
  2026-04-24 18:01 [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Lekë Hapçiu
@ 2026-04-24 18:13 ` Lekë Hapçiu
  2026-04-24 18:13   ` [PATCH net v4 4/5] nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm Lekë Hapçiu
  2026-04-24 18:13   ` [PATCH net v4 5/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn Lekë Hapçiu
  2026-04-28 12:55 ` [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Simon Horman
  1 sibling, 2 replies; 6+ messages in thread
From: Lekë Hapçiu @ 2026-04-24 18:13 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, krzk, horms, linux-kernel, stable,
	Lekë Hapçiu

nfc_llcp_recv_snl() has five problems when handling a hostile peer:

 1. nfc_llcp_dsap()/nfc_llcp_ssap() dereference skb->data[0..1] without
    verifying skb->len; a 0- or 1-byte frame leads to an OOB read.
    Additionally tlv_len = skb->len - LLCP_HEADER_SIZE wraps when
    skb->len < 2, causing the following loop to run far past the
    buffer.

 2. The per-iteration loop guard `offset < tlv_len` only proves one
    byte is available, but the body reads tlv[0] and tlv[1].

 3. The peer-supplied `length` field is used to advance `tlv` without
    being checked against the remaining array space.

 4. The SDREQ handler reads tid (tlv[2]) and the first byte of
    service_name (tlv[3]) whenever length >= 1, so length == 1 reads
    one byte past the TLV.  A length of 2 is required to cover both
    accesses and to yield a non-empty service_name.

    The pr_debug("Looking for %.16s\n", service_name) additionally
    treats the buffer as a NUL-terminated string and can read up to
    15 bytes past service_name_len.  Use a length-bounded format
    ("%.*s") so the print cannot escape the validated TLV region.

 5. The SDRES handler reads tlv[2] (tid) and tlv[3] (sap) without
    checking length.  A length of 0 or 1 therefore triggers the same
    1- or 2-byte OOB read as SDREQ; require length >= 2 here as well.

Fix: reject frames smaller than LLCP_HEADER_SIZE up front; add TLV
header and TLV value guards at the top of each iteration; require
length >= 2 for SDREQ and SDRES; bound the pr_debug of service_name
with %.*s.

Fixes: 19cfe5843e86 ("NFC: Initial SNL support")
Cc: stable@vger.kernel.org
Signed-off-by: Lekë Hapçiu <snowwlake@icloud.com>
---
 net/nfc/llcp_core.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index db5bc6a878dd..3284be517204 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,16 +1305,23 @@ 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 < 2)
+				break;
 			tid = tlv[2];
 			service_name = (char *) &tlv[3];
 			service_name_len = length - 1;
 
-			pr_debug("Looking for %.16s\n", service_name);
+			pr_debug("Looking for %.*s\n",
+				 (int)service_name_len, service_name);
 
 			if (service_name_len == strlen("urn:nfc:sn:sdp") &&
 			    !strncmp(service_name, "urn:nfc:sn:sdp",
@@ -1369,6 +1381,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


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net v4 4/5] nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm
  2026-04-24 18:13 ` [PATCH net v4 3/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl Lekë Hapçiu
@ 2026-04-24 18:13   ` Lekë Hapçiu
  2026-04-24 18:13   ` [PATCH net v4 5/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn Lekë Hapçiu
  1 sibling, 0 replies; 6+ messages in thread
From: Lekë Hapçiu @ 2026-04-24 18:13 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, krzk, horms, linux-kernel, stable,
	Lekë Hapçiu

nfc_llcp_recv_dm() reads skb->data[2] (the DM reason byte) without
first verifying that skb->len is at least LLCP_HEADER_SIZE + 1.  A DM
PDU carrying only the 2-byte LLCP header from a rogue peer therefore
triggers a 1-byte OOB read.

Add the minimum-length guard at function entry, matching the pattern
used by nfc_llcp_recv_snl() and nfc_llcp_recv_agf().

Fixes: 5c0560b7a5c6 ("NFC: Handle LLCP Disconnected Mode frames")
Cc: stable@vger.kernel.org
Signed-off-by: Lekë Hapçiu <snowwlake@icloud.com>
---
 net/nfc/llcp_core.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index 3284be517204..ca0abfd329e5 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -1239,6 +1239,11 @@ static void nfc_llcp_recv_dm(struct nfc_llcp_local *local,
 	struct sock *sk;
 	u8 dsap, ssap, reason;
 
+	if (skb->len < LLCP_HEADER_SIZE + 1) {
+		pr_err("Malformed DM PDU\n");
+		return;
+	}
+
 	dsap = nfc_llcp_dsap(skb);
 	ssap = nfc_llcp_ssap(skb);
 	reason = skb->data[2];
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net v4 5/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn
  2026-04-24 18:13 ` [PATCH net v4 3/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl Lekë Hapçiu
  2026-04-24 18:13   ` [PATCH net v4 4/5] nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm Lekë Hapçiu
@ 2026-04-24 18:13   ` Lekë Hapçiu
  1 sibling, 0 replies; 6+ messages in thread
From: Lekë Hapçiu @ 2026-04-24 18:13 UTC (permalink / raw)
  To: netdev
  Cc: davem, edumazet, kuba, pabeni, krzk, horms, linux-kernel, stable,
	Lekë Hapçiu

nfc_llcp_connect_sn() walks the TLV array of an LLCP CONNECT PDU
looking for the Service Name TLV, but shares the same class of bugs
as nfc_llcp_recv_snl() / nfc_llcp_parse_gb_tlv():

 1. tlv_array_len = skb->len - LLCP_HEADER_SIZE wraps when skb->len
    is 0 or 1.  The subsequent loop then runs far past the buffer.

 2. The per-iteration guard `offset < tlv_array_len` only proves one
    byte is available, but the body reads both tlv[0] (type) and
    tlv[1] (length).

 3. The peer-supplied `length` field is used to advance `tlv` without
    being checked against the remaining array space, so a crafted
    length walks `tlv` past the buffer.  On the following iteration
    tlv[0]/tlv[1] are read from adjacent memory.

 4. When an LLCP_TLV_SN is found, the function returns &tlv[2] with
    *sn_len = length but without verifying that `length` bytes at
    tlv[2..] are still inside the TLV array.  The caller in
    nfc_llcp_recv_connect() then uses this (pointer, length) pair as
    a service name, so it may read past the PDU.

Fix: reject frames smaller than LLCP_HEADER_SIZE up front; add TLV
header and TLV value guards at the top of each iteration.  The value
guard also ensures that the (&tlv[2], length) pair returned on
LLCP_TLV_SN lies fully inside the TLV array.

Also use LLCP_HEADER_SIZE instead of the magic literal `2` to match
the style of neighbouring LLCP receive paths.

Reported-by: Simon Horman <horms@kernel.org>
Closes: https://lore.kernel.org/netdev/20260417160438.GH31784@horms.kernel.org/
Fixes: d646960f7986 ("NFC: Initial LLCP support")
Cc: stable@vger.kernel.org
Signed-off-by: Lekë Hapçiu <snowwlake@icloud.com>
---
 net/nfc/llcp_core.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c
index ca0abfd329e5..df5567ca7fa8 100644
--- a/net/nfc/llcp_core.c
+++ b/net/nfc/llcp_core.c
@@ -849,12 +849,22 @@ 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;
+	size_t tlv_array_len, offset = 0;
+
+	if (skb->len < LLCP_HEADER_SIZE)
+		return NULL;
+
+	tlv = &skb->data[LLCP_HEADER_SIZE];
+	tlv_array_len = skb->len - LLCP_HEADER_SIZE;
 
 	while (offset < tlv_array_len) {
+		if (tlv_array_len - offset < 2)
+			break;
 		type = tlv[0];
 		length = tlv[1];
+		if (tlv_array_len - offset - 2 < length)
+			break;
 
 		pr_debug("type 0x%x length %d\n", type, length);
 
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths
  2026-04-24 18:01 [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Lekë Hapçiu
  2026-04-24 18:13 ` [PATCH net v4 3/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl Lekë Hapçiu
@ 2026-04-28 12:55 ` Simon Horman
  2026-04-28 13:46   ` Paolo Abeni
  1 sibling, 1 reply; 6+ messages in thread
From: Simon Horman @ 2026-04-28 12:55 UTC (permalink / raw)
  To: Lekë Hapçiu
  Cc: netdev, davem, edumazet, kuba, pabeni, krzk, linux-kernel, stable

On Fri, Apr 24, 2026 at 08:01:46PM +0200, Lekë Hapçiu wrote:
> This series fixes five out-of-bounds / underflow bugs in the kernel NFC
> stack.  All are reachable from a remote NFC peer that the local stack
> has already associated with; in the LLCP cases the peer only needs to
> send a malformed frame.
> 
>   1/5  nci: u8 underflow in nci_store_general_bytes_nfc_dep() lets the
>        attacker-controlled atr_res_len skip the GT-offset subtraction
>        and cause an OOB read/write against general_bytes[].
>   2/5  llcp: parse_gb_tlv() / parse_connection_tlv() trust the TLV
>        length byte without checking remaining buffer, and the tlv16
>        accessors read past the end when length < 2.
>   3/5  llcp: nfc_llcp_recv_snl() has the same TLV-length trust bug, and
>        its SDRES handler uses an unbounded "%.16s" pr_debug() that
>        walks past service_name_len.
>   4/5  llcp: nfc_llcp_recv_dm() reads skb->data[3] without checking
>        skb->len, giving a 1-byte heap OOB read.
>   5/5  llcp: nfc_llcp_connect_sn() walks the TLV array with no length
>        validation; a crafted CONNECT frame drops it into OOB reads /
>        an unbounded service-name pointer.
> 
> The series applies on top of net/main.
> 
> Lekë Hapçiu (5):
>   nfc: nci: fix u8 underflow in nci_store_general_bytes_nfc_dep
>   nfc: llcp: fix TLV parsing in parse_gb_tlv and parse_connection_tlv
>   nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl
>   nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm
>   nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn

Hi,

My only feedback on v4 of this patchset is that somehow the
threading is broken: each of patch 1/5 - 5/5 should be a reply
to the cover letter - 0/5 - but that does not seem to be the case.
And some tooling, notably Sashiko, seems to rely on the
entire patchset being contained in a single email thread.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths
  2026-04-28 12:55 ` [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Simon Horman
@ 2026-04-28 13:46   ` Paolo Abeni
  0 siblings, 0 replies; 6+ messages in thread
From: Paolo Abeni @ 2026-04-28 13:46 UTC (permalink / raw)
  To: Simon Horman, Lekë Hapçiu
  Cc: netdev, davem, edumazet, kuba, krzk, linux-kernel, stable,
	David Heidelberg



On 4/28/26 2:55 PM, Simon Horman wrote:
> On Fri, Apr 24, 2026 at 08:01:46PM +0200, Lekë Hapçiu wrote:
>> This series fixes five out-of-bounds / underflow bugs in the kernel NFC
>> stack.  All are reachable from a remote NFC peer that the local stack
>> has already associated with; in the LLCP cases the peer only needs to
>> send a malformed frame.
>>
>>   1/5  nci: u8 underflow in nci_store_general_bytes_nfc_dep() lets the
>>        attacker-controlled atr_res_len skip the GT-offset subtraction
>>        and cause an OOB read/write against general_bytes[].
>>   2/5  llcp: parse_gb_tlv() / parse_connection_tlv() trust the TLV
>>        length byte without checking remaining buffer, and the tlv16
>>        accessors read past the end when length < 2.
>>   3/5  llcp: nfc_llcp_recv_snl() has the same TLV-length trust bug, and
>>        its SDRES handler uses an unbounded "%.16s" pr_debug() that
>>        walks past service_name_len.
>>   4/5  llcp: nfc_llcp_recv_dm() reads skb->data[3] without checking
>>        skb->len, giving a 1-byte heap OOB read.
>>   5/5  llcp: nfc_llcp_connect_sn() walks the TLV array with no length
>>        validation; a crafted CONNECT frame drops it into OOB reads /
>>        an unbounded service-name pointer.
>>
>> The series applies on top of net/main.
>>
>> Lekë Hapçiu (5):
>>   nfc: nci: fix u8 underflow in nci_store_general_bytes_nfc_dep
>>   nfc: llcp: fix TLV parsing in parse_gb_tlv and parse_connection_tlv
>>   nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl
>>   nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm
>>   nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn
> 
> Hi,
> 
> My only feedback on v4 of this patchset is that somehow the
> threading is broken: each of patch 1/5 - 5/5 should be a reply
> to the cover letter - 0/5 - but that does not seem to be the case.
> And some tooling, notably Sashiko, seems to rely on the
> entire patchset being contained in a single email thread.

Given the above, I suggest re-posting.

Also note that we are moving NFC to a specific subtree, see:

https://lore.kernel.org/netdev/938496c6-84c1-4d53-bb56-73bbd7b2bdd7@ixit.cz/

please wait a bit for resubmission, possibly David will be already ready
to catch them.

Thanks,

Paolo


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-04-28 13:46 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-24 18:01 [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Lekë Hapçiu
2026-04-24 18:13 ` [PATCH net v4 3/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_recv_snl Lekë Hapçiu
2026-04-24 18:13   ` [PATCH net v4 4/5] nfc: llcp: fix OOB read of DM reason byte in nfc_llcp_recv_dm Lekë Hapçiu
2026-04-24 18:13   ` [PATCH net v4 5/5] nfc: llcp: fix TLV parsing OOB in nfc_llcp_connect_sn Lekë Hapçiu
2026-04-28 12:55 ` [PATCH net v4 0/5] nfc: fix multiple OOB reads in NCI and LLCP parsing paths Simon Horman
2026-04-28 13:46   ` Paolo Abeni

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox