From: Yu Zhang <yuz08559@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Neill Kapron <nkapron@google.com>
Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
Yu Zhang <yuz08559@gmail.com>
Subject: [PATCH] usb: gadget: f_ecm: serialize notification state with a spinlock
Date: Mon, 26 Oct 2026 16:00:31 +1000 [thread overview]
Message-ID: <20261026060031.60582-1-yuz08559@gmail.com> (raw)
ecm_do_notify() guards re-entry only with an atomic_read() of
notify_count, then fills ecm->notify_req, does atomic_inc() and
usb_ep_queue(). This check-fill-inc-queue sequence is not atomic and
runs from three contexts that share no lock: ecm_notify_complete(),
ecm_open()/ecm_close() (netdev up/down via u_ether) and ecm_set_alt().
When the completion handler decrements notify_count to 0 and re-enters
ecm_do_notify(), a concurrent ecm_close()/ecm_set_alt() can observe
notify_count == 0 in the window between the atomic_dec() and the next
atomic_inc(), and queue the same ecm->notify_req a second time. The
request is then added to the endpoint queue twice (list_add corruption),
and notify_state is updated concurrently.
struct f_ecm has carried a FIXME about this missing locking. The sibling
f_ncm already serializes the identical state machine with a per-instance
spinlock. Do the same in f_ecm: take ecm->lock across the is_open update
and ecm_notify() in open/close/set_alt and across the state update in
ecm_notify_complete(), dropping it only around usb_ep_queue().
Signed-off-by: Yu Zhang <yuz08559@gmail.com>
---
drivers/usb/gadget/function/f_ecm.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/drivers/usb/gadget/function/f_ecm.c b/drivers/usb/gadget/function/f_ecm.c
index e495bac..d699832 100644
--- a/drivers/usb/gadget/function/f_ecm.c
+++ b/drivers/usb/gadget/function/f_ecm.c
@@ -59,9 +59,8 @@ struct f_ecm {
atomic_t notify_count;
bool is_open;
- /* FIXME is_open needs some irq-ish locking
- * ... possibly the same as port.ioport
- */
+ /* protects the notification state machine */
+ spinlock_t lock;
};
static inline struct f_ecm *func_to_ecm(struct usb_function *f)
@@ -414,7 +413,11 @@ static void ecm_do_notify(struct f_ecm *ecm)
event->wIndex = cpu_to_le16(ecm->ctrl_id);
atomic_inc(&ecm->notify_count);
+
+ /* usb_ep_queue() can complete the request re-entrantly */
+ spin_unlock(&ecm->lock);
status = usb_ep_queue(ecm->notify, req, GFP_ATOMIC);
+ spin_lock(&ecm->lock);
if (status < 0) {
atomic_dec(&ecm->notify_count);
DBG(cdev, "notify --> %d\n", status);
@@ -438,6 +441,7 @@ static void ecm_notify_complete(struct usb_ep *ep, struct usb_request *req)
struct usb_composite_dev *cdev = ecm->port.func.config->cdev;
struct usb_cdc_notification *event = req->buf;
+ spin_lock(&ecm->lock);
switch (req->status) {
case 0:
/* no fault */
@@ -455,6 +459,7 @@ static void ecm_notify_complete(struct usb_ep *ep, struct usb_request *req)
break;
}
ecm_do_notify(ecm);
+ spin_unlock(&ecm->lock);
}
static int ecm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
@@ -588,7 +593,9 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
* follow another (if the first is in flight), and instead
* just guarantee that a speed notification is always sent.
*/
+ spin_lock(&ecm->lock);
ecm_notify(ecm);
+ spin_unlock(&ecm->lock);
} else
goto fail;
@@ -653,8 +660,10 @@ static void ecm_open(struct gether *geth)
DBG(ecm->port.func.config->cdev, "%s\n", __func__);
+ spin_lock(&ecm->lock);
ecm->is_open = true;
ecm_notify(ecm);
+ spin_unlock(&ecm->lock);
}
static void ecm_close(struct gether *geth)
@@ -663,8 +672,10 @@ static void ecm_close(struct gether *geth)
DBG(ecm->port.func.config->cdev, "%s\n", __func__);
+ spin_lock(&ecm->lock);
ecm->is_open = false;
ecm_notify(ecm);
+ spin_unlock(&ecm->lock);
}
/*-------------------------------------------------------------------------*/
@@ -942,6 +953,8 @@ static struct usb_function *ecm_alloc(struct usb_function_instance *fi)
if (!ecm)
return ERR_PTR(-ENOMEM);
+ spin_lock_init(&ecm->lock);
+
opts = container_of(fi, struct f_ecm_opts, func_inst);
mutex_lock(&opts->lock);
opts->refcnt++;
--
2.43.0
reply other threads:[~2026-10-26 6:00 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261026060031.60582-1-yuz08559@gmail.com \
--to=yuz08559@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=nkapron@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®