* [PATCH v2 0/2] usb: gadget: u_serial: flush_buffer, and keep the port for its tty @ 2026-10-19 8:58 Mehmet Fide 2026-10-19 9:09 ` [PATCH v2 1/2] usb: gadget: u_serial: keep the port until its tty is released Mehmet Fide 2026-10-19 9:09 ` [PATCH v2 2/2] usb: gadget: u_serial: implement flush_buffer Mehmet Fide 0 siblings, 2 replies; 5+ messages in thread From: Mehmet Fide @ 2026-10-19 8:58 UTC (permalink / raw) To: Greg Kroah-Hartman Cc: Neill Kapron, Sebastian Andrzej Siewior, Heikki Krogerus, Wayne Chang, Jon Hunter, Felipe Balbi, Felipe Balbi, linux-usb, linux-kernel, mehmet.fide From: Mehmet Fide <mehmet.fide@screeningeagle.com> On a gadget whose host side does not read the port, the last close of /dev/ttyGS* waits GS_CLOSE_TIMEOUT (15 s) for the write buffer to drain, and tcflush(TCOFLUSH) cannot avoid that because u_serial has no flush_buffer operation. v1 added it. Sashiko pointed out that the hangup gserial_disconnect() schedules can then call flush_buffer after gserial_free_line() has freed the port. That window is older than the new callback: the tty core already reads the freed port there, KASAN reports it in check_tty_count() with a delay added to gs_close(). Patch 1 makes the port live as long as its tty, with a tty_port reference taken in install and dropped in cleanup as cdc-acm does, and carries Fixes and Cc stable. Patch 2 is v1. Changes in v2: - new patch 1, the port lifetime fix - patch 2 unchanged v1: https://lore.kernel.org/linux-usb/20261015162732.3170938-1-mehmet.fide@gmail.com/ Tested on a Verdin AM62 with KASAN and lockdep, 6.12.113 plus both patches: with a 3 s delay in gs_close() the hangup after gserial_free_line() no longer reports a use-after-free and the port is freed after the tty; four function teardowns, two with the port open, free four ports without leaks or warnings; close after tcflush returns at once with the host not reading and the data written after the flush arrives; 47850 flushes against a concurrent writer; NCM torn down under iperf traffic. The same two patches run in our i.MX8MP and VF50 product images on CI rigs, gadget replug included. On usb-next both patches build with W=1 and sparse without new warnings, each on its own. Mehmet Fide (2): usb: gadget: u_serial: keep the port until its tty is released usb: gadget: u_serial: implement flush_buffer drivers/usb/gadget/function/u_serial.c | 59 +++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) base-commit: 04ac7b445880b0878c83e6e89d961455cbea0427 -- 2.55.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/2] usb: gadget: u_serial: keep the port until its tty is released 2026-10-19 8:58 [PATCH v2 0/2] usb: gadget: u_serial: flush_buffer, and keep the port for its tty Mehmet Fide @ 2026-10-19 9:09 ` Mehmet Fide 2026-10-19 9:23 ` sashiko-bot 2026-10-19 9:09 ` [PATCH v2 2/2] usb: gadget: u_serial: implement flush_buffer Mehmet Fide 1 sibling, 1 reply; 5+ messages in thread From: Mehmet Fide @ 2026-10-19 9:09 UTC (permalink / raw) To: Greg Kroah-Hartman Cc: Neill Kapron, Sebastian Andrzej Siewior, Heikki Krogerus, Wayne Chang, Jon Hunter, Felipe Balbi, Felipe Balbi, linux-usb, linux-kernel, mehmet.fide From: Mehmet Fide <mehmet.fide@screeningeagle.com> gserial_free_line() frees the gs_port, and with it the tty_port it embeds, as soon as the last close has returned. The tty core keeps using tty->port until release_tty(), so a hangup that gserial_disconnect() scheduled before that close can still run and read the freed port: BUG: KASAN: slab-use-after-free in check_tty_count+0x230/0x278 Workqueue: events do_tty_hangup __tty_hangup.part.0 do_tty_hangup Allocated by: gserial_alloc_line_no_console <- acm_alloc_instance Freed by: gserial_free_port <- gserial_free_line <- acm_free_instance and release_tty() then cancels the buffer work of the freed port. Take a reference on the tty_port in install and drop it in cleanup, and let the last reference free the gs_port. Fixes: 19b10a8828a6 ("usb: gadget: allocate & giveback serial ports instead hard code them") Cc: stable@vger.kernel.org Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com> --- Notes: Changes in v2: - new patch: Sashiko pointed out that with flush_buffer the hangup that gserial_disconnect() schedules can run after gserial_free_line() has freed the port; the tty core itself already reads the freed port in that window (check_tty_count, release_tty), so the port has to live as long as its tty drivers/usb/gadget/function/u_serial.c | 45 ++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index cdd1dfc666c4..19eea724280b 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -908,7 +908,39 @@ static int gs_get_icount(struct tty_struct *tty, return 0; } +static int gs_install(struct tty_driver *driver, struct tty_struct *tty) +{ + int port_num = tty->index; + struct gs_port *port; + int ret; + + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + if (port) + tty_port_get(&port->port); + mutex_unlock(&ports[port_num].lock); + if (!port) + return -ENODEV; + + ret = tty_port_install(&port->port, driver, tty); + if (ret) { + tty_port_put(&port->port); + return ret; + } + tty->driver_data = port; + return 0; +} + +static void gs_cleanup(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + + tty_port_put(&port->port); +} + static const struct tty_operations gs_tty_ops = { + .install = gs_install, + .cleanup = gs_cleanup, .open = gs_open, .close = gs_close, .write = gs_write, @@ -1203,6 +1235,15 @@ static void gs_console_exit(struct gs_port *port) #endif +static void gs_port_destruct(struct tty_port *tport) +{ + kfree(container_of(tport, struct gs_port, port)); +} + +static const struct tty_port_operations gs_port_ops = { + .destruct = gs_port_destruct, +}; + static int gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) { @@ -1222,6 +1263,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) } tty_port_init(&port->port); + port->port.ops = &gs_port_ops; spin_lock_init(&port->port_lock); init_waitqueue_head(&port->drain_wait); init_waitqueue_head(&port->close_wait); @@ -1258,8 +1300,7 @@ static void gserial_free_port(struct gs_port *port) /* wait for old opens to finish */ wait_event(port->close_wait, gs_closed(port)); WARN_ON(port->port_usb != NULL); - tty_port_destroy(&port->port); - kfree(port); + tty_port_put(&port->port); } void gserial_free_line(unsigned char port_num) -- 2.55.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] usb: gadget: u_serial: keep the port until its tty is released 2026-10-19 9:09 ` [PATCH v2 1/2] usb: gadget: u_serial: keep the port until its tty is released Mehmet Fide @ 2026-10-19 9:23 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-10-19 9:23 UTC (permalink / raw) To: Mehmet Fide; +Cc: linux-usb Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] usb: gadget: u_serial: port reallocation race between gs_install() and gs_open() - [High] usb: gadget: u_serial: unbalanced port count in gs_close() when gs_open() fails -- commit 80163fbde5c8a63d71508c20f74ff07e66b93936 Author: Mehmet Fide <mehmet.fide@screeningeagle.com> usb: gadget: u_serial: keep the port until its tty is released This commit adds install and cleanup callbacks to gs_tty_ops to take a reference on tty_port during tty installation and release the gs_port via a tty_port destructor when the last reference is dropped. > diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c > index cdd1dfc666c4f..19eea724280b0 100644 > --- a/drivers/usb/gadget/function/u_serial.c > +++ b/drivers/usb/gadget/function/u_serial.c > @@ -908,7 +908,39 @@ static int gs_get_icount(struct tty_struct *tty, > return 0; > } > > +static int gs_install(struct tty_driver *driver, struct tty_struct *tty) > +{ > + int port_num = tty->index; > + struct gs_port *port; > + int ret; > + > + mutex_lock(&ports[port_num].lock); > + port = ports[port_num].port; > + if (port) > + tty_port_get(&port->port); > + mutex_unlock(&ports[port_num].lock); > + if (!port) > + return -ENODEV; > + > + ret = tty_port_install(&port->port, driver, tty); > + if (ret) { > + tty_port_put(&port->port); > + return ret; > + } > + tty->driver_data = port; > + return 0; > +} [Severity: High] Does setting tty->driver_data in gs_install() cause an unbalanced port->port.count decrement in gs_close() when gs_open() fails? When gs_open() returns a non-zero error, tty_open() cleans up by calling tty_release() -> gs_close(). In gs_open(), the function can fail and return -ENODEV or -ENOMEM before incrementing port->port.count: drivers/usb/gadget/function/u_serial.c:gs_open() { mutex_lock(&ports[port_num].lock); port = ports[port_num].port; if (!port) { status = -ENODEV; goto out; } spin_lock_irq(&port->port_lock); /* allocate circular buffer on first open */ if (!kfifo_initialized(&port->port_write_buf)) { spin_unlock_irq(&port->port_lock); status = kfifo_alloc(&port->port_write_buf, WRITE_BUF_SIZE, GFP_KERNEL); if (status) { ... goto out; } spin_lock_irq(&port->port_lock); } /* already open? Great. */ if (port->port.count++) goto exit_unlock_port; } Because gs_install() already set tty->driver_data = port, gs_close() runs with a valid port pointer even though gs_open() never incremented port->port.count: drivers/usb/gadget/function/u_serial.c:gs_close() { struct gs_port *port = tty->driver_data; ... spin_lock_irq(&port->port_lock); if (port->port.count != 1) { raced_with_open: if (port->port.count == 0) WARN_ON(1); else --port->port.count; goto exit; } } On a failed first open where port->port.count is 0, gs_close() hits WARN_ON(1). Furthermore, if a process already holds the tty open (port->port.count == 1) and gserial_free_line() has cleared ports[port_num].port = NULL while waiting in gserial_free_port() at wait_event(port->close_wait, gs_closed(port)), a second process reopening the existing tty via tty_reopen() -> gs_open() sees ports[port_num].port == NULL and returns -ENODEV. The resulting tty_release() -> gs_close() sees port->port.count == 1 from the first opener and runs final-close teardown on the active port, resetting port->port_write_buf, clearing port->port.tty = NULL, setting port->port.count = 0, and waking port->close_wait while the first opener still has the tty open. > + > +static void gs_cleanup(struct tty_struct *tty) > +{ > + struct gs_port *port = tty->driver_data; > + > + tty_port_put(&port->port); > +} [Severity: High] Can a port free and reallocation between gs_install() and gs_open() cause gs_cleanup() to free a newly allocated struct gs_port while leaking the originally installed struct gs_port? On first open, tty_open() -> tty_open_by_driver() -> tty_init_dev() calls gs_install(), which locks ports[port_num].lock, takes a reference on the current port (port1), unlocks ports[port_num].lock, binds tty->port to &port1->port, and sets tty->driver_data = port1. Before tty_open() invokes gs_open(), neither ports[port_num].lock nor tty_mutex is held, and port1->port.count is still 0. If gserial_free_line() runs in this window, wait_event(port->close_wait, gs_closed(port)) in gserial_free_port() returns immediately because port1->port.count is 0, and gserial_free_port() drops port1's initial reference. If the line is then reallocated via gs_port_alloc(), a new struct gs_port (port2) is stored in ports[port_num].port with kref == 1. When gs_open() then executes, it re-reads ports[port_num].port instead of using tty->driver_data: drivers/usb/gadget/function/u_serial.c:gs_open() { mutex_lock(&ports[port_num].lock); port = ports[port_num].port; ... if (port->port.count++) goto exit_unlock_port; tty->driver_data = port; port->port.tty = tty; } This overwrites tty->driver_data with port2 without taking a tty_port reference on &port2->port, while tty->port remains &port1->port (and port2->port.itty remains NULL). When the tty is later released, gs_cleanup() calls tty_port_put(&port->port) on tty->driver_data (port2), dropping port2's sole reference and freeing port2 via gs_port_destruct() while ports[port_num].port still points to it, while port1's reference from gs_install() is never dropped. [ ... ] > @@ -1258,8 +1300,7 @@ static void gserial_free_port(struct gs_port *port) > /* wait for old opens to finish */ > wait_event(port->close_wait, gs_closed(port)); > WARN_ON(port->port_usb != NULL); > - tty_port_destroy(&port->port); > - kfree(port); > + tty_port_put(&port->port); > } > > void gserial_free_line(unsigned char port_num) -- Sashiko AI review · https://sashiko.dev/#/patchset/20261019085855.3259716-1-mehmet.fide@gmail.com?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] usb: gadget: u_serial: implement flush_buffer 2026-10-19 8:58 [PATCH v2 0/2] usb: gadget: u_serial: flush_buffer, and keep the port for its tty Mehmet Fide 2026-10-19 9:09 ` [PATCH v2 1/2] usb: gadget: u_serial: keep the port until its tty is released Mehmet Fide @ 2026-10-19 9:09 ` Mehmet Fide 2026-10-19 9:15 ` sashiko-bot 1 sibling, 1 reply; 5+ messages in thread From: Mehmet Fide @ 2026-10-19 9:09 UTC (permalink / raw) To: Greg Kroah-Hartman Cc: Neill Kapron, Sebastian Andrzej Siewior, Heikki Krogerus, Wayne Chang, Jon Hunter, Felipe Balbi, Felipe Balbi, linux-usb, linux-kernel, mehmet.fide From: Mehmet Fide <mehmet.fide@screeningeagle.com> gs_close() waits up to GS_CLOSE_TIMEOUT (15 s) for the write buffer to drain while the gadget is connected. When the host does not read the port, for example because no application on the host has it open, the last close blocks for the full timeout. tcflush(TCOFLUSH) cannot avoid that: u_serial has no flush_buffer operation, so the request is a no-op. Implement flush_buffer: discard the buffered output under the port lock and wake up the close and write waiters. As on other tty drivers, a hangup and an interrupt character (ISIG without NOFLSH) now discard the pending output as well. Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com> --- Notes: Changes in v2: - none, now after the patch that keeps the port alive for the hangup drivers/usb/gadget/function/u_serial.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c index 19eea724280b..187a2598181a 100644 --- a/drivers/usb/gadget/function/u_serial.c +++ b/drivers/usb/gadget/function/u_serial.c @@ -822,6 +822,19 @@ static void gs_flush_chars(struct tty_struct *tty) spin_unlock_irqrestore(&port->port_lock, flags); } +static void gs_flush_buffer(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + kfifo_reset(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + wake_up_interruptible(&port->drain_wait); + tty_wakeup(tty); +} + static unsigned int gs_write_room(struct tty_struct *tty) { struct gs_port *port = tty->driver_data; @@ -946,6 +959,7 @@ static const struct tty_operations gs_tty_ops = { .write = gs_write, .put_char = gs_put_char, .flush_chars = gs_flush_chars, + .flush_buffer = gs_flush_buffer, .write_room = gs_write_room, .chars_in_buffer = gs_chars_in_buffer, .unthrottle = gs_unthrottle, -- 2.55.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] usb: gadget: u_serial: implement flush_buffer 2026-10-19 9:09 ` [PATCH v2 2/2] usb: gadget: u_serial: implement flush_buffer Mehmet Fide @ 2026-10-19 9:15 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-10-19 9:15 UTC (permalink / raw) To: Mehmet Fide; +Cc: linux-usb > From: Mehmet Fide <mehmet.fide@screeningeagle.com> > > gs_close() waits up to GS_CLOSE_TIMEOUT (15 s) for the write buffer > to drain while the gadget is connected. When the host does not read > the port, for example because no application on the host has it open, > the last close blocks for the full timeout. tcflush(TCOFLUSH) cannot > avoid that: u_serial has no flush_buffer operation, so the request is > a no-op. > > Implement flush_buffer: discard the buffered output under the port > lock and wake up the close and write waiters. As on other tty drivers, > a hangup and an interrupt character (ISIG without NOFLSH) now discard > the pending output as well. > > Signed-off-by: Mehmet Fide <mehmet.fide@screeningeagle.com> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261019085855.3259716-1-mehmet.fide@gmail.com?part=2 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-19 9:23 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-19 8:58 [PATCH v2 0/2] usb: gadget: u_serial: flush_buffer, and keep the port for its tty Mehmet Fide 2026-10-19 9:09 ` [PATCH v2 1/2] usb: gadget: u_serial: keep the port until its tty is released Mehmet Fide 2026-10-19 9:23 ` sashiko-bot 2026-10-19 9:09 ` [PATCH v2 2/2] usb: gadget: u_serial: implement flush_buffer Mehmet Fide 2026-10-19 9:15 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.