Linux Input/HID development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Michael Zaidman" <michael.zaidman@gmail.com>
Cc: linux-input@vger.kernel.org, linux-serial@vger.kernel.org,
	linux-gpio@vger.kernel.org, dmitry.torokhov@gmail.com
Subject: Re: [PATCH v3 02/16] HID: ft260: uart: fix the serial driver
Date: Sat, 14 Nov 2026 23:37:15 +0000	[thread overview]
Message-ID: <sashiko-outbox-215115@kernel.org> (raw)
In-Reply-To: <20261114230800.1344401-3-michael.zaidman@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [High] HID: ft260: undrained xmit_fifo on TX error or remove hangs tty
  drain and vhangup
- [High] HID: ft260: race between tty open/reopen and ft260_remove()
  causes hdev UAF
- [High] HID: ft260: premature list_del() in ft260_uart_port_remove()
  causes index reuse
- [Medium] HID: ft260: missing UART_COUNT_MAX check in
  ft260_uart_add_port()
--

commit 4759b9dbc3d5fe28cb8dc19ab0c19093471e2b92
Author: Michael Zaidman <michael.zaidman@gmail.com>

HID: ft260: uart: fix the serial driver

This commit fixes buffer sharing, power-save wakeup handling, input report
validation, TX work queueing, and port lifecycle management in the FT260
UART driver.

> diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
> index 982e2ca7a2de6..8d72a22d7a079 100644
> --- a/drivers/hid/hid-ft260.c
> +++ b/drivers/hid/hid-ft260.c

[ ... ]

> @@ -1123,8 +1161,8 @@ static int ft260_uart_add_port(struct ft260_device *port)
>  	int index = 0, ret = 0;
>  	struct ft260_device *dev;
>  
> -	spin_lock_init(&port->write_lock);
> -	if (kfifo_alloc(&port->xmit_fifo, FIFO_SIZE, GFP_KERNEL))
> +	spin_lock_init(&port->xmit_fifo_lock);
> +	if (kfifo_alloc(&port->xmit_fifo, XMIT_FIFO_SIZE, GFP_KERNEL))
>  		return -ENOMEM;
>  
>  	mutex_lock(&ft260_uart_list_lock);
> @@ -1135,7 +1173,8 @@ static int ft260_uart_add_port(struct ft260_device *port)
>  	}
>  
>  	port->index = index;
> -	list_add_tail(&port->device_list, &ft260_uart_device_list);
> +	/* Before the first gap, or at the end: the list stays sorted */
> +	list_add_tail(&port->device_list, &dev->device_list);
>  	mutex_unlock(&ft260_uart_list_lock);
>  
>  	return ret;

[Severity: Medium]
Can ft260_uart_add_port() assign port->index >= UART_COUNT_MAX when more
than four FT260 UART interfaces are probed?

ft260_tty_driver is allocated with UART_COUNT_MAX (4) ports, but
ft260_uart_add_port() never checks index against UART_COUNT_MAX before
adding the port and returning 0.  ft260_uart_probe() then passes
dev->index to tty_port_register_device_attr() -> tty_port_link_device(),
which hits WARN_ON(index >= driver->num).

