net: nfc: fix use-after-free in nfc_get_local_general_bytes
Subsystems (3)
v3 cover net: nfc: fix use-after-free in nfc_get_local_general_bytes
From: Luxiao Xu <[email protected]>
This series addresses a use-after-free (UAF) regression introduced by commit 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local").
In commit 6709d4b7bc2e, nfc_llcp_local_put(local) was added right before returning local->gb. However, if the reference count drops to zero, the object is immediately freed, causing callers accessing the returned pointer to trigger a use-after-free.
Using dynamic allocation (e.g. kmemdup) to copy the buffer was evaluated in v1, but it led to memory leaks because all callers consistently treat the returned buffer as borrowed memory and do not free it.
To solve this properly without lifetime issues or leaks, this patch refactors nfc_llcp_general_bytes() and nfc_get_local_general_bytes() to take a caller-provided destination buffer and maximum length. The bytes are safely copied before dropping the reference via nfc_llcp_local_put(). All callers in core and drivers (microread, pn533, pn544, st21nfca, digital_dep, and nci) are updated accordingly.
---
v3:
- Include <net/nfc/nfc.h> in pn533.h to fix build error in uart.c caused by undefined NFC_MAX_GT_LEN.
- Restore nci_request() in nci_set_local_general_bytes() to preserve ndev->req_lock synchronization (avoid unlocked __nci_request() via nci_set_config()).
v2 Link: https://lore.kernel.org/all/[email protected]/
v2:
- Use caller-provided output buffers to fix UAF instead of dynamic allocation (kmemdup), avoiding memory leaks.
Luxiao Xu (1):
net: nfc: fix use-after-free in nfc_get_local_general_bytes
drivers/nfc/microread/microread.c | 6 +++--- drivers/nfc/pn533/pn533.c | 14 ++++++++------ drivers/nfc/pn533/pn533.h | 4 +++- drivers/nfc/pn544/pn544.c | 7 +++---- drivers/nfc/st21nfca/core.c | 8 ++++---- include/net/nfc/hci.h | 2 +- include/net/nfc/nfc.h | 3 ++- net/nfc/core.c | 15 +++++++-------- net/nfc/digital_dep.c | 8 ++++---- net/nfc/llcp_core.c | 17 +++++++++++++---- net/nfc/nci/core.c | 10 +++++----- net/nfc/nfc.h | 3 ++- 12 files changed, 55 insertions(+), 42 deletions(-)
Signature · 1 line
2.43.0
PATCH v3 1/1 net: nfc: fix use-after-free in nfc_get_local_general_bytes
From: Luxiao Xu <[email protected]>
Commit 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local") attempted to fix a use-after-free (UAF) issue by invoking nfc_llcp_local_put(local) after accessing local->gb. However, if the reference count drops to zero, local is freed immediately, leading to a use-after-free when callers access the returned pointer. Alternative approaches using dynamic allocation (e.g. kmemdup) introduced memory leaks because callers consistently treat the returned pointer as borrowed memory.
Fix this properly by refactoring nfc_llcp_general_bytes() and nfc_get_local_general_bytes() to accept a caller-provided output buffer (out_gb) and its maximum length (gb_max_len). The general bytes are safely copied into out_gb before calling nfc_llcp_local_put(local), ensuring safe lifetime management without ownership transfer complications.
Update all callers across drivers (microread, pn533, pn544, st21nfca, digital_dep, and nci) to provide their own destination buffers and pass them to nfc_get_local_general_bytes().
---
v3:
- Include <net/nfc/nfc.h> in pn533.h to fix build error in uart.c caused by undefined NFC_MAX_GT_LEN.
- Restore nci_request() in nci_set_local_general_bytes() to preserve ndev->req_lock synchronization (avoid unlocked __nci_request() via nci_set_config()).
v2:
- Use caller-provided output buffers to fix UAF instead of dynamic allocation (kmemdup), avoiding memory leaks.
--- drivers/nfc/microread/microread.c | 6 +++--- drivers/nfc/pn533/pn533.c | 14 ++++++++------ drivers/nfc/pn533/pn533.h | 4 +++- drivers/nfc/pn544/pn544.c | 7 +++---- drivers/nfc/st21nfca/core.c | 8 ++++---- include/net/nfc/hci.h | 2 +- include/net/nfc/nfc.h | 3 ++- net/nfc/core.c | 15 +++++++-------- net/nfc/digital_dep.c | 8 ++++---- net/nfc/llcp_core.c | 17 +++++++++++++---- net/nfc/nci/core.c | 10 +++++----- net/nfc/nfc.h | 3 ++- 12 files changed, 55 insertions(+), 42 deletions(-)
drivers/nfc/microread/microread.c +3 -3
@@ -251,9 +251,9 @@ static int microread_start_poll(struct nfc_hci_dev *hdev, param[1 ] |= (1 <<1 );if ((im_protocols | tm_protocols) &NFC_PROTO_NFC_DEP_MASK ) { - hdev->gb =nfc_get_local_general_bytes (hdev->ndev, - &hdev->gb_len); -if (hdev->gb ==NULL || hdev->gb_len ==0 ) { +nfc_get_local_general_bytes (hdev->ndev, hdev->gb, +sizeof (hdev->gb), &hdev->gb_len); +if (hdev->gb_len ==0 ) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK ; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK ; }
drivers/nfc/pn533/pn533.c +8 -6
@@ -1346,10 +1346,11 @@ static int pn533_poll_dep(struct nfc_dev *nfc_dev)u8 *next, nfcid3[NFC_NFCID3_MAXSIZE ];u8 passive_data[PASSIVE_DATA_LEN ] = {0x00 ,0xff ,0xff ,0x00 ,0x3 }; -if (!dev->gb) { - dev->gb =nfc_get_local_general_bytes (nfc_dev, &dev->gb_len); - -if (!dev->gb || !dev->gb_len) { +if (!dev->gb_len) { +nfc_get_local_general_bytes (nfc_dev, dev->gb, +sizeof (dev->gb), + &dev->gb_len); +if (!dev->gb_len) { dev->poll_dep =0 ;queue_work (dev->wq, &dev->rf_work); }
@@ -1647,8 +1648,9 @@ static int pn533_start_poll(struct nfc_dev *nfc_dev, }if (tm_protocols) { - dev->gb =nfc_get_local_general_bytes (nfc_dev, &dev->gb_len); -if (dev->gb ==NULL ) +nfc_get_local_general_bytes (nfc_dev, dev->gb, +sizeof (dev->gb), &dev->gb_len); +if (dev->gb_len ==0 ) tm_protocols =0 ; }
drivers/nfc/pn533/pn533.h +3 -1
@@ -6,6 +6,8 @@* Copyright (C) 2012-2013 Tieto Poland */ +#include <net/nfc/nfc.h> +#define PN533_DEVICE_STD 0x1#define PN533_DEVICE_PASORI 0x2#define PN533_DEVICE_ACR122U 0x3
@@ -166,7 +168,7 @@ struct pn533 {struct timer_list listen_timer;int cancel_listen; -u8 *gb; +u8 gb[NFC_MAX_GT_LEN ];size_t gb_len;u8 tgt_available_prots;
drivers/nfc/pn544/pn544.c +3 -4
@@ -377,10 +377,9 @@ static int pn544_hci_start_poll(struct nfc_hci_dev *hdev,return r;if ((im_protocols | tm_protocols) &NFC_PROTO_NFC_DEP_MASK ) { - hdev->gb =nfc_get_local_general_bytes (hdev->ndev, - &hdev->gb_len); -pr_debug ("generate local bytes %p\n" , hdev->gb); -if (hdev->gb ==NULL || hdev->gb_len ==0 ) { +nfc_get_local_general_bytes (hdev->ndev, hdev->gb, +sizeof (hdev->gb), &hdev->gb_len); +if (hdev->gb_len ==0 ) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK ; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK ; }
drivers/nfc/st21nfca/core.c +4 -4
@@ -351,10 +351,10 @@ static int st21nfca_hci_start_poll(struct nfc_hci_dev *hdev,if (r <0 )return r; }else { - hdev->gb =nfc_get_local_general_bytes (hdev->ndev, - &hdev->gb_len); - -if (hdev->gb ==NULL || hdev->gb_len ==0 ) { +nfc_get_local_general_bytes (hdev->ndev, hdev->gb, +sizeof (hdev->gb), + &hdev->gb_len); +if (hdev->gb_len ==0 ) { im_protocols &= ~NFC_PROTO_NFC_DEP_MASK ; tm_protocols &= ~NFC_PROTO_NFC_DEP_MASK ; }
include/net/nfc/hci.h +1 -1
@@ -144,7 +144,7 @@ struct nfc_hci_dev {data_exchange_cb_t async_cb;void *async_cb_context; -u8 *gb; +u8 gb[NFC_MAX_GT_LEN ];size_t gb_len;unsigned long quirks;
include/net/nfc/nfc.h +2 -1
@@ -273,7 +273,8 @@ struct sk_buff *nfc_alloc_recv_skb(unsigned int size, gfp_t gfp);int nfc_set_remote_general_bytes (struct nfc_dev *dev,const u8 *gt,u8 gt_len); -u8 *nfc_get_local_general_bytes (struct nfc_dev *dev,size_t *gb_len); +u8 *nfc_get_local_general_bytes (struct nfc_dev *dev,u8 *out_gb, +size_t gb_max_len,size_t *gb_len);int nfc_fw_download_done (struct nfc_dev *dev,const char *firmware_name,u32 result);
net/nfc/core.c +7 -8
@@ -279,10 +279,10 @@ static struct nfc_target *nfc_find_target(struct nfc_dev *dev, u32 target_idx)int nfc_dep_link_up (struct nfc_dev *dev,int target_index,u8 comm_mode) { -int rc =0 ; -u8 *gb; -size_t gb_len;struct nfc_target *target; +u8 gb[NFC_MAX_GT_LEN ]; +size_t gb_len =0 ; +int rc =0 ;pr_debug ("dev_name=%s comm %d\n" ,dev_name (&dev->dev), comm_mode);
@@ -301,7 +301,7 @@ int nfc_dep_link_up(struct nfc_dev *dev, int target_index, u8 comm_mode) goto error; } - gb =nfc_llcp_general_bytes (dev, &gb_len); +nfc_get_local_general_bytes (dev, gb,sizeof (gb), &gb_len);if (gb_len >NFC_MAX_GT_LEN ) { rc = -EINVAL ; goto error;
@@ -644,11 +644,10 @@ int nfc_set_remote_general_bytes(struct nfc_dev *dev, const u8 *gb, u8 gb_len) }EXPORT_SYMBOL (nfc_set_remote_general_bytes); -u8 *nfc_get_local_general_bytes (struct nfc_dev *dev,size_t *gb_len) +u8 *nfc_get_local_general_bytes (struct nfc_dev *dev,u8 *out_gb, +size_t gb_max_len,size_t *gb_len) { -pr_debug ("dev_name=%s\n" ,dev_name (&dev->dev)); - -return nfc_llcp_general_bytes (dev, gb_len); +return nfc_llcp_general_bytes (dev, out_gb, gb_max_len, gb_len); }EXPORT_SYMBOL (nfc_get_local_general_bytes);
net/nfc/digital_dep.c +4 -4
@@ -1490,14 +1490,14 @@ static int digital_tg_send_atr_res(struct nfc_digital_dev *ddev, struct digital_atr_req *atr_req) {struct digital_atr_res *atr_res; +u8 gb[NFC_MAX_GT_LEN ];struct sk_buff *skb; -u8 *gb, payload_bits; +u8 payload_bits;size_t gb_len;int rc; - gb =nfc_get_local_general_bytes (ddev->nfc_dev, &gb_len); -if (!gb) - gb_len =0 ; +nfc_get_local_general_bytes (ddev->nfc_dev, gb,sizeof (gb), + &gb_len); skb =digital_skb_alloc (ddev,sizeof (struct digital_atr_res ) + gb_len);if (!skb)
net/nfc/llcp_core.c +13 -4
@@ -635,23 +635,32 @@ static int nfc_llcp_build_gb(struct nfc_llcp_local *local)return ret; } -u8 *nfc_llcp_general_bytes (struct nfc_dev *dev,size_t *general_bytes_len) +u8 *nfc_llcp_general_bytes (struct nfc_dev *dev,u8 *out_gb,size_t gb_max_len, +size_t *general_bytes_len) {struct nfc_llcp_local *local; +if (!out_gb || !general_bytes_len) +return NULL ; + local =nfc_llcp_find_local (dev); -if (local ==NULL ) { +if (!local) { *general_bytes_len =0 ;return NULL ; }nfc_llcp_build_gb (local); - *general_bytes_len = local->gb_len; +if (local->gb_len) { + *general_bytes_len =min_t (size_t, local->gb_len, gb_max_len); +memcpy (out_gb, local->gb, *general_bytes_len); + }else { + *general_bytes_len =0 ; + }nfc_llcp_local_put (local); -return local->gb; +return out_gb; }int nfc_llcp_set_remote_gb (struct nfc_dev *dev,const u8 *gb,u8 gb_len)
net/nfc/nci/core.c +5 -5
@@ -780,15 +780,15 @@ static int nci_set_local_general_bytes(struct nfc_dev *nfc_dev) {struct nci_dev *ndev =nfc_get_drvdata (nfc_dev);struct nci_set_config_param param; +u8 gb[NFC_MAX_GT_LEN ];int rc; - param.val =nfc_get_local_general_bytes (nfc_dev, ¶m.len); -if ((param.val ==NULL ) || (param.len ==0 )) +nfc_get_local_general_bytes (nfc_dev, gb,sizeof (gb), + ¶m.len); +if (param.len ==0 )return 0 ; -if (param.len >NFC_MAX_GT_LEN ) -return -EINVAL ; - + param.val = gb; param.id =NCI_PN_ATR_REQ_GEN_BYTES ; rc = nci_request(ndev, nci_set_config_req, ¶m,
net/nfc/nfc.h +2 -1
@@ -49,7 +49,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx,int nfc_llcp_register_device (struct nfc_dev *dev);void nfc_llcp_unregister_device (struct nfc_dev *dev);int nfc_llcp_set_remote_gb (struct nfc_dev *dev,const u8 *gb,u8 gb_len); -u8 *nfc_llcp_general_bytes (struct nfc_dev *dev,size_t *general_bytes_len); +u8 *nfc_llcp_general_bytes (struct nfc_dev *dev,u8 *out_gb,size_t gb_max_len, +size_t *general_bytes_len);int nfc_llcp_data_received (struct nfc_dev *dev,struct sk_buff *skb);struct nfc_llcp_local *nfc_llcp_find_local (struct nfc_dev *dev);int nfc_llcp_local_put (struct nfc_llcp_local *local);
Signature · 1 line
2.43.0
Quoted 27 lines · Ren Wei
From: Luxiao Xu <[email protected]>
Commit
6709d4b7bc2e("net: nfc: Fix use-after-free caused bynfc_llcp_find_local") attempted to fix a use-after-free (UAF) issue by invokingnfc_llcp_local_put(local)after accessinglocal->gb. However, if the reference count drops to zero, local is freed immediately, leading to a use-after-free when callers access the returned pointer. Alternative approaches using dynamic allocation (e.g. kmemdup) introduced memory leaks because callers consistently treat the returned pointer as borrowed memory.Fix this properly by refactoring
nfc_llcp_general_bytes()andnfc_get_local_general_bytes()to accept a caller-provided output buffer (out_gb) and its maximum length (gb_max_len). The general bytes are safely copied intoout_gbbefore callingnfc_llcp_local_put(local), ensuring safe lifetime management without ownership transfer complications.Update all callers across drivers (microread, pn533, pn544, st21nfca,
digital_dep, and nci) to provide their own destination buffers and pass them tonfc_get_local_general_bytes().Fixes 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local")Reported-by Vega <[email protected]>Assisted-by LLMSigned-off-by Luxiao Xu <[email protected]>Signed-off-by Ren Wei <[email protected]>
Quoted 10 lines
---
v3:
- Include <net/nfc/nfc.h> inpn533.hto fix build error inuart.ccaused by undefinedNFC_MAX_GT_LEN.
- Restorenci_request()innci_set_local_general_bytes()to preservendev->req_locksynchronization (avoid unlocked__nci_request()vianci_set_config()).
v2:
- Use caller-provided output buffers to fix UAF instead of dynamic allocation (kmemdup), avoiding memory leaks.
Overall this patch looks good to me.
But I'd appreciate it if you could consider my suggestion further below.
...
Quoted 29 lines
net/nfc/llcp_core.c +8
-3@@ -635,23 +635,32 @@ static int nfc_llcp_build_gb(struct nfc_llcp_local *local)return ret; } -u8 *nfc_llcp_general_bytes (struct nfc_dev *dev,size_t *general_bytes_len) +u8 *nfc_llcp_general_bytes (struct nfc_dev *dev,u8 *out_gb,size_t gb_max_len, +size_t *general_bytes_len) {struct nfc_llcp_local *local; +if (!out_gb || !general_bytes_len) +return NULL ; + local =nfc_llcp_find_local (dev); -if (local ==NULL ) { +if (!local) { *general_bytes_len =0 ;return NULL ; }nfc_llcp_build_gb (local); - *general_bytes_len = local->gb_len; +if (local->gb_len) { + *general_bytes_len =min_t (size_t, local->gb_len, gb_max_len);
net/nfc/llcp_core.c static int nfc_llcp_build_gb(struct nfc_llcp_local *local) · line 664
Quoted 10 lines
net/nfc/llcp_core.c +5
-1+memcpy (out_gb, local->gb, *general_bytes_len); + }else { + *general_bytes_len =0 ; + }nfc_llcp_local_put (local); -return local->gb; +return out_gb; }
As far as I can see the return value of nfc_llcp_general_bytes() is now ignored. And for a bug fix the approach you have taken looks good, as the interface provided by nfc_llcp_general_bytes() is only slightly modified.
But, FWIIW, I think it would be cleaner if nfc_llcp_general_bytes() returned the length, or 0 on error. And the general_bytes_len parameter was passed by value rather than reference.
Something like this:
size_t nfc_llcp_general_bytes(struct nfc_dev *dev, u8 *out_gb,
size_t gb_max_len, size_t general_bytes_len)
{
...
if (error_condition)
return 0;
...
return general_bytes_len;
}Perhaps that approach could be considered as a follow-up.
David Heidelberg
maintainerQuoted 29 lines · Simon Horman
Quoted 28 lines · Ren Wei
From: Luxiao Xu <[email protected]>
Commit
6709d4b7bc2e("net: nfc: Fix use-after-free caused bynfc_llcp_find_local") attempted to fix a use-after-free (UAF) issue by invokingnfc_llcp_local_put(local)after accessinglocal->gb. However, if the reference count drops to zero, local is freed immediately, leading to a use-after-free when callers access the returned pointer. Alternative approaches using dynamic allocation (e.g. kmemdup) introduced memory leaks because callers consistently treat the returned pointer as borrowed memory.Fix this properly by refactoring
nfc_llcp_general_bytes()andnfc_get_local_general_bytes()to accept a caller-provided output buffer (out_gb) and its maximum length (gb_max_len). The general bytes are safely copied intoout_gbbefore callingnfc_llcp_local_put(local), ensuring safe lifetime management without ownership transfer complications.Update all callers across drivers (microread, pn533, pn544, st21nfca,
digital_dep, and nci) to provide their own destination buffers and pass them tonfc_get_local_general_bytes().Fixes 6709d4b7bc2e ("net: nfc: Fix use-after-free caused by nfc_llcp_find_local")Reported-by Vega <[email protected]>Assisted-by LLMSigned-off-by Luxiao Xu <[email protected]>Signed-off-by Ren Wei <[email protected]>---
[...]
Quoted 24 lines
As far as I can see the return value of
nfc_llcp_general_bytes()is now ignored. And for a bug fix the approach you have taken looks good, as the interface provided bynfc_llcp_general_bytes()is only slightly modified.But, FWIIW, I think it would be cleaner if
nfc_llcp_general_bytes()returned the length, or 0 on error. And thegeneral_bytes_lenparameter was passed by value rather than reference.Something like this:
size_tnfc_llcp_general_bytes(structnfc_dev*dev, u8*out_gb,
size_tgb_max_len,size_tgeneral_bytes_len){ ... if (error_condition) return 0; ... return general_bytes_len; }
Quoted 1 line
Perhaps that approach could be considered as a follow-up.
Yes, that would be really great!
Thanks
David