Archive-only list for syzbot
 help / color / mirror / Atom feed
From: Krystian Kaniewski <krystianmkaniewski@gmail.com>
To: syzbot <syzbot@kernel.org>,
	syzkaller-upstream-moderation@googlegroups.com
Cc: syzbot@lists.linux.dev
Subject: Re: [PATCH RFC] usb: gadget: u_serial: Fix use-after-free in release_tty
Date: Thu, 10 Sep 2026 12:20:55 +0200	[thread overview]
Message-ID: <64df2b77-52a4-4c1e-a40f-2abebba5b080@gmail.com> (raw)
In-Reply-To: <acde99b9-2954-43ab-8403-6ca10699472d@mail.kernel.org>

Keep the install-to-cleanup `tty_port` reference, the matching cleanup 
put, destructor-based final release, and the existing report, fix, 
discussion, and assistance tags. Apply all corrections below to complete 
the lifetime fix.

Bind one `tty_struct` for its installed lifetime to the exact `gs_port` 
selected during install. In `gs_install()`, hold the indexed `ports[]` 
mutex while selecting the public port, acquiring its `tty_port` 
reference, calling `tty_port_install()`, and assigning that port to 
`tty->driver_data`. Drop the acquired reference if installation fails. 
In `gs_open()`, use only `tty->driver_data`, reject a missing object 
with `-ENODEV`, and under the same indexed mutex reject the open unless 
the public slot still contains that exact object. Do not perform a fresh 
indexed lookup or overwrite `driver_data` during open.

Account explicitly for TTY core calling `.close` after a failed driver 
`.open`. Add a per-port failed-open counter protected by the TTY lock. 
Increment it for an open failure that did not increment the gadget 
serial open count. At the start of `gs_close()`, consume a pending 
failed-open entry and return without changing any successful open count. 
This must also handle a failed reopen while another file keeps the 
successful count positive. After that check, restore the original 
`port->port.count != 1` and `raced_with_open` handling for successful 
opens, including the zero-count warning. Do not retain the silent 
zero-count exits introduced by the current patch.

Prevent replacement registration from reusing a minor until old TTY 
cleanup is complete. Add reservation state beside each public `ports[]` 
pointer and protect both fields with the existing indexed mutex. Model 
the slot as free when no port is present and the minor is not reserved, 
published when both are set, and retiring when the public pointer is 
clear but the minor remains reserved. Make allocation test the 
reservation, set it when publishing a new port, and return `-EBUSY` 
while a prior generation is retiring.

Release that reservation only from the final `tty_port` destructor. 
Under the indexed mutex, warn and clear the slot if a public port 
unexpectedly remains, warn if the minor is not reserved, clear the 
reservation, then free the FIFO and the port. On device-registration 
failure, clear only the public pointer and drop the owner reference so 
the destructor releases the reservation. During normal line removal, 
clear public availability first, call `tty_unregister_device()` while 
retaining the owner reference, and drop that owner reference only after 
unregister completes. This ordering must prevent 
`tty_register_device_attr()` from freeing `tty_driver.termios[index]` 
while old release code is still writing it in `tty_save_termios()`.

Update the lifecycle comments to match the resulting implementation. 
State that `tty->driver_data` refers to `gs_port` from install through 
cleanup, while `gs_port->port.tty` exists only while open. Document the 
TTY-lock protection and purpose of the failed-open counter, the three 
valid public-slot and reservation states, the mutex ownership of both 
slot fields, the install-to-cleanup pin, and the reason the owner 
reference is retained through device unregister. Remove the stale 
wording that describes `driver_data` as `gserial` or says both TTY links 
exist only while open.

Use the subject `usb: gadget: u_serial: pin ports while TTYs are 
installed`. Update the commit message to explain both uncovered lifetime 
intervals, the installed-object identity requirement, failed-open close 
pairing, and delayed minor reuse protecting saved termios. Preserve 
ordinary open, I/O, close, and termios persistence behavior. Do not add 
a generic TTY-core locking change, do not use 
`TTY_DRIVER_RESET_TERMIOS`, and do not include unrelated refactoring.