> @@ -1148,7 +1187,12 @@ static void ft260_uart_port_put(struct ft260_device *port)
>  
>  static void ft260_uart_port_remove(struct ft260_device *port)
>  {
> -	timer_delete_sync(&port->wakeup_timer);
> +	/*
> +	 * Shut down, not delete: a tty call before the hangup below
> +	 * must not re-arm the timer.
> +	 */
> +	timer_shutdown_sync(&port->wakeup_timer);
> +	cancel_work_sync(&port->wakeup_work);
>  
>  	mutex_lock(&ft260_uart_list_lock);
>  	list_del(&port->device_list);
>  	mutex_unlock(&ft260_uart_list_lock);

[Severity: High]
Does removing port->device_list in ft260_uart_port_remove() allow a newly
probed FT260 to reuse port->index while an open file descriptor still
keeps the old tty_struct in ft260_tty_driver->ttys[index]?

Until the last file descriptor is closed and release_tty() runs,
ft260_tty_driver->ttys[index] still points to the old hung-up tty_struct.
If the device is re-plugged before that close, ft260_uart_add_port() sees
the index as free and assigns it to the new device.  Opening the new
/dev/ttyFTx then finds the old tty_struct via tty_driver_lookup_tty() and
calls tty_reopen() -> ft260_uart_open() -> ft260_uart_port_activate() on
the old removed ft260_device, dereferencing its stopped and freed
port->hdev.

Moving list_del(&port->device_list) to ft260_uart_port_destroy() would keep
the index reserved until ft260_tty_driver->ttys[index] has been cleared.

[ ... ]

> @@ -1207,61 +1254,51 @@ static int ft260_uart_transmit_chars(struct ft260_device *port)
>  	struct kfifo *xmit = &port->xmit_fifo;
>  	struct tty_struct *tty;
>  	struct ft260_uart_write_request_report *rep;
> -	int len, data_len, ret = 0;
> +	int len, ret = 0;
>  
>  	tty = tty_port_tty_get(&port->port);
> +	if (!tty)
> +		return -EINVAL;
>  
> -	data_len = kfifo_len(xmit);
> -	if (!tty || !data_len) {
> -		ret = -EINVAL;
> -		goto tty_out;
> -	}
> -
> -	rep = (struct ft260_uart_write_request_report *)port->write_buf;
> -
> -	do {
> -		len = min(data_len, FT260_WR_DATA_MAX);
> +	rep = (struct ft260_uart_write_request_report *)port->uart_wr_buf;
>  
> +	while ((len = kfifo_out_spinlocked(xmit, rep->data,
> +					   FT260_WR_UART_DATA_MAX,
> +					   &port->xmit_fifo_lock)) > 0) {
>  		rep->report = FT260_UART_DATA_REPORT_ID(len);
>  		rep->length = len;
>  
> -		len = kfifo_out_locked(xmit, rep->data, len, &port->write_lock);
> -
> -		ret = ft260_hid_output_report(hdev, (u8 *)rep, len + sizeof(*rep));
> -		if (ret < 0) {
> -			hid_err(hdev, "Failed to start transfer, ret %d\n", ret);
> -			goto tty_out;
> -		}
> +		ret = ft260_hid_output_report(hdev, (u8 *)rep, len + 2);
> +		if (ret < 0)
> +			break;
>  
> -		data_len -= len;
>  		port->icount.tx += len;
> -	} while (data_len > 0);
> -
> -	len = kfifo_len(xmit);
> -	if ((FIFO_SIZE - len) > TTY_WAKEUP_WATERMARK)
> -		tty_wakeup(tty);
> -
> -	ret = 0;
> +	}
>  
> -tty_out:
> +	tty_wakeup(tty);
>  	tty_kref_put(tty);
> -	return ret;
> +	return ret < 0 ? ret : 0;
>  }

[Severity: High]
If ft260_hid_output_report() fails when more than 60 bytes are queued in
xmit_fifo, does breaking out of this loop leave xmit_fifo non-empty with
nothing to drain or flush it?

ft260_uart_chars_in_buffer() then continues returning a non-zero
kfifo_len(&port->xmit_fifo) forever, and ft260_uart_ops has no
.flush_buffer callback.  As a result, tcdrain() and
set_termios(TCSADRAIN/TCSAFLUSH) sleep indefinitely in
wait_event_interruptible(tty->write_wait, !tty_chars_in_buffer(tty)), and
if the device is unplugged while set_termios() is waiting with an ldisc
reference held, ft260_uart_port_remove() -> tty_port_tty_vhangup() ->
tty_ldisc_hangup() blocks forever in tty_ldisc_lock() before reaching
kfifo_free(&port->xmit_fifo):

CPU0                                  CPU1
tty_ioctl() [TCSETSW]
  ld = tty_ldisc_ref_wait(tty)
  ...
  set_termios()
    wait_event_interruptible(
      tty->write_wait,
      !tty_chars_in_buffer(tty))
                                      ft260_uart_port_remove()
                                        tty_port_tty_vhangup(&port->port)
                                          ...
                                          tty_ldisc_hangup()
                                            tty_driver_flush_buffer(tty)
                                            wake_up_interruptible_poll(
                                              &tty->write_wait, EPOLLOUT)
    /* xmit_fifo still non-empty,
       goes back to sleep */
                                            tty_ldisc_lock(tty,
                                              MAX_SCHEDULE_TIMEOUT)
                                              <-- hangs forever

[ ... ]

> @@ -1503,53 +1548,69 @@ static void ft260_uart_start_wakeup(struct timer_list *t)

[ ... ]

