From nobody Sun Feb 8 21:33:44 2026 Received: from mail-pg1-f201.google.com (mail-pg1-f201.google.com [209.85.215.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F2E1E2D9EFF for ; Tue, 30 Dec 2025 10:13:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767089620; cv=none; b=AP4L10WCwBmhlgnONVwPXKaSU73nNb0DGxzszhdRsBC8TO3sSfT0nqKi6XA8sIXt59drm/rf+IYBd4Pyw6W/hap4qm1HbeqvT/s6AaBapvRydU9O/f7gMaoSS9CHWoanFEiMNmb4F1Y7+dcPt+UHylSDGcnZru3pFoSS7TINumg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767089620; c=relaxed/simple; bh=V+vuLtUzxOFOzho0gfd3w8V5fzTGbDa0Otz8inB6yew=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=SrjdJqSTyOtbIGS8jBZjsdtkVV2Rz6e7T9RlpYlfsmFmZGYDVWsMdUbU9ejYN39Rheg5U2EAFtZ/QOyEXzByOunasfHbBc/YMRsdnrUq+mMf2XRmVQVt6hhu3aASVNHxUb1hp/DUUl1Ft0a7OPBjhB+4n+5ZXNPoMZgpFxWS4IQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--khtsai.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=3Pf98mPe; arc=none smtp.client-ip=209.85.215.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--khtsai.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="3Pf98mPe" Received: by mail-pg1-f201.google.com with SMTP id 41be03b00d2f7-b6097ca315bso18209524a12.3 for ; Tue, 30 Dec 2025 02:13:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1767089617; x=1767694417; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=sOvss5Tw/9kk97Yc1J9wSFxhfjd/wCmusqywC0TPEkM=; b=3Pf98mPeG/KE8VSvxrx5J+K2dJlziVvvboFS/f9hm1Rpfz9wD4Q21ZF7a8/rXJjbme /ebSqIpMJjoiRAGd5bY2gXuOJ4lTIDJzBFIcLIiYgIwlhIV57ZEZVtQZgiUHEMw9py8Q aBqL68QKxyyTer1JLfUK+BQCr9zL9Rj27LSWBHT3aEjud7lARzDPYsLt/+ho7BQTbKNo KFb+KbwdH1roKkTz0T7uHpQgngSgo54tZuVvbw1Uqp+G2BbtGEa8A/h0pJIA1oHmkAkd hTn/sHciA21LtWhkB5jicTQT2m6GEeGjU3o1P3v9Ww/JFV5ajLoukUcp4q8MCyppLvN9 9Yyg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1767089617; x=1767694417; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=sOvss5Tw/9kk97Yc1J9wSFxhfjd/wCmusqywC0TPEkM=; b=B2glyadNpO5pv1jXqAyIpNMGEn9hKOr9H5TRDyBYbMwZR74CpMCsHv/HwBqfKXdHZH 6J4UV+E0ZxyVfEm6k3vqGjNKWELJZe2vr5QiO23IGCBCEADla+A2CONpHS/PHabnJfeZ 6PIfcGtBD8yL53J5z0C2JyDd6HEq+iML5VzcVspmao3s3FfbMqEsVeJk3xVeffRo+1Fr e2pgo1LlvIi7oGmLiBU6iTYN7JdXJFp4uSP3kS5YtJdP1CAusZIR1Sg4l4k0+nv+uxpX l/iDiqXk+FM+pdlY/Tp/GhChvg1jenOSoEglGUiYAAYGTSm1kD3JLxRpBlTx62l3nLN8 dO9w== X-Forwarded-Encrypted: i=1; AJvYcCXhH8Dm7uDwnj+kpxvfWaGPqPKaaZ4jNzvNHVr3htQQCm2wX81n9vTA+McSLCq5hoLWCbQPndD44Qmo+XQ=@vger.kernel.org X-Gm-Message-State: AOJu0YzJYScCPmWg/+5lhOstMxvleU2bhHPHQQ+Wbc+QUUPssT5UnJKS NHH0ytlrdlAmKJinU/xMyJ1Gmc8t67nW4Biw0G38zfUjGRUpU4eb9FN7jVWKrR+C5u4ueCF3hYP d0M/q+g== X-Google-Smtp-Source: AGHT+IHWCbgO2xBwg0SUc905QlZchBrMsGVFYn8nqZlrnIc+Q75J28SeBBgnAPGRmX6Nrlm8je82afclBfQ= X-Received: from pgtl20.prod.google.com ([2002:a65:6814:0:b0:c1d:27c6:5305]) (user=khtsai job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a21:e082:b0:366:14ac:e1f5 with SMTP id adf61e73a8af0-376aa1e7fc4mr27535714637.71.1767089617218; Tue, 30 Dec 2025 02:13:37 -0800 (PST) Date: Tue, 30 Dec 2025 18:13:16 +0800 In-Reply-To: <20251230-ncm-refactor-v1-0-793e347bc7a7@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20251230-ncm-refactor-v1-0-793e347bc7a7@google.com> X-Developer-Key: i=khtsai@google.com; a=ed25519; pk=abA4Pw6dY2ZufSbSXW9mtp7xiv1AVPtgRhCFWJSEqLE= X-Developer-Signature: v=1; a=ed25519-sha256; t=1767089609; l=11983; i=khtsai@google.com; s=20250916; h=from:subject:message-id; bh=V+vuLtUzxOFOzho0gfd3w8V5fzTGbDa0Otz8inB6yew=; b=a8R520vrkG2qLB3+yBkY+hsTafAd14WNa4cpvDgqj818dX5ZNwiedJtZJgXXrswF5iWrSABzZ ubX2Z7B2EPNCxBbH0h4RIqc6vkRQ4IHg76/GoxLG8o6u3fxu2gmF8ys X-Mailer: b4 0.14.2 Message-ID: <20251230-ncm-refactor-v1-3-793e347bc7a7@google.com> Subject: [PATCH 3/3] usb: gadget: f_ncm: align net_device lifecycle with bind/unbind From: Kuen-Han Tsai To: Greg Kroah-Hartman , Felipe Balbi , Prashanth K , Kyungmin Park , Andrzej Pietrasiewicz Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Kuen-Han Tsai , stable@kernel.org Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 Tested-by: Ernest Van Hoecke # Aquila iMX95 --- 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/funct= ion/f_ncm.c index 0e38330271d5ac40f7f46b7e8d565c1f0d41e4b8..e23adc132f8865f6bbce6c88c8b= 5f3f06110faaa 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_funct= ion *f) return container_of(f, struct f_ncm, port.func); } =20 +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); +} + /*------------------------------------------------------------------------= -*/ =20 /* @@ -859,6 +864,7 @@ static int ncm_setup(struct usb_function *f, const stru= ct usb_ctrlrequest *ctrl) static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_ncm *ncm =3D func_to_ncm(f); + struct f_ncm_opts *opts =3D func_to_ncm_opts(f); struct usb_composite_dev *cdev =3D f->config->cdev; =20 /* Control interface has only altsetting 0 */ @@ -881,12 +887,13 @@ static int ncm_set_alt(struct usb_function *f, unsign= ed intf, unsigned alt) if (alt > 1) goto fail; =20 - if (ncm->netdev) { - DBG(cdev, "reset ncm\n"); - ncm->netdev =3D NULL; - gether_disconnect(&ncm->port); - ncm_reset_values(ncm); - } + scoped_guard(mutex, &opts->lock) + if (opts->net) { + DBG(cdev, "reset ncm\n"); + opts->net =3D NULL; + gether_disconnect(&ncm->port); + ncm_reset_values(ncm); + } =20 /* * 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 =3D gether_connect(&ncm->port); if (IS_ERR(net)) return PTR_ERR(net); - ncm->netdev =3D net; + scoped_guard(mutex, &opts->lock) + opts->net =3D net; } =20 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 =3D func_to_ncm(f); + struct f_ncm_opts *opts =3D func_to_ncm_opts(f); struct usb_composite_dev *cdev =3D f->config->cdev; =20 DBG(cdev, "ncm deactivated\n"); =20 - if (ncm->netdev) { - ncm->netdev =3D NULL; - gether_disconnect(&ncm->port); - } + scoped_guard(mutex, &opts->lock) + if (opts->net) { + opts->net =3D NULL; + gether_disconnect(&ncm->port); + } =20 if (ncm->notify->enabled) { usb_ep_disable(ncm->notify); @@ -1433,39 +1443,44 @@ static int ncm_bind(struct usb_configuration *c, st= ruct usb_function *f) { struct usb_composite_dev *cdev =3D c->cdev; struct f_ncm *ncm =3D func_to_ncm(f); + struct f_ncm_opts *ncm_opts =3D func_to_ncm_opts(f); struct usb_string *us; int status =3D 0; struct usb_ep *ep; - struct f_ncm_opts *ncm_opts; =20 struct usb_os_desc_table *os_desc_table __free(kfree) =3D NULL; + struct net_device *netdev __free(free_gether_netdev) =3D NULL; struct usb_request *request __free(free_usb_request) =3D NULL; =20 if (!can_support_ecm(cdev->gadget)) return -EINVAL; =20 - ncm_opts =3D container_of(f->fi, struct f_ncm_opts, func_inst); - if (cdev->use_os_string) { os_desc_table =3D kzalloc(sizeof(*os_desc_table), GFP_KERNEL); if (!os_desc_table) return -ENOMEM; } =20 - mutex_lock(&ncm_opts->lock); - gether_set_gadget(ncm_opts->net, cdev->gadget); - if (!ncm_opts->bound) { - ncm_opts->net->mtu =3D (ncm_opts->max_segment_size - ETH_HLEN); - status =3D gether_register_netdev(ncm_opts->net); + netdev =3D 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 =3D ncm_opts->max_segment_size - ETH_HLEN; } - mutex_unlock(&ncm_opts->lock); =20 + gether_set_gadget(netdev, cdev->gadget); + status =3D gether_register_netdev(netdev); if (status) return status; =20 - ncm_opts->bound =3D true; - - ncm_string_defs[1].s =3D ncm->ethaddr; + /* export host's Ethernet address in CDC format */ + status =3D gether_get_host_addr_cdc(netdev, ncm->ethaddr, + sizeof(ncm->ethaddr)); + if (status < 12) + return -EINVAL; + ncm_string_defs[STRING_MAC_IDX].s =3D ncm->ethaddr; =20 us =3D usb_gstrings_attach(cdev, ncm_strings, ARRAY_SIZE(ncm_string_defs)); @@ -1563,6 +1578,8 @@ static int ncm_bind(struct usb_configuration *c, stru= ct usb_function *f) f->os_desc_n =3D 1; } ncm->notify_req =3D no_free_ptr(request); + ncm->netdev =3D no_free_ptr(netdev); + ncm->port.ioport =3D netdev_priv(ncm->netdev); =20 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(stru= ct config_item *item) } =20 /* f_ncm_item_ops */ -USB_ETHERNET_CONFIGFS_ITEM(ncm); +USB_ETHER_OPTS_ITEM(ncm); =20 /* f_ncm_opts_dev_addr */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ncm); +USB_ETHER_OPTS_ATTR_DEV_ADDR(ncm); =20 /* f_ncm_opts_host_addr */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ncm); +USB_ETHER_OPTS_ATTR_HOST_ADDR(ncm); =20 /* f_ncm_opts_qmult */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm); +USB_ETHER_OPTS_ATTR_QMULT(ncm); =20 /* f_ncm_opts_ifname */ -USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm); +USB_ETHER_OPTS_ATTR_IFNAME(ncm); =20 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_insta= nce *f) struct f_ncm_opts *opts; =20 opts =3D 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); } =20 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; =20 - opts =3D kzalloc(sizeof(*opts), GFP_KERNEL); + struct f_ncm_opts *opts __free(kfree) =3D kzalloc(sizeof(*opts), GFP_KERN= EL); if (!opts) return ERR_PTR(-ENOMEM); + + opts->net =3D NULL; opts->ncm_os_desc.ext_compat_id =3D opts->ncm_ext_compat_id; + gether_setup_opts_default(&opts->net_opts, "usb"); =20 mutex_init(&opts->lock); opts->func_inst.free_func_inst =3D ncm_free_inst; - opts->net =3D gether_setup_default(); - if (IS_ERR(opts->net)) { - struct net_device *net =3D opts->net; - kfree(opts); - return ERR_CAST(net); - } opts->max_segment_size =3D ETH_FRAME_LEN; INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop); =20 @@ -1693,26 +1703,22 @@ static struct usb_function_instance *ncm_alloc_inst= (void) ncm_interf_group =3D 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 =3D ncm_interf_group; =20 - return &opts->func_inst; + ret =3D &opts->func_inst; + retain_and_null_ptr(opts); + return ret; } =20 static void ncm_free(struct usb_function *f) { - struct f_ncm *ncm; - struct f_ncm_opts *opts; + struct f_ncm_opts *opts =3D func_to_ncm_opts(f); =20 - ncm =3D func_to_ncm(f); - opts =3D 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)); } =20 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) =20 kfree(ncm->notify_req->buf); usb_ep_free_request(ncm->notify, ncm->notify_req); + + ncm->port.ioport =3D NULL; + gether_cleanup(netdev_priv(ncm->netdev)); } =20 static struct usb_function *ncm_alloc(struct usb_function_instance *fi) { struct f_ncm *ncm; struct f_ncm_opts *opts; - int status; =20 /* allocate and initialize one new instance */ ncm =3D kzalloc(sizeof(*ncm), GFP_KERNEL); @@ -1750,22 +1758,12 @@ static struct usb_function *ncm_alloc(struct usb_fu= nction_instance *fi) return ERR_PTR(-ENOMEM); =20 opts =3D container_of(fi, struct f_ncm_opts, func_inst); - mutex_lock(&opts->lock); - opts->refcnt++; =20 - /* export host's Ethernet address in CDC format */ - status =3D 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++; =20 spin_lock_init(&ncm->lock); ncm_reset_values(ncm); - ncm->port.ioport =3D netdev_priv(opts->net); - mutex_unlock(&opts->lock); ncm->port.is_fixed =3D true; ncm->port.supports_multi_frame =3D true; =20 diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/funct= ion/u_ncm.h index 49ec095cdb4b6dcb330fd3149b502840312f78ec..d99330fe31e880f636615774d21= 2062952c31e43 100644 --- a/drivers/usb/gadget/function/u_ncm.h +++ b/drivers/usb/gadget/function/u_ncm.h @@ -15,11 +15,13 @@ =20 #include =20 +#include "u_ether.h" + struct f_ncm_opts { struct usb_function_instance func_inst; struct net_device *net; - bool bound; =20 + struct gether_opts net_opts; struct config_group *ncm_interf_group; struct usb_os_desc ncm_os_desc; char ncm_ext_compat_id[16]; --=20 2.52.0.351.gbe84eed79e-goog