On 9/8/2026 2:00 PM, syzbot wrote:
> A slab-use-after-free occurs in release_tty() when closing a USB serial
> port while the gadget is concurrently being unbound or destroyed. When a
> user closes /dev/ttyGS*, tty_release() invokes gs_close(), which decrements
> port->port.count to 0 and wakes up port->close_wait. Concurrently,
> gserial_free_port() (invoked during gadget unbind/rmdir) wakes up from
> wait_event(port->close_wait, gs_closed(port)) and frees the gs_port
> structure with tty_port_destroy() and kfree(). After gs_close() completes,
> tty_release() continues execution and calls release_tty(), which accesses
> the already freed gs_port through tty->port->itty = NULL.
>
> BUG: KASAN: slab-use-after-free in release_tty+0x371/0x570
> drivers/tty/tty_io.c:1557
> Write of size 8 at addr ffff8881903f2128 by task syz-executor600/5845
>
> Call Trace:
>   <TASK>
>   release_tty+0x371/0x570 drivers/tty/tty_io.c:1557
>   tty_release_struct+0xb8/0xd0 drivers/tty/tty_io.c:1665
>   tty_release+0xc62/0x1670 drivers/tty/tty_io.c:1825
>   __fput+0x418/0xa50 fs/file_table.c:512
>   fput_close_sync+0x11f/0x240 fs/file_table.c:617
>   __x64_sys_close+0x7e/0x110 fs/open.c:1545
>   do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
>   entry_SYSCALL_64_after_hwframe+0x77/0x7f
>   </TASK>
>
> Allocated by task 5843:
>   __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
>   gs_port_alloc drivers/usb/gadget/function/u_serial.c:1218 [inline]
>   gserial_alloc_line_no_console+0x232/0x6e0
>   drivers/usb/gadget/function/u_serial.c:1298
>   gserial_alloc_line+0x18/0x90 drivers/usb/gadget/function/u_serial.c:1332
>   acm_alloc_instance+0xc7/0x140 drivers/usb/gadget/function/f_acm.c:891
>   usb_get_function_instance+0xe3/0x2f0 drivers/usb/gadget/functions.c:44
>   function_make+0x127/0x360 drivers/usb/gadget/configfs.c:626
>   configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
>   vfs_mkdir+0x40c/0x620 fs/namei.c:5410
>
> Freed by task 5843:
>   kfree+0x1c5/0x650 mm/slub.c:6792
>   gserial_free_port+0x248/0x2c0 drivers/usb/gadget/function/u_serial.c:1262
>   gserial_free_line+0xc0/0x1f0 drivers/usb/gadget/function/u_serial.c:1279
>   acm_free_instance+0x39/0x60 drivers/usb/gadget/function/f_acm.c:875
>   usb_put_function_instance+0x95/0xc0 drivers/usb/gadget/functions.c:77
>   config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
>   configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
>   vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
>
> Fix this by managing the lifetime of struct gs_port through standard
> tty_port reference counting:
> - Implement .install (gs_install) to acquire a port reference with
> tty_port_get(), attach the port via tty_port_install(), and initialize
> tty->driver_data.
> - Implement .cleanup (gs_cleanup) to drop the tty_struct's reference with
> tty_port_put().
> - Implement the .destruct port operation (gs_port_destruct) to free the
> write FIFO buffer and kfree() the gs_port structure once all references
> have been dropped.
> - In gserial_free_port(), replace the direct tty_port_destroy() and kfree()
> calls with tty_port_put().
> - In gs_close(), check if port is NULL or if port->port.count is 0 (which
> may occur if open failed) to prevent NULL pointer dereferences or spurious
> warnings.
>
> Fixes: 19b10a8828a6 ("usb: gadget: allocate & giveback serial ports instead hard code them")
> Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot
> Reported-by: syzbot+fe63e4d633540f230624@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=fe63e4d633540f230624
> Link: https://syzkaller.appspot.com/ai_job?id=b41b38ff-60c3-4626-adff-58b24c0a4cbc
> To: "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>
> To: <linux-usb@vger.kernel.org>
> To: "Sebastian Andrzej Siewior" <bigeasy@linutronix.de>
> Cc: "Ai Chao" <aichao@kylinos.cn>
> Cc: "Kees Cook" <kees@kernel.org>
> Cc: <linux-kernel@vger.kernel.org>
>
> ---
> diff --git a/drivers/usb/gadget/function/u_serial.c b/drivers/usb/gadget/function/u_serial.c
> index cdd1dfc66..592bb9504 100644
> --- a/drivers/usb/gadget/function/u_serial.c
> +++ b/drivers/usb/gadget/function/u_serial.c
> @@ -603,6 +603,41 @@ static int gserial_wakeup_host(struct gserial *gser)
>   
>   /* TTY Driver */
>   
> +static int gs_install(struct tty_driver *driver, struct tty_struct *tty)
> +{
> +	struct gs_port *port;
> +	struct tty_port *tport;
> +	int ret;
> +
> +	mutex_lock(&ports[tty->index].lock);
> +	port = ports[tty->index].port;
> +	if (!port) {
> +		mutex_unlock(&ports[tty->index].lock);
> +		return -ENODEV;
> +	}
> +
> +	tport = tty_port_get(&port->port);
> +	mutex_unlock(&ports[tty->index].lock);
> +
> +	if (!tport)
> +		return -ENODEV;
> +
> +	ret = tty_port_install(tport, driver, tty);
> +	if (ret) {
> +		tty_port_put(tport);
> +		return ret;
> +	}
> +
> +	tty->driver_data = port;
> +
> +	return 0;
> +}
> +
> +static void gs_cleanup(struct tty_struct *tty)
> +{
> +	tty_port_put(tty->port);
> +}
> +
>   /*
>    * gs_open sets up the link between a gs_port and its associated TTY.
>    * That link is broken *only* by TTY close(), and all driver methods
> @@ -695,14 +730,16 @@ static void gs_close(struct tty_struct *tty, struct file *file)
>   	struct gs_port *port = tty->driver_data;
>   	struct gserial	*gser;
>   
> +	if (!port)
> +		return;
> +
>   	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;
> +	if (port->port.count == 0)
> +		goto exit;
> +
> +	if (port->port.count > 1) {
> +		--port->port.count;
>   		goto exit;
>   	}
>   
> @@ -722,8 +759,11 @@ static void gs_close(struct tty_struct *tty, struct file *file)
>   					GS_CLOSE_TIMEOUT * HZ);
>   		spin_lock_irq(&port->port_lock);
>   
> -		if (port->port.count != 1)
> -			goto raced_with_open;
> +		if (port->port.count != 1) {
> +			if (port->port.count > 1)
> +				--port->port.count;
> +			goto exit;
> +		}
>   
>   		gser = port->port_usb;
>   	}
> @@ -909,8 +949,10 @@ static int gs_get_icount(struct tty_struct *tty,
>   }
>   
>   static const struct tty_operations gs_tty_ops = {
> +	.install =		gs_install,
>   	.open =			gs_open,
>   	.close =		gs_close,
> +	.cleanup =		gs_cleanup,
>   	.write =		gs_write,
>   	.put_char =		gs_put_char,
>   	.flush_chars =		gs_flush_chars,
> @@ -1203,6 +1245,18 @@ static void gs_console_exit(struct gs_port *port)
>   
>   #endif
>   
> +static void gs_port_destruct(struct tty_port *tport)
> +{
> +	struct gs_port *port = container_of(tport, struct gs_port, port);
> +
> +	kfifo_free(&port->port_write_buf);
> +	kfree(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 +1276,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 +1313,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)
>
>
> base-commit: df2908090cda368b01ff43709f51890076c56157

      reply	other threads:[~2026-09-10 10:20 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-08 12:00 [PATCH RFC] usb: gadget: u_serial: Fix use-after-free in release_tty syzbot
2026-09-10 10:20 ` Krystian Kaniewski [this message]

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=64df2b77-52a4-4c1e-a40f-2abebba5b080@gmail.com \
    --to=krystianmkaniewski@gmail.com \
    --cc=syzbot@kernel.org \
    --cc=syzbot@lists.linux.dev \
    --cc=syzkaller-upstream-moderation@googlegroups.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