> -static int ft260_uart_activate(struct tty_port *tport, struct tty_struct *tty)
> +static int ft260_uart_port_activate(struct tty_port *tport, struct tty_struct *tty)
>  {
> +	int ret;
> +	int baudrate;
> +	struct ft260_get_uart_settings_report cfg;
> +	struct ft260_device *port = container_of(tport, struct ft260_device, port);
> +
>  	set_bit(TTY_IO_ERROR, &tty->flags);
>  
> -	spin_lock(&port->write_lock);
> +	spin_lock(&port->xmit_fifo_lock);
>  	kfifo_reset(&port->xmit_fifo);
> -	spin_unlock(&port->write_lock);
> +	spin_unlock(&port->xmit_fifo_lock);
>  
> -	ft260_uart_change_speed(port, &tty->termios, NULL);
>  	clear_bit(TTY_IO_ERROR, &tty->flags);
>  
> -	if (port->reschedule_work) {
> +	/*
> +	 * The port setting may remain intact after session termination.
> +	 * Then, when reopening the port without configuring the port
> +	 * setting, we need to retrieve the baud rate from the device to
> +	 * reactivate the wakeup workaround if needed.
> +	 */
> +	ret = ft260_get_uart_settings(port->hdev, &cfg);

[Severity: High]
Can ft260_uart_port_activate() race with ft260_remove() and dereference
port->hdev after it has been stopped and freed?

CPU0                                  CPU1
tty_open()
  tty_open_by_driver()
    tty_init_dev()
      ft260_uart_install()
        ft260_uart_port_get()
    mutex_unlock(&tty_mutex)
                                      ft260_remove()
                                        ...
                                        ft260_uart_port_remove()
                                          list_del(&port->device_list)
                                          tty_port_tty_vhangup(&port->port)
                                            /* port.tty is still NULL */
                                          kfifo_free(&port->xmit_fifo)
                                        hid_hw_close(hdev)
                                        hid_hw_stop(hdev)
                                        /* hdev freed by HID/USB core */
  ft260_uart_open()
    tty_port_open()
      ft260_uart_port_activate()
        ft260_get_uart_settings(port->hdev, ...)
          <-- use-after-free on port->hdev

The same race also happens on reopen when a second opener waits on
tty_lock_interruptible(tty) in tty_open_by_driver() while
tty_port_tty_vhangup() runs: vhangup clears the tty_port's initialized
flag, and tty_reopen() then proceeds to ft260_uart_open() ->
tty_port_open() -> ft260_uart_port_activate() after ft260_remove() has
finished.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261114230800.1344401-1-michael.zaidman@gmail.com?part=2

  reply	other threads:[~2026-11-14 23:37 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-14 23:07 [PATCH v3 00/16] HID: ft260: add UART and GPIO support, plus I2C fixes Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 01/16] HID: ft260: add serial driver Michael Zaidman
2026-11-14 23:39   ` sashiko-bot
2026-11-14 23:07 ` [PATCH v3 02/16] HID: ft260: uart: fix the " Michael Zaidman
2026-11-14 23:37   ` sashiko-bot [this message]
2026-11-14 23:07 ` [PATCH v3 03/16] HID: ft260: add GPIO support Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 04/16] HID: ft260: i2c: reduce driver module loading time Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 05/16] HID: ft260: fix sysfs store failure notice for big numbers Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 06/16] HID: ft260: i2c: reduce bus-error message severity Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 07/16] HID: ft260: uart: enable flow control Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 08/16] HID: ft260: uart: add modem pins control via ioctl Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 09/16] HID: ft260: group sysfs attrs per HID interface Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 10/16] HID: ft260: i2c: fix large write transaction failure Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 11/16] HID: ft260: workaround for TN_189 errata endpoint STALL after enumeration Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 12/16] HID: ft260: i2c: abort in-flight transfers with STOP before reset Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 13/16] HID: ft260: i2c: reject an I2C clock outside the supported range Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 14/16] HID: ft260: i2c: fix i2c_reset sysfs store return value Michael Zaidman
2026-11-14 23:07 ` [PATCH v3 15/16] HID: ft260: i2c: recover a bus held by a slave Michael Zaidman
2026-11-14 23:08 ` [PATCH v3 16/16] HID: ft260: use lock guards Michael Zaidman
2026-11-14 23:21   ` sashiko-bot

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=sashiko-outbox-215115@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=michael.zaidman@gmail.com \
    --cc=sashiko-reviews@lists.linux.dev \
    /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