From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BAA362F6911; Sun, 1 Mar 2026 01:49:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772329764; cv=none; b=QSI6HvimXqW2F2CdGcpaPZ/wY1T/psEhV9gS9mH6Dc+vR+5q15PtMkft3so/xkUnTALoqPsMRvJq6m5FkSlwCPzBhCyUFzeh3OWzEGLIaYmYgQ/SWGto1nyDfefcgsiXPKs//7Of27iRYJyu4w47BnBsIYvf8h/ioGfau7c4NDg= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772329764; c=relaxed/simple; bh=tGALvmjhMwRmcOza4NXBUiS2dA9IouY+EEIfLv9yvUU=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=sZRVSTPb9k6Nmwzkz74qt06ueFltRD0jJVKzKBxEY/k+6TCrQ4wZ0rcjOjHHDzWRnLUCl3qEC8MLRu43S+PyP9j1FMdkYp5NPqWjMWOs9PwoLmQeH2IUInZ4eaRLPdvi2ykstiM3RT0SFvowAaSQGwA9xUZRmPx2KKTmQiVBCJM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ucwqOACB; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ucwqOACB" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 12800C19425; Sun, 1 Mar 2026 01:49:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1772329764; bh=tGALvmjhMwRmcOza4NXBUiS2dA9IouY+EEIfLv9yvUU=; h=From:To:Cc:Subject:Date:From; b=ucwqOACBt0PeeKztL6KfEMI+kFmRLiQaWmbLsuz1NrZDSY5ZcgAUWkpEvPTkseo2/ AcDu5MjTOMA6CWEHs2ZLQ+x1W3kLdtt6sDimPZFP9qkh7fi0j08JRzVuig+96Hl9eF VvSzOXcHAEDBPsw+Mm16OdJ+8ChYP0Up6Ln5VceD68P7qmToGrBJKFkA5spFiTrwuC BSP+fVd+BodGXrl1ts5Fc/L4WyEvPmWmCqYcjgMFhpxNeNcYHf3bjt1vkQdz4hZX2R RRv5IVhE9UkE+J5w2spqi67jslcUSY8i4j3Cr2cz0rDfORPADvGQDnAyjMGQMskkhI rDSYc2VKblhtQ== From: Sasha Levin To: stable@vger.kernel.org, khtsai@google.com Cc: stable@kernel.org, Greg Kroah-Hartman , linux-usb@vger.kernel.org Subject: FAILED: Patch "usb: gadget: f_ncm: align net_device lifecycle with bind/unbind" failed to apply to 5.15-stable tree Date: Sat, 28 Feb 2026 20:49:22 -0500 Message-ID: <20260301014923.1713824-1-sashal@kernel.org> X-Mailer: git-send-email 2.51.0 Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Patchwork-Hint: ignore X-stable: review Content-Transfer-Encoding: 8bit The patch below does not apply to the 5.15-stable tree. If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to . Thanks, Sasha ------------------ original commit in Linus's tree ------------------ >From 56a512a9b4107079f68701e7d55da8507eb963d9 Mon Sep 17 00:00:00 2001 From: Kuen-Han Tsai Date: Tue, 30 Dec 2025 18:13:16 +0800 Subject: [PATCH] usb: gadget: f_ncm: align net_device lifecycle with bind/unbind Currently, the net_device is allocated in ncm_alloc_inst() and freed in ncm_free_inst(). This ties the network interface's lifetime to the configuration instance rather than the USB connection (bind/unbind). This decoupling causes issues when the USB gadget is disconnected where the underlying gadget device is removed. The net_device can outlive its parent, leading to dangling sysfs links and NULL pointer dereferences when accessing the freed gadget device. Problem 1: NULL pointer dereference on disconnect Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000 Call trace: __pi_strlen+0x14/0x150 rtnl_fill_ifinfo+0x6b4/0x708 rtmsg_ifinfo_build_skb+0xd8/0x13c rtmsg_ifinfo+0x50/0xa0 __dev_notify_flags+0x4c/0x1f0 dev_change_flags+0x54/0x70 do_setlink+0x390/0xebc rtnl_newlink+0x7d0/0xac8 rtnetlink_rcv_msg+0x27c/0x410 netlink_rcv_skb+0x134/0x150 rtnetlink_rcv+0x18/0x28 netlink_unicast+0x254/0x3f0 netlink_sendmsg+0x2e0/0x3d4 Problem 2: Dangling sysfs symlinks console:/ # ls -l /sys/class/net/ncm0 lrwxrwxrwx ... /sys/class/net/ncm0 -> /sys/devices/platform/.../gadget.0/net/ncm0 console:/ # ls -l /sys/devices/platform/.../gadget.0/net/ncm0 ls: .../gadget.0/net/ncm0: No such file or directory Move the net_device allocation to ncm_bind() and deallocation to ncm_unbind(). This ensures the network interface exists only when the gadget function is actually bound to a configuration. To support pre-bind configuration (e.g., setting interface name or MAC address via configfs), cache user-provided options in f_ncm_opts using the gether_opts structure. Apply these cached settings to the net_device upon creation in ncm_bind(). Preserve the use-after-free fix from commit 6334b8e4553c ("usb: gadget: f_ncm: Fix UAF ncm object at re-bind after usb ep transport error"). Check opts->net in ncm_set_alt() and ncm_disable() to ensure gether_disconnect() runs only if a connection was established. Fixes: 40d133d7f542 ("usb: gadget: f_ncm: convert to new function interface with backward compatibility") Cc: stable@kernel.org Signed-off-by: Kuen-Han Tsai Link: https://patch.msgid.link/20251230-ncm-refactor-v1-3-793e347bc7a7@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_ncm.c | 128 ++++++++++++++-------------- drivers/usb/gadget/function/u_ncm.h | 4 +- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 0e38330271d5a..e23adc132f886 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -83,6 +83,11 @@ static inline struct f_ncm *func_to_ncm(struct usb_function *f) return container_of(f, struct f_ncm, port.func); } +static inline struct f_ncm_opts *func_to_ncm_opts(struct usb_function *f) +{ + return container_of(f->fi, struct f_ncm_opts, func_inst); +} + /*-------------------------------------------------------------------------*/ /* @@ -859,6 +864,7 @@ static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_ncm *ncm = func_to_ncm(f); + struct f_ncm_opts *opts = func_to_ncm_opts(f); struct usb_composite_dev *cdev = f->config->cdev; /* Control interface has only altsetting 0 */ @@ -881,12 +887,13 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (alt > 1) goto fail; - if (ncm->netdev) { - DBG(cdev, "reset ncm\n"); - ncm->netdev = NULL; - gether_disconnect(&ncm->port); - ncm_reset_values(ncm); - } + scoped_guard(mutex, &opts->lock) + if (opts->net) { + DBG(cdev, "reset ncm\n"); + opts->net = NULL; + gether_disconnect(&ncm->port); + ncm_reset_values(ncm); + } /* * CDC Network only sends data in non-default altsettings. @@ -919,7 +926,8 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) net = gether_connect(&ncm->port); if (IS_ERR(net)) return PTR_ERR(net); - ncm->netdev = net; + scoped_guard(mutex, &opts->lock) + opts->net = net; } spin_lock(&ncm->lock); @@ -1366,14 +1374,16 @@ static int ncm_unwrap_ntb(struct gether *port, static void ncm_disable(struct usb_function *f) { struct f_ncm *ncm = func_to_ncm(f); + struct f_ncm_opts *opts = func_to_ncm_opts(f); struct usb_composite_dev *cdev = f->config->cdev; DBG(cdev, "ncm deactivated\n"); - if (ncm->netdev) { - ncm->netdev = NULL; - gether_disconnect(&ncm->port); - } + scoped_guard(mutex, &opts->lock) + if (opts->net) { + opts->net = NULL; + gether_disconnect(&ncm->port); + } if (ncm->notify->enabled) { usb_ep_disable(ncm->notify); @@ -1433,39 +1443,44 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_ncm *ncm = func_to_ncm(f); + struct f_ncm_opts *ncm_opts = func_to_ncm_opts(f); struct usb_string *us; int status = 0; struct usb_ep *ep; - struct f_ncm_opts *ncm_opts; struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; + struct net_device *netdev __free(free_gether_netdev) = NULL; struct usb_request *request __free(free_usb_request) = NULL; if (!can_support_ecm(cdev->gadget)) return -EINVAL; - ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); - if (cdev->use_os_string) { os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL); if (!os_desc_table) return -ENOMEM; } - mutex_lock(&ncm_opts->lock); - gether_set_gadget(ncm_opts->net, cdev->gadget); - if (!ncm_opts->bound) { - ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN); - status = gether_register_netdev(ncm_opts->net); + netdev = gether_setup_default(); + if (IS_ERR(netdev)) + return -ENOMEM; + + scoped_guard(mutex, &ncm_opts->lock) { + gether_apply_opts(netdev, &ncm_opts->net_opts); + netdev->mtu = ncm_opts->max_segment_size - ETH_HLEN; } - mutex_unlock(&ncm_opts->lock); + gether_set_gadget(netdev, cdev->gadget); + status = gether_register_netdev(netdev); if (status) return status; - ncm_opts->bound = true; - - ncm_string_defs[1].s = ncm->ethaddr; + /* export host's Ethernet address in CDC format */ + status = gether_get_host_addr_cdc(netdev, ncm->ethaddr, + sizeof(ncm->ethaddr)); + if (status < 12) + return -EINVAL; + ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr; us = usb_gstrings_attach(cdev, ncm_strings, ARRAY_SIZE(ncm_string_defs)); @@ -1563,6 +1578,8 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) f->os_desc_n = 1; } ncm->notify_req = no_free_ptr(request); + ncm->netdev = no_free_ptr(netdev); + ncm->port.ioport = netdev_priv(ncm->netdev); DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n", ncm->port.in_ep->name, ncm->port.out_ep->name, @@ -1577,19 +1594,19 @@ static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item) } /* f_ncm_item_ops */ -USB_ETHERNET_CONFIGFS_ITEM(ncm); +USB_ETHER_OPTS_ITEM(ncm); /* f_ncm_opts_dev_addr */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ncm); +USB_ETHER_OPTS_ATTR_DEV_ADDR(ncm); /* f_ncm_opts_host_addr */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ncm); +USB_ETHER_OPTS_ATTR_HOST_ADDR(ncm); /* f_ncm_opts_qmult */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm); +USB_ETHER_OPTS_ATTR_QMULT(ncm); /* f_ncm_opts_ifname */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm); +USB_ETHER_OPTS_ATTR_IFNAME(ncm); static ssize_t ncm_opts_max_segment_size_show(struct config_item *item, char *page) @@ -1655,34 +1672,27 @@ static void ncm_free_inst(struct usb_function_instance *f) struct f_ncm_opts *opts; opts = container_of(f, struct f_ncm_opts, func_inst); - if (opts->bound) - gether_cleanup(netdev_priv(opts->net)); - else - free_netdev(opts->net); kfree(opts->ncm_interf_group); kfree(opts); } static struct usb_function_instance *ncm_alloc_inst(void) { - struct f_ncm_opts *opts; + struct usb_function_instance *ret; struct usb_os_desc *descs[1]; char *names[1]; struct config_group *ncm_interf_group; - opts = kzalloc(sizeof(*opts), GFP_KERNEL); + struct f_ncm_opts *opts __free(kfree) = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return ERR_PTR(-ENOMEM); + + opts->net = NULL; opts->ncm_os_desc.ext_compat_id = opts->ncm_ext_compat_id; + gether_setup_opts_default(&opts->net_opts, "usb"); mutex_init(&opts->lock); opts->func_inst.free_func_inst = ncm_free_inst; - opts->net = gether_setup_default(); - if (IS_ERR(opts->net)) { - struct net_device *net = opts->net; - kfree(opts); - return ERR_CAST(net); - } opts->max_segment_size = ETH_FRAME_LEN; INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop); @@ -1693,26 +1703,22 @@ static struct usb_function_instance *ncm_alloc_inst(void) ncm_interf_group = usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs, names, THIS_MODULE); - if (IS_ERR(ncm_interf_group)) { - ncm_free_inst(&opts->func_inst); + if (IS_ERR(ncm_interf_group)) return ERR_CAST(ncm_interf_group); - } opts->ncm_interf_group = ncm_interf_group; - return &opts->func_inst; + ret = &opts->func_inst; + retain_and_null_ptr(opts); + return ret; } static void ncm_free(struct usb_function *f) { - struct f_ncm *ncm; - struct f_ncm_opts *opts; + struct f_ncm_opts *opts = func_to_ncm_opts(f); - ncm = func_to_ncm(f); - opts = container_of(f->fi, struct f_ncm_opts, func_inst); - kfree(ncm); - mutex_lock(&opts->lock); - opts->refcnt--; - mutex_unlock(&opts->lock); + scoped_guard(mutex, &opts->lock) + opts->refcnt--; + kfree(func_to_ncm(f)); } static void ncm_unbind(struct usb_configuration *c, struct usb_function *f) @@ -1736,13 +1742,15 @@ static void ncm_unbind(struct usb_configuration *c, struct usb_function *f) kfree(ncm->notify_req->buf); usb_ep_free_request(ncm->notify, ncm->notify_req); + + ncm->port.ioport = NULL; + gether_cleanup(netdev_priv(ncm->netdev)); } static struct usb_function *ncm_alloc(struct usb_function_instance *fi) { struct f_ncm *ncm; struct f_ncm_opts *opts; - int status; /* allocate and initialize one new instance */ ncm = kzalloc(sizeof(*ncm), GFP_KERNEL); @@ -1750,22 +1758,12 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) return ERR_PTR(-ENOMEM); opts = container_of(fi, struct f_ncm_opts, func_inst); - mutex_lock(&opts->lock); - opts->refcnt++; - /* export host's Ethernet address in CDC format */ - status = gether_get_host_addr_cdc(opts->net, ncm->ethaddr, - sizeof(ncm->ethaddr)); - if (status < 12) { /* strlen("01234567890a") */ - kfree(ncm); - mutex_unlock(&opts->lock); - return ERR_PTR(-EINVAL); - } + scoped_guard(mutex, &opts->lock) + opts->refcnt++; spin_lock_init(&ncm->lock); ncm_reset_values(ncm); - ncm->port.ioport = netdev_priv(opts->net); - mutex_unlock(&opts->lock); ncm->port.is_fixed = true; ncm->port.supports_multi_frame = true; diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/function/u_ncm.h index 49ec095cdb4b6..d99330fe31e88 100644 --- a/drivers/usb/gadget/function/u_ncm.h +++ b/drivers/usb/gadget/function/u_ncm.h @@ -15,11 +15,13 @@ #include +#include "u_ether.h" + struct f_ncm_opts { struct usb_function_instance func_inst; struct net_device *net; - bool bound; + struct gether_opts net_opts; struct config_group *ncm_interf_group; struct usb_os_desc ncm_os_desc; char ncm_ext_compat_id[16]; -- 2.51.0