Message ID | 20230625091007.199624-1-linma@zju.edu.cn (mailing list archive) |
---|---|
State | Accepted |
Commit | 6709d4b7bc2e079241fdef15d1160581c5261c10 |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | [v5] net: nfc: Fix use-after-free caused by nfc_llcp_find_local | expand |
+ maintainers; corrected Davem's email address On Sun, Jun 25, 2023 at 05:10:07PM +0800, Lin Ma wrote: > This commit fixes several use-after-free that caused by function > nfc_llcp_find_local(). For example, one UAF can happen when below buggy > time window occurs. > > // nfc_genl_llc_get_params | // nfc_unregister_device > | > dev = nfc_get_device(idx); | device_lock(...) > if (!dev) | dev->shutting_down = true; > return -ENODEV; | device_unlock(...); > | > device_lock(...); | // nfc_llcp_unregister_device > | nfc_llcp_find_local() > nfc_llcp_find_local(...); | > | local_cleanup() > if (!local) { | > rc = -ENODEV; | // nfc_llcp_local_put > goto exit; | kref_put(.., local_release) > } | > | // local_release > | list_del(&local->list) > // nfc_genl_send_params | kfree() > local->dev->idx !!!UAF!!! | > | > > and the crash trace for the one of the discussed UAF like: > > BUG: KASAN: slab-use-after-free in nfc_genl_llc_get_params+0x72f/0x780 net/nfc/netlink.c:1045 > Read of size 8 at addr ffff888105b0e410 by task 20114 > > Call Trace: > <TASK> > __dump_stack lib/dump_stack.c:88 [inline] > dump_stack_lvl+0x72/0xa0 lib/dump_stack.c:106 > print_address_description mm/kasan/report.c:319 [inline] > print_report+0xcc/0x620 mm/kasan/report.c:430 > kasan_report+0xb2/0xe0 mm/kasan/report.c:536 > nfc_genl_send_params net/nfc/netlink.c:999 [inline] > nfc_genl_llc_get_params+0x72f/0x780 net/nfc/netlink.c:1045 > genl_family_rcv_msg_doit.isra.0+0x1ee/0x2e0 net/netlink/genetlink.c:968 > genl_family_rcv_msg net/netlink/genetlink.c:1048 [inline] > genl_rcv_msg+0x503/0x7d0 net/netlink/genetlink.c:1065 > netlink_rcv_skb+0x161/0x430 net/netlink/af_netlink.c:2548 > genl_rcv+0x28/0x40 net/netlink/genetlink.c:1076 > netlink_unicast_kernel net/netlink/af_netlink.c:1339 [inline] > netlink_unicast+0x644/0x900 net/netlink/af_netlink.c:1365 > netlink_sendmsg+0x934/0xe70 net/netlink/af_netlink.c:1913 > sock_sendmsg_nosec net/socket.c:724 [inline] > sock_sendmsg+0x1b6/0x200 net/socket.c:747 > ____sys_sendmsg+0x6e9/0x890 net/socket.c:2501 > ___sys_sendmsg+0x110/0x1b0 net/socket.c:2555 > __sys_sendmsg+0xf7/0x1d0 net/socket.c:2584 > do_syscall_x64 arch/x86/entry/common.c:50 [inline] > do_syscall_64+0x3f/0x90 arch/x86/entry/common.c:80 > entry_SYSCALL_64_after_hwframe+0x72/0xdc > RIP: 0033:0x7f34640a2389 > RSP: 002b:00007f3463415168 EFLAGS: 00000246 ORIG_RAX: 000000000000002e > RAX: ffffffffffffffda RBX: 00007f34641c1f80 RCX: 00007f34640a2389 > RDX: 0000000000000000 RSI: 0000000020000240 RDI: 0000000000000006 > RBP: 00007f34640ed493 R08: 0000000000000000 R09: 0000000000000000 > R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 > R13: 00007ffe38449ecf R14: 00007f3463415300 R15: 0000000000022000 > </TASK> > > Allocated by task 20116: > kasan_save_stack+0x22/0x50 mm/kasan/common.c:45 > kasan_set_track+0x25/0x30 mm/kasan/common.c:52 > ____kasan_kmalloc mm/kasan/common.c:374 [inline] > __kasan_kmalloc+0x7f/0x90 mm/kasan/common.c:383 > kmalloc include/linux/slab.h:580 [inline] > kzalloc include/linux/slab.h:720 [inline] > nfc_llcp_register_device+0x49/0xa40 net/nfc/llcp_core.c:1567 > nfc_register_device+0x61/0x260 net/nfc/core.c:1124 > nci_register_device+0x776/0xb20 net/nfc/nci/core.c:1257 > virtual_ncidev_open+0x147/0x230 drivers/nfc/virtual_ncidev.c:148 > misc_open+0x379/0x4a0 drivers/char/misc.c:165 > chrdev_open+0x26c/0x780 fs/char_dev.c:414 > do_dentry_open+0x6c4/0x12a0 fs/open.c:920 > do_open fs/namei.c:3560 [inline] > path_openat+0x24fe/0x37e0 fs/namei.c:3715 > do_filp_open+0x1ba/0x410 fs/namei.c:3742 > do_sys_openat2+0x171/0x4c0 fs/open.c:1356 > do_sys_open fs/open.c:1372 [inline] > __do_sys_openat fs/open.c:1388 [inline] > __se_sys_openat fs/open.c:1383 [inline] > __x64_sys_openat+0x143/0x200 fs/open.c:1383 > do_syscall_x64 arch/x86/entry/common.c:50 [inline] > do_syscall_64+0x3f/0x90 arch/x86/entry/common.c:80 > entry_SYSCALL_64_after_hwframe+0x72/0xdc > > Freed by task 20115: > kasan_save_stack+0x22/0x50 mm/kasan/common.c:45 > kasan_set_track+0x25/0x30 mm/kasan/common.c:52 > kasan_save_free_info+0x2e/0x50 mm/kasan/generic.c:521 > ____kasan_slab_free mm/kasan/common.c:236 [inline] > ____kasan_slab_free mm/kasan/common.c:200 [inline] > __kasan_slab_free+0x10a/0x190 mm/kasan/common.c:244 > kasan_slab_free include/linux/kasan.h:162 [inline] > slab_free_hook mm/slub.c:1781 [inline] > slab_free_freelist_hook mm/slub.c:1807 [inline] > slab_free mm/slub.c:3787 [inline] > __kmem_cache_free+0x7a/0x190 mm/slub.c:3800 > local_release net/nfc/llcp_core.c:174 [inline] > kref_put include/linux/kref.h:65 [inline] > nfc_llcp_local_put net/nfc/llcp_core.c:182 [inline] > nfc_llcp_local_put net/nfc/llcp_core.c:177 [inline] > nfc_llcp_unregister_device+0x206/0x290 net/nfc/llcp_core.c:1620 > nfc_unregister_device+0x160/0x1d0 net/nfc/core.c:1179 > virtual_ncidev_close+0x52/0xa0 drivers/nfc/virtual_ncidev.c:163 > __fput+0x252/0xa20 fs/file_table.c:321 > task_work_run+0x174/0x270 kernel/task_work.c:179 > resume_user_mode_work include/linux/resume_user_mode.h:49 [inline] > exit_to_user_mode_loop kernel/entry/common.c:171 [inline] > exit_to_user_mode_prepare+0x108/0x110 kernel/entry/common.c:204 > __syscall_exit_to_user_mode_work kernel/entry/common.c:286 [inline] > syscall_exit_to_user_mode+0x21/0x50 kernel/entry/common.c:297 > do_syscall_64+0x4c/0x90 arch/x86/entry/common.c:86 > entry_SYSCALL_64_after_hwframe+0x72/0xdc > > Last potentially related work creation: > kasan_save_stack+0x22/0x50 mm/kasan/common.c:45 > __kasan_record_aux_stack+0x95/0xb0 mm/kasan/generic.c:491 > kvfree_call_rcu+0x29/0xa80 kernel/rcu/tree.c:3328 > drop_sysctl_table+0x3be/0x4e0 fs/proc/proc_sysctl.c:1735 > unregister_sysctl_table.part.0+0x9c/0x190 fs/proc/proc_sysctl.c:1773 > unregister_sysctl_table+0x24/0x30 fs/proc/proc_sysctl.c:1753 > neigh_sysctl_unregister+0x5f/0x80 net/core/neighbour.c:3895 > addrconf_notify+0x140/0x17b0 net/ipv6/addrconf.c:3684 > notifier_call_chain+0xbe/0x210 kernel/notifier.c:87 > call_netdevice_notifiers_info+0xb5/0x150 net/core/dev.c:1937 > call_netdevice_notifiers_extack net/core/dev.c:1975 [inline] > call_netdevice_notifiers net/core/dev.c:1989 [inline] > dev_change_name+0x3c3/0x870 net/core/dev.c:1211 > dev_ifsioc+0x800/0xf70 net/core/dev_ioctl.c:376 > dev_ioctl+0x3d9/0xf80 net/core/dev_ioctl.c:542 > sock_do_ioctl+0x160/0x260 net/socket.c:1213 > sock_ioctl+0x3f9/0x670 net/socket.c:1316 > vfs_ioctl fs/ioctl.c:51 [inline] > __do_sys_ioctl fs/ioctl.c:870 [inline] > __se_sys_ioctl fs/ioctl.c:856 [inline] > __x64_sys_ioctl+0x19e/0x210 fs/ioctl.c:856 > do_syscall_x64 arch/x86/entry/common.c:50 [inline] > do_syscall_64+0x3f/0x90 arch/x86/entry/common.c:80 > entry_SYSCALL_64_after_hwframe+0x72/0xdc > > The buggy address belongs to the object at ffff888105b0e400 > which belongs to the cache kmalloc-1k of size 1024 > The buggy address is located 16 bytes inside of > freed 1024-byte region [ffff888105b0e400, ffff888105b0e800) > > The buggy address belongs to the physical page: > head:ffffea000416c200 order:3 entire_mapcount:0 nr_pages_mapped:0 pincount:0 > flags: 0x200000000010200(slab|head|node=0|zone=2) > raw: 0200000000010200 ffff8881000430c0 ffffea00044c7010 ffffea0004510e10 > raw: 0000000000000000 00000000000a000a 00000001ffffffff 0000000000000000 > page dumped because: kasan: bad access detected > > Memory state around the buggy address: > ffff888105b0e300: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc > ffff888105b0e380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc > >ffff888105b0e400: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb > ^ > ffff888105b0e480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb > ffff888105b0e500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb > > In summary, this patch solves those use-after-free by > > 1. Re-implement the nfc_llcp_find_local(). The current version does not > grab the reference when getting the local from the linked list. For > example, the llcp_sock_bind() gets the reference like below: > > // llcp_sock_bind() > > local = nfc_llcp_find_local(dev); // A > ..... \ > | raceable > ..... / > llcp_sock->local = nfc_llcp_local_get(local); // B > > There is an apparent race window that one can drop the reference > and free the local object fetched in (A) before (B) gets the reference. > > 2. Some callers of the nfc_llcp_find_local() do not grab the reference > at all. For example, the nfc_genl_llc_{{get/set}_params/sdreq} functions. > We add the nfc_llcp_local_put() for them. Moreover, we add the necessary > error handling function to put the reference. > > 3. Add the nfc_llcp_remove_local() helper. The local object is removed > from the linked list in local_release() when all reference is gone. This > patch removes it when nfc_llcp_unregister_device() is called. > > Therefore, every caller of nfc_llcp_find_local() will get a reference > even when the nfc_llcp_unregister_device() is called. This promises no > use-after-free for the local object is ever possible. > > Fixes: 52feb444a903 ("NFC: Extend netlink interface for LTO, RW, and MIUX parameters support") > Fixes: c7aa12252f51 ("NFC: Take a reference on the LLCP local pointer when creating a socket") > Signed-off-by: Lin Ma <linma@zju.edu.cn> > --- > V1 -> V2: not just fix nfc_genl_llc_{{get/set}_params/sdreq} > but concern on all callers of nfc_llcp_find_local() > V2 -> V3: make nfc_llcp_local_get() static to ease the prototype > warning, and make the commit message more clear > V3 -> V4: make nfc_llcp_remove_local() also static > V4 -> V5: fix the buggy error handling in llcp_sock_connect(), make > sure llcp_sock->local/dev reset to NULL Thanks. My only issue with v4 has been resolved by v5. Reviewed-by: Simon Horman <simon.horman@corigine.com> [ full patch left in place below for the benefit of newly CCed people ] > net/nfc/llcp.h | 1 - > net/nfc/llcp_commands.c | 12 +++++++--- > net/nfc/llcp_core.c | 49 +++++++++++++++++++++++++++++++++++------ > net/nfc/llcp_sock.c | 18 ++++++++------- > net/nfc/netlink.c | 20 ++++++++++++----- > net/nfc/nfc.h | 1 + > 6 files changed, 77 insertions(+), 24 deletions(-) > > diff --git a/net/nfc/llcp.h b/net/nfc/llcp.h > index c1d9be636933..d8345ed57c95 100644 > --- a/net/nfc/llcp.h > +++ b/net/nfc/llcp.h > @@ -201,7 +201,6 @@ void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *s); > void nfc_llcp_sock_unlink(struct llcp_sock_list *l, struct sock *s); > void nfc_llcp_socket_remote_param_init(struct nfc_llcp_sock *sock); > struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev); > -struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local); > int nfc_llcp_local_put(struct nfc_llcp_local *local); > u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local, > struct nfc_llcp_sock *sock); > diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c > index 41e3a20c8935..5d2d4bc26ef9 100644 > --- a/net/nfc/llcp_commands.c > +++ b/net/nfc/llcp_commands.c > @@ -359,6 +359,7 @@ int nfc_llcp_send_symm(struct nfc_dev *dev) > struct sk_buff *skb; > struct nfc_llcp_local *local; > u16 size = 0; > + int err; > > local = nfc_llcp_find_local(dev); > if (local == NULL) > @@ -368,8 +369,10 @@ int nfc_llcp_send_symm(struct nfc_dev *dev) > size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE; > > skb = alloc_skb(size, GFP_KERNEL); > - if (skb == NULL) > - return -ENOMEM; > + if (skb == NULL) { > + err = -ENOMEM; > + goto out; > + } > > skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE); > > @@ -379,8 +382,11 @@ int nfc_llcp_send_symm(struct nfc_dev *dev) > > nfc_llcp_send_to_raw_sock(local, skb, NFC_DIRECTION_TX); > > - return nfc_data_exchange(dev, local->target_idx, skb, > + err = nfc_data_exchange(dev, local->target_idx, skb, > nfc_llcp_recv, local); > +out: > + nfc_llcp_local_put(local); > + return err; > } > > int nfc_llcp_send_connect(struct nfc_llcp_sock *sock) > diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c > index a27e1842b2a0..f60e424e0607 100644 > --- a/net/nfc/llcp_core.c > +++ b/net/nfc/llcp_core.c > @@ -17,6 +17,8 @@ > static u8 llcp_magic[3] = {0x46, 0x66, 0x6d}; > > static LIST_HEAD(llcp_devices); > +/* Protects llcp_devices list */ > +static DEFINE_SPINLOCK(llcp_devices_lock); > > static void nfc_llcp_rx_skb(struct nfc_llcp_local *local, struct sk_buff *skb); > > @@ -141,7 +143,7 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, > write_unlock(&local->raw_sockets.lock); > } > > -struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) > +static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) > { > kref_get(&local->ref); > > @@ -169,7 +171,6 @@ static void local_release(struct kref *ref) > > local = container_of(ref, struct nfc_llcp_local, ref); > > - list_del(&local->list); > local_cleanup(local); > kfree(local); > } > @@ -282,12 +283,33 @@ static void nfc_llcp_sdreq_timer(struct timer_list *t) > struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev) > { > struct nfc_llcp_local *local; > + struct nfc_llcp_local *res = NULL; > > + spin_lock(&llcp_devices_lock); > list_for_each_entry(local, &llcp_devices, list) > - if (local->dev == dev) > + if (local->dev == dev) { > + res = nfc_llcp_local_get(local); > + break; > + } > + spin_unlock(&llcp_devices_lock); > + > + return res; > +} > + > +static struct nfc_llcp_local *nfc_llcp_remove_local(struct nfc_dev *dev) > +{ > + struct nfc_llcp_local *local, *tmp; > + > + spin_lock(&llcp_devices_lock); > + list_for_each_entry_safe(local, tmp, &llcp_devices, list) > + if (local->dev == dev) { > + list_del(&local->list); > + spin_unlock(&llcp_devices_lock); > return local; > + } > + spin_unlock(&llcp_devices_lock); > > - pr_debug("No device found\n"); > + pr_warn("Shutting down device not found\n"); > > return NULL; > } > @@ -608,12 +630,15 @@ u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len) > > *general_bytes_len = local->gb_len; > > + nfc_llcp_local_put(local); > + > return local->gb; > } > > int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) > { > struct nfc_llcp_local *local; > + int err; > > if (gb_len < 3 || gb_len > NFC_MAX_GT_LEN) > return -EINVAL; > @@ -630,12 +655,16 @@ int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) > > if (memcmp(local->remote_gb, llcp_magic, 3)) { > pr_err("MAC does not support LLCP\n"); > - return -EINVAL; > + err = -EINVAL; > + goto out; > } > > - return nfc_llcp_parse_gb_tlv(local, > + err = nfc_llcp_parse_gb_tlv(local, > &local->remote_gb[3], > local->remote_gb_len - 3); > +out: > + nfc_llcp_local_put(local); > + return err; > } > > static u8 nfc_llcp_dsap(const struct sk_buff *pdu) > @@ -1517,6 +1546,8 @@ int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb) > > __nfc_llcp_recv(local, skb); > > + nfc_llcp_local_put(local); > + > return 0; > } > > @@ -1533,6 +1564,8 @@ void nfc_llcp_mac_is_down(struct nfc_dev *dev) > > /* Close and purge all existing sockets */ > nfc_llcp_socket_release(local, true, 0); > + > + nfc_llcp_local_put(local); > } > > void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, > @@ -1558,6 +1591,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, > mod_timer(&local->link_timer, > jiffies + msecs_to_jiffies(local->remote_lto)); > } > + > + nfc_llcp_local_put(local); > } > > int nfc_llcp_register_device(struct nfc_dev *ndev) > @@ -1608,7 +1643,7 @@ int nfc_llcp_register_device(struct nfc_dev *ndev) > > void nfc_llcp_unregister_device(struct nfc_dev *dev) > { > - struct nfc_llcp_local *local = nfc_llcp_find_local(dev); > + struct nfc_llcp_local *local = nfc_llcp_remove_local(dev); > > if (local == NULL) { > pr_debug("No such device\n"); > diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c > index 77642d18a3b4..645677f84dba 100644 > --- a/net/nfc/llcp_sock.c > +++ b/net/nfc/llcp_sock.c > @@ -99,7 +99,7 @@ static int llcp_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) > } > > llcp_sock->dev = dev; > - llcp_sock->local = nfc_llcp_local_get(local); > + llcp_sock->local = local; > llcp_sock->nfc_protocol = llcp_addr.nfc_protocol; > llcp_sock->service_name_len = min_t(unsigned int, > llcp_addr.service_name_len, > @@ -186,7 +186,7 @@ static int llcp_raw_sock_bind(struct socket *sock, struct sockaddr *addr, > } > > llcp_sock->dev = dev; > - llcp_sock->local = nfc_llcp_local_get(local); > + llcp_sock->local = local; > llcp_sock->nfc_protocol = llcp_addr.nfc_protocol; > > nfc_llcp_sock_link(&local->raw_sockets, sk); > @@ -696,22 +696,22 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr, > if (dev->dep_link_up == false) { > ret = -ENOLINK; > device_unlock(&dev->dev); > - goto put_dev; > + goto sock_llcp_put_local; > } > device_unlock(&dev->dev); > > if (local->rf_mode == NFC_RF_INITIATOR && > addr->target_idx != local->target_idx) { > ret = -ENOLINK; > - goto put_dev; > + goto sock_llcp_put_local; > } > > llcp_sock->dev = dev; > - llcp_sock->local = nfc_llcp_local_get(local); > + llcp_sock->local = local; > llcp_sock->ssap = nfc_llcp_get_local_ssap(local); > if (llcp_sock->ssap == LLCP_SAP_MAX) { > ret = -ENOMEM; > - goto sock_llcp_put_local; > + goto sock_llcp_nullify; > } > > llcp_sock->reserved_ssap = llcp_sock->ssap; > @@ -757,11 +757,13 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr, > sock_llcp_release: > nfc_llcp_put_ssap(local, llcp_sock->ssap); > > -sock_llcp_put_local: > - nfc_llcp_local_put(llcp_sock->local); > +sock_llcp_nullify: > llcp_sock->local = NULL; > llcp_sock->dev = NULL; > > +sock_llcp_put_local: > + nfc_llcp_local_put(local); > + > put_dev: > nfc_put_device(dev); > > diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c > index b9264e730fd9..e9ac6a6f934e 100644 > --- a/net/nfc/netlink.c > +++ b/net/nfc/netlink.c > @@ -1039,11 +1039,14 @@ static int nfc_genl_llc_get_params(struct sk_buff *skb, struct genl_info *info) > msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); > if (!msg) { > rc = -ENOMEM; > - goto exit; > + goto put_local; > } > > rc = nfc_genl_send_params(msg, local, info->snd_portid, info->snd_seq); > > +put_local: > + nfc_llcp_local_put(local); > + > exit: > device_unlock(&dev->dev); > > @@ -1105,7 +1108,7 @@ static int nfc_genl_llc_set_params(struct sk_buff *skb, struct genl_info *info) > if (info->attrs[NFC_ATTR_LLC_PARAM_LTO]) { > if (dev->dep_link_up) { > rc = -EINPROGRESS; > - goto exit; > + goto put_local; > } > > local->lto = nla_get_u8(info->attrs[NFC_ATTR_LLC_PARAM_LTO]); > @@ -1117,6 +1120,9 @@ static int nfc_genl_llc_set_params(struct sk_buff *skb, struct genl_info *info) > if (info->attrs[NFC_ATTR_LLC_PARAM_MIUX]) > local->miux = cpu_to_be16(miux); > > +put_local: > + nfc_llcp_local_put(local); > + > exit: > device_unlock(&dev->dev); > > @@ -1172,7 +1178,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) > > if (rc != 0) { > rc = -EINVAL; > - goto exit; > + goto put_local; > } > > if (!sdp_attrs[NFC_SDP_ATTR_URI]) > @@ -1191,7 +1197,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) > sdreq = nfc_llcp_build_sdreq_tlv(tid, uri, uri_len); > if (sdreq == NULL) { > rc = -ENOMEM; > - goto exit; > + goto put_local; > } > > tlvs_len += sdreq->tlv_len; > @@ -1201,10 +1207,14 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) > > if (hlist_empty(&sdreq_list)) { > rc = -EINVAL; > - goto exit; > + goto put_local; > } > > rc = nfc_llcp_send_snl_sdreq(local, &sdreq_list, tlvs_len); > + > +put_local: > + nfc_llcp_local_put(local); > + > exit: > device_unlock(&dev->dev); > > diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h > index de2ec66d7e83..0b1e6466f4fb 100644 > --- a/net/nfc/nfc.h > +++ b/net/nfc/nfc.h > @@ -52,6 +52,7 @@ 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); > 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); > int __init nfc_llcp_init(void); > void nfc_llcp_exit(void); > void nfc_llcp_free_sdp_tlv(struct nfc_llcp_sdp_tlv *sdp); > -- > 2.17.1 > >
Hello: This patch was applied to netdev/net.git (main) by David S. Miller <davem@davemloft.net>: On Sun, 25 Jun 2023 17:10:07 +0800 you wrote: > This commit fixes several use-after-free that caused by function > nfc_llcp_find_local(). For example, one UAF can happen when below buggy > time window occurs. > > // nfc_genl_llc_get_params | // nfc_unregister_device > | > dev = nfc_get_device(idx); | device_lock(...) > if (!dev) | dev->shutting_down = true; > return -ENODEV; | device_unlock(...); > | > device_lock(...); | // nfc_llcp_unregister_device > | nfc_llcp_find_local() > nfc_llcp_find_local(...); | > | local_cleanup() > if (!local) { | > rc = -ENODEV; | // nfc_llcp_local_put > goto exit; | kref_put(.., local_release) > } | > | // local_release > | list_del(&local->list) > // nfc_genl_send_params | kfree() > local->dev->idx !!!UAF!!! | > | > > [...] Here is the summary with links: - [v5] net: nfc: Fix use-after-free caused by nfc_llcp_find_local https://git.kernel.org/netdev/net/c/6709d4b7bc2e You are awesome, thank you!
diff --git a/net/nfc/llcp.h b/net/nfc/llcp.h index c1d9be636933..d8345ed57c95 100644 --- a/net/nfc/llcp.h +++ b/net/nfc/llcp.h @@ -201,7 +201,6 @@ void nfc_llcp_sock_link(struct llcp_sock_list *l, struct sock *s); void nfc_llcp_sock_unlink(struct llcp_sock_list *l, struct sock *s); void nfc_llcp_socket_remote_param_init(struct nfc_llcp_sock *sock); struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev); -struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local); int nfc_llcp_local_put(struct nfc_llcp_local *local); u8 nfc_llcp_get_sdp_ssap(struct nfc_llcp_local *local, struct nfc_llcp_sock *sock); diff --git a/net/nfc/llcp_commands.c b/net/nfc/llcp_commands.c index 41e3a20c8935..5d2d4bc26ef9 100644 --- a/net/nfc/llcp_commands.c +++ b/net/nfc/llcp_commands.c @@ -359,6 +359,7 @@ int nfc_llcp_send_symm(struct nfc_dev *dev) struct sk_buff *skb; struct nfc_llcp_local *local; u16 size = 0; + int err; local = nfc_llcp_find_local(dev); if (local == NULL) @@ -368,8 +369,10 @@ int nfc_llcp_send_symm(struct nfc_dev *dev) size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE; skb = alloc_skb(size, GFP_KERNEL); - if (skb == NULL) - return -ENOMEM; + if (skb == NULL) { + err = -ENOMEM; + goto out; + } skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE); @@ -379,8 +382,11 @@ int nfc_llcp_send_symm(struct nfc_dev *dev) nfc_llcp_send_to_raw_sock(local, skb, NFC_DIRECTION_TX); - return nfc_data_exchange(dev, local->target_idx, skb, + err = nfc_data_exchange(dev, local->target_idx, skb, nfc_llcp_recv, local); +out: + nfc_llcp_local_put(local); + return err; } int nfc_llcp_send_connect(struct nfc_llcp_sock *sock) diff --git a/net/nfc/llcp_core.c b/net/nfc/llcp_core.c index a27e1842b2a0..f60e424e0607 100644 --- a/net/nfc/llcp_core.c +++ b/net/nfc/llcp_core.c @@ -17,6 +17,8 @@ static u8 llcp_magic[3] = {0x46, 0x66, 0x6d}; static LIST_HEAD(llcp_devices); +/* Protects llcp_devices list */ +static DEFINE_SPINLOCK(llcp_devices_lock); static void nfc_llcp_rx_skb(struct nfc_llcp_local *local, struct sk_buff *skb); @@ -141,7 +143,7 @@ static void nfc_llcp_socket_release(struct nfc_llcp_local *local, bool device, write_unlock(&local->raw_sockets.lock); } -struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) +static struct nfc_llcp_local *nfc_llcp_local_get(struct nfc_llcp_local *local) { kref_get(&local->ref); @@ -169,7 +171,6 @@ static void local_release(struct kref *ref) local = container_of(ref, struct nfc_llcp_local, ref); - list_del(&local->list); local_cleanup(local); kfree(local); } @@ -282,12 +283,33 @@ static void nfc_llcp_sdreq_timer(struct timer_list *t) struct nfc_llcp_local *nfc_llcp_find_local(struct nfc_dev *dev) { struct nfc_llcp_local *local; + struct nfc_llcp_local *res = NULL; + spin_lock(&llcp_devices_lock); list_for_each_entry(local, &llcp_devices, list) - if (local->dev == dev) + if (local->dev == dev) { + res = nfc_llcp_local_get(local); + break; + } + spin_unlock(&llcp_devices_lock); + + return res; +} + +static struct nfc_llcp_local *nfc_llcp_remove_local(struct nfc_dev *dev) +{ + struct nfc_llcp_local *local, *tmp; + + spin_lock(&llcp_devices_lock); + list_for_each_entry_safe(local, tmp, &llcp_devices, list) + if (local->dev == dev) { + list_del(&local->list); + spin_unlock(&llcp_devices_lock); return local; + } + spin_unlock(&llcp_devices_lock); - pr_debug("No device found\n"); + pr_warn("Shutting down device not found\n"); return NULL; } @@ -608,12 +630,15 @@ u8 *nfc_llcp_general_bytes(struct nfc_dev *dev, size_t *general_bytes_len) *general_bytes_len = local->gb_len; + nfc_llcp_local_put(local); + return local->gb; } int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) { struct nfc_llcp_local *local; + int err; if (gb_len < 3 || gb_len > NFC_MAX_GT_LEN) return -EINVAL; @@ -630,12 +655,16 @@ int nfc_llcp_set_remote_gb(struct nfc_dev *dev, const u8 *gb, u8 gb_len) if (memcmp(local->remote_gb, llcp_magic, 3)) { pr_err("MAC does not support LLCP\n"); - return -EINVAL; + err = -EINVAL; + goto out; } - return nfc_llcp_parse_gb_tlv(local, + err = nfc_llcp_parse_gb_tlv(local, &local->remote_gb[3], local->remote_gb_len - 3); +out: + nfc_llcp_local_put(local); + return err; } static u8 nfc_llcp_dsap(const struct sk_buff *pdu) @@ -1517,6 +1546,8 @@ int nfc_llcp_data_received(struct nfc_dev *dev, struct sk_buff *skb) __nfc_llcp_recv(local, skb); + nfc_llcp_local_put(local); + return 0; } @@ -1533,6 +1564,8 @@ void nfc_llcp_mac_is_down(struct nfc_dev *dev) /* Close and purge all existing sockets */ nfc_llcp_socket_release(local, true, 0); + + nfc_llcp_local_put(local); } void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, @@ -1558,6 +1591,8 @@ void nfc_llcp_mac_is_up(struct nfc_dev *dev, u32 target_idx, mod_timer(&local->link_timer, jiffies + msecs_to_jiffies(local->remote_lto)); } + + nfc_llcp_local_put(local); } int nfc_llcp_register_device(struct nfc_dev *ndev) @@ -1608,7 +1643,7 @@ int nfc_llcp_register_device(struct nfc_dev *ndev) void nfc_llcp_unregister_device(struct nfc_dev *dev) { - struct nfc_llcp_local *local = nfc_llcp_find_local(dev); + struct nfc_llcp_local *local = nfc_llcp_remove_local(dev); if (local == NULL) { pr_debug("No such device\n"); diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c index 77642d18a3b4..645677f84dba 100644 --- a/net/nfc/llcp_sock.c +++ b/net/nfc/llcp_sock.c @@ -99,7 +99,7 @@ static int llcp_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) } llcp_sock->dev = dev; - llcp_sock->local = nfc_llcp_local_get(local); + llcp_sock->local = local; llcp_sock->nfc_protocol = llcp_addr.nfc_protocol; llcp_sock->service_name_len = min_t(unsigned int, llcp_addr.service_name_len, @@ -186,7 +186,7 @@ static int llcp_raw_sock_bind(struct socket *sock, struct sockaddr *addr, } llcp_sock->dev = dev; - llcp_sock->local = nfc_llcp_local_get(local); + llcp_sock->local = local; llcp_sock->nfc_protocol = llcp_addr.nfc_protocol; nfc_llcp_sock_link(&local->raw_sockets, sk); @@ -696,22 +696,22 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr, if (dev->dep_link_up == false) { ret = -ENOLINK; device_unlock(&dev->dev); - goto put_dev; + goto sock_llcp_put_local; } device_unlock(&dev->dev); if (local->rf_mode == NFC_RF_INITIATOR && addr->target_idx != local->target_idx) { ret = -ENOLINK; - goto put_dev; + goto sock_llcp_put_local; } llcp_sock->dev = dev; - llcp_sock->local = nfc_llcp_local_get(local); + llcp_sock->local = local; llcp_sock->ssap = nfc_llcp_get_local_ssap(local); if (llcp_sock->ssap == LLCP_SAP_MAX) { ret = -ENOMEM; - goto sock_llcp_put_local; + goto sock_llcp_nullify; } llcp_sock->reserved_ssap = llcp_sock->ssap; @@ -757,11 +757,13 @@ static int llcp_sock_connect(struct socket *sock, struct sockaddr *_addr, sock_llcp_release: nfc_llcp_put_ssap(local, llcp_sock->ssap); -sock_llcp_put_local: - nfc_llcp_local_put(llcp_sock->local); +sock_llcp_nullify: llcp_sock->local = NULL; llcp_sock->dev = NULL; +sock_llcp_put_local: + nfc_llcp_local_put(local); + put_dev: nfc_put_device(dev); diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index b9264e730fd9..e9ac6a6f934e 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c @@ -1039,11 +1039,14 @@ static int nfc_genl_llc_get_params(struct sk_buff *skb, struct genl_info *info) msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); if (!msg) { rc = -ENOMEM; - goto exit; + goto put_local; } rc = nfc_genl_send_params(msg, local, info->snd_portid, info->snd_seq); +put_local: + nfc_llcp_local_put(local); + exit: device_unlock(&dev->dev); @@ -1105,7 +1108,7 @@ static int nfc_genl_llc_set_params(struct sk_buff *skb, struct genl_info *info) if (info->attrs[NFC_ATTR_LLC_PARAM_LTO]) { if (dev->dep_link_up) { rc = -EINPROGRESS; - goto exit; + goto put_local; } local->lto = nla_get_u8(info->attrs[NFC_ATTR_LLC_PARAM_LTO]); @@ -1117,6 +1120,9 @@ static int nfc_genl_llc_set_params(struct sk_buff *skb, struct genl_info *info) if (info->attrs[NFC_ATTR_LLC_PARAM_MIUX]) local->miux = cpu_to_be16(miux); +put_local: + nfc_llcp_local_put(local); + exit: device_unlock(&dev->dev); @@ -1172,7 +1178,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) if (rc != 0) { rc = -EINVAL; - goto exit; + goto put_local; } if (!sdp_attrs[NFC_SDP_ATTR_URI]) @@ -1191,7 +1197,7 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) sdreq = nfc_llcp_build_sdreq_tlv(tid, uri, uri_len); if (sdreq == NULL) { rc = -ENOMEM; - goto exit; + goto put_local; } tlvs_len += sdreq->tlv_len; @@ -1201,10 +1207,14 @@ static int nfc_genl_llc_sdreq(struct sk_buff *skb, struct genl_info *info) if (hlist_empty(&sdreq_list)) { rc = -EINVAL; - goto exit; + goto put_local; } rc = nfc_llcp_send_snl_sdreq(local, &sdreq_list, tlvs_len); + +put_local: + nfc_llcp_local_put(local); + exit: device_unlock(&dev->dev); diff --git a/net/nfc/nfc.h b/net/nfc/nfc.h index de2ec66d7e83..0b1e6466f4fb 100644 --- a/net/nfc/nfc.h +++ b/net/nfc/nfc.h @@ -52,6 +52,7 @@ 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); 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); int __init nfc_llcp_init(void); void nfc_llcp_exit(void); void nfc_llcp_free_sdp_tlv(struct nfc_llcp_sdp_tlv *sdp);