lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <3e1baa67-2624-4e5d-85a7-57b6cdf619e7@suse.com>
Date: Mon, 23 Jun 2025 12:32:49 +0200
From: Oliver Neukum <oneukum@...e.com>
To: hsyemail2@...il.com, Johan Hovold <johan@...nel.org>,
 Greg Kroah-Hartman <gregkh@...uxfoundation.org>
Cc: linux-usb@...r.kernel.org, linux-kernel@...r.kernel.org,
 Sheng-Yuan Huang <syhuang3@...oton.com>
Subject: Re: [PATCH v3 1/1] USB: serial: nct_usb_serial: add support for
 Nuvoton USB adapter

Hi,

On 23.06.25 09:17, hsyemail2@...il.com wrote:
> From: Sheng-Yuan Huang <syhuang3@...oton.com>
> 
> Add support for the Nuvoton USB-to-serial adapter, which provides
> multiple serial ports over a single USB interface.
> 
> The device exposes one control endpoint, one bulk-in endpoint, and
> one bulk-out endpoint for data transfer. Port status is reported via
> an interrupt-in or bulk-in endpoint, depending on device configuration.

A few issues left I am afraid.
Comments on them inline.

	Regards
		Oliver

> +/* Index definition */
> +#define NCT_VCOM_INDEX_0	0
> +#define NCT_VCOM_INDEX_1	1
> +#define NCT_VCOM_INDEX_2	2
> +#define NCT_VCOM_INDEX_3	3
> +#define NCT_VCOM_INDEX_4	4
> +#define NCT_VCOM_INDEX_5	5

Why? These make no sense.

> +#define NCT_VCOM_INDEX_GLOBAL	0xF
> +
> +/* Command */
> +#define NCT_VCOM_GET_NUM_PORTS		0
> +#define NCT_VCOM_GET_PORTS_SUPPORT	1
> +#define NCT_VCOM_GET_BAUD		2
> +#define NCT_VCOM_SET_INIT		3
> +#define NCT_VCOM_SET_CONFIG		4
> +#define NCT_VCOM_SET_BAUD		5
> +#define NCT_VCOM_SET_HCR		6
> +#define NCT_VCOM_SET_OPEN_PORT		7
> +#define NCT_VCOM_SET_CLOSE_PORT		8
> +#define NCT_VCOM_SILENT			9
> +/* Use bulk-in status instead of interrupt-in status */
> +#define NCT_VCON_SET_BULK_IN_STATUS	10
> +
> +struct nct_vendor_cmd {
> +	__le16 val;	/* bits[3:0]: index, bits[11:4]: cmd, bits[15:12]: reserved */
> +};
> +
> +#define NCT_CMD_INDEX_MASK	0x000F
> +#define NCT_CMD_CMD_MASK	0x0FF0
> +#define NCT_CMD_CMD_SHIFT	4
> +
> +static inline __le16 nct_build_cmd(__u8 cmd_code, __u8 index)
> +{
> +	return cpu_to_le16((cmd_code << NCT_CMD_CMD_SHIFT) | (index & NCT_CMD_INDEX_MASK));

This may be picking nits, but it seems to me that cmd_code is u8.
Hence cmd_code << NCT_CMD_CMD_SHIFT) would also be u8 and the operation
may overflow. You better cast cmd_code to u16.

> +static u16 nct_set_baud(struct usb_interface *intf, u16 index, unsigned int cflag, bool *found)
> +{
> +	struct nct_vendor_cmd cmd;
> +	struct nct_ctrl_msg msg;
> +	u16 i;
> +	u8 spd = NCT_DEFAULT_BAUD;
> +
> +	*found = false;
> +	cmd.val = nct_build_cmd(NCT_VCOM_SET_BAUD, index);
> +	dev_dbg(&intf->dev, "tty baud: 0x%X\n", (cflag & CBAUD));
> +	for (i = 0; i < ARRAY_SIZE(NCT_BAUD_SUP); i++) {
> +		if ((cflag & CBAUD) == NCT_BAUD_SUP[i]) {
> +			spd = i;
> +			dev_dbg(&intf->dev, "index %d set baud: NCT_BAUD_SUP[%d]=%d\n",
> +				index, spd, NCT_BAUD_SUP[i]);
> +			/*
> +			 * Create control message
> +			 * Note: The NCT_VCOM_SET_BAUD only set the baud rate
> +			 */
> +			msg.val = nct_build_ctrl_msg(0, 0, 0, 0, spd);
> +			if (nct_vendor_write(intf, cmd, le16_to_cpu(msg.val)))
> +				dev_err(&intf->dev, "%s - Set index: %d speed error\n",
> +					__func__, index);
> +			else
> +				*found = true;
> +
> +			break;
> +		}
> +	}
> +
> +	if (!*found)
> +		dev_warn(&intf->dev, "Unsupported baud rate 0x%X requested\n", (cflag & CBAUD));

This is problematic. There are two reasons for this to trigger

1. no match
2. IO error in nct_vendor_write()

If the second case happens you nevertheless claim the first cause
I'd just drop the warning. Better nothing than something misleading.
  
> +
> +	return spd;
> +}


> +static int nct_tiocmset_helper(struct tty_struct *tty, unsigned int set,
> +			       unsigned int clear)
> +{
> +	struct usb_serial_port *port = tty->driver_data;
> +	struct nct_tty_port *tport = usb_get_serial_port_data(port);
> +	struct usb_serial *serial = port->serial;
> +	struct usb_interface *intf = serial->interface;
> +	struct nct_ctrl_msg msg;
> +	struct nct_vendor_cmd cmd;
> +	unsigned long flags;
> +	u8 hcr = 0;
> +
> +	if (set & TIOCM_RTS)
> +		hcr |= NCT_HCR_RTS;
> +	if (set & TIOCM_DTR)
> +		hcr |= NCT_HCR_DTR;
> +	if (clear & TIOCM_RTS)
> +		hcr &= ~NCT_HCR_RTS;
> +	if (clear & TIOCM_DTR)
> +		hcr &= ~NCT_HCR_DTR;
> +
> +	cmd.val = nct_build_cmd(NCT_VCOM_SET_HCR, tport->hw_idx);
> +	msg.val = cpu_to_le16(hcr);
> +
> +	spin_lock_irqsave(&tport->port_lock, flags);

No need for irqsave. A simple irq version will do.
Using irqsave is misleading, because we know that this
function can sleep.

> +	tport->hcr = hcr;
> +	spin_unlock_irqrestore(&tport->port_lock, flags);
> +
> +	dev_dbg(&intf->dev,
> +		"index/cmd/val(hcr)=%X, %X, %X [RTS=%X, DTR=%X]\n",
> +		nct_get_cmd_index(cmd.val), nct_get_cmd_cmd(cmd.val),
> +		le16_to_cpu(msg.val), hcr & NCT_HCR_RTS, hcr & NCT_HCR_DTR);
> +
> +	return nct_vendor_write(intf, cmd, le16_to_cpu(msg.val));
> +}


> +static int nct_serial_write_data(struct tty_struct *tty, struct usb_serial_port *port,
> +				 const unsigned char *buf, int count)
> +{
> +	int ret;
> +	unsigned long flags;
> +	struct nct_packet_header hdr;
> +	int wr_len;
> +	struct nct_tty_port *tport = usb_get_serial_port_data(port);
> +
> +	wr_len = min((unsigned int)count, NCT_MAX_SEND_BULK_SIZE - sizeof(hdr));
> +
> +	if (!wr_len)
> +		return 0;
> +
> +	spin_lock_irqsave(&tport->port_lock, flags);
> +

[..]

> +	/* Fill header */
> +	hdr.magic = NCT_HDR_MAGIC;
> +	hdr.magic2 = NCT_HDR_MAGIC2;
> +	nct_set_hdr_idx_len(&hdr, tport->hw_idx, wr_len); /* The 'hw_idx' is based on 1 */
> +
> +	/* Copy data */
> +	memcpy(port->write_urb->transfer_buffer + sizeof(hdr),
> +	       (const void *)buf, wr_len);
> +
> +	/* Filled urb data */
> +	memcpy(port->write_urb->transfer_buffer, (const void *)&hdr,
> +	       sizeof(hdr)); /* Copy header after filling all other fields */

You are copying the header in unconditionally ...

> +
> +	/* Set urb length(Total length) */
> +	port->write_urb->transfer_buffer_length = wr_len + sizeof(hdr);
> +
> +	port->write_urb->transfer_flags |= URB_ZERO_PACKET;
> +
> +	ret = usb_submit_urb(port->write_urb, GFP_ATOMIC);
> +	if (ret < 0) {
> +		dev_err(&port->dev,
> +			"%s: usb_submit_urb failed, ret=%d, hw_idx=%d\n",
> +			__func__, ret, tport->hw_idx);
> +	} else {
> +		tport->write_urb_in_use = true; /* Set it as busy */
> +		ret = wr_len + sizeof(hdr);
> +	}
> +
> +	spin_unlock_irqrestore(&tport->port_lock, flags);
> +
> +	if (ret > sizeof(hdr))
> +		ret = ret - sizeof(hdr);

... and here you check?

This needs an explanation. A very good explanation.

> +
> +	dev_dbg(&port->dev, "returning %d\n", ret);
> +	return ret;
> +}
> +
> +static int nct_serial_write(struct tty_struct *tty, struct usb_serial_port *port,
> +			    const unsigned char *buf, int count)
> +{
> +	struct nct_tty_port *tport = usb_get_serial_port_data(port);
> +
> +	if (!port) {
> +		pr_err("%s: port is NULL!\n", __func__);
> +		return -EIO;
> +	}
> +	if (!port->write_urb) {
> +		dev_err(&port->dev, "%s: write_urb not initialized!\n",	__func__);
> +		return -EIO;
> +	}
> +	if (!port->write_urb->transfer_buffer) {
> +		dev_err(&port->dev, "%s: transfer_buffer not initialized!\n", __func__);
> +		return -EIO;
> +	}

Can these errors really happen?

> +
> +	/* Flow control */
> +	if (tty_port_cts_enabled(tty->port))
> +		if (tport->flow_stop_wrt)
> +			return 0;
> +
> +	return nct_serial_write_data(tty, port, buf, count);
> +}


> +static int nct_open(struct tty_struct *tty, struct usb_serial_port *port)
> +{
> +	struct nct_vendor_cmd cmd;
> +	struct nct_ctrl_msg msg;
> +	struct nct_tty_port *tport = usb_get_serial_port_data(port);
> +	struct usb_serial *serial = port->serial;
> +	struct nct_serial *serial_priv = usb_get_serial_data(serial);
> +	struct usb_interface *intf = serial->interface;
> +
> +	if (!port->serial)
> +		return -ENXIO;
> +
> +	/* Allocate write_urb */
> +	if (!port->write_urb) {
> +		port->write_urb = usb_alloc_urb(0, GFP_KERNEL);
> +		if (!port->write_urb) {
> +			dev_err(&port->dev, "%s: Failed to allocate write URB\n", __func__);
> +			return -ENOMEM;
> +		}
> +	}
> +
> +	/* Allocate bulk_out_buffer */
> +	port->write_urb->transfer_buffer = kmalloc(NCT_MAX_SEND_BULK_SIZE, GFP_KERNEL);

Can use kzalloc()

> +	if (!port->write_urb->transfer_buffer) {
> +		usb_free_urb(port->write_urb);
> +		port->write_urb = NULL;
> +		return -ENOMEM;
> +	}
> +
> +	/* Clear(init) buffer */
> +	memset(port->write_urb->transfer_buffer, 0, NCT_MAX_SEND_BULK_SIZE);
> +
> +	/* Set write_urb */
> +	usb_fill_bulk_urb(port->write_urb, serial->dev,
> +			  usb_sndbulkpipe(serial->dev, serial_priv->bulk_out_ep->bEndpointAddress),
> +			  port->write_urb->transfer_buffer, NCT_MAX_SEND_BULK_SIZE,
> +			  nct_write_bulk_callback, port);
> +
> +	/* Be sure the device is started up */
> +	if (nct_startup_device(port->serial) != 0)
> +		return -ENXIO;
> +
> +	cmd.val = nct_build_cmd(NCT_VCOM_SET_OPEN_PORT, tport->hw_idx);
> +	msg.val = cpu_to_le16(0);
> +	nct_vendor_write(intf, cmd, le16_to_cpu(msg.val));

This can fail.

> +	/*
> +	 * Delay 1ms for firmware to configure hardware after opening the port.
> +	 * (Especially at high speed)
> +	 */
> +	usleep_range(1000, 2000);
> +	return 0;
> +}
> +
> +static void nct_close(struct usb_serial_port *port)
> +{
> +	struct nct_tty_port *tport = usb_get_serial_port_data(port);
> +	unsigned long flags;
> +
> +	mutex_lock(&port->serial->disc_mutex);
> +	/* If disconnected, don't send the close-command to the firmware */
> +	if (port->serial->disconnected)

We are disconnected ...
> +		goto exit;
> +
> +	nct_serial_port_end(port);
> +
> +exit:
> +	/* Shutdown any outstanding bulk writes */
> +	usb_kill_urb(port->write_urb);

... so this is either useless, or a bug has already happened.

> +
> +	/* Free transfer_buffer */
> +	kfree(port->write_urb->transfer_buffer);
> +	port->write_urb->transfer_buffer = NULL;
> +
> +	if (tport) {
> +		spin_lock_irqsave(&tport->port_lock, flags);
> +		tport->write_urb_in_use = false;
> +		spin_unlock_irqrestore(&tport->port_lock, flags);
> +	}
> +
> +	mutex_unlock(&port->serial->disc_mutex);
> +}
> +

> +static void nct_usb_serial_read(struct urb *urb)
> +{
> +	struct usb_serial_port *port =  urb->context;
> +	struct usb_serial *serial = port->serial;
> +	struct usb_interface *intf = serial->interface;
> +	struct nct_serial *serial_priv = usb_get_serial_data(serial);
> +	struct nct_tty_port *tport;
> +	struct nct_packet_header *hdr = NULL;
> +	unsigned char *data = urb->transfer_buffer;
> +	int i, j;
> +	int actual_len = urb->actual_length;
> +	int len = 0;
> +	struct nct_port_status *nps;
> +	unsigned long flags;
> +
> +	if (!urb->actual_length)
> +		return;
> +
> +again:
> +	spin_lock_irqsave(&serial_priv->serial_lock, flags);
> +	tport = serial_priv->cur_port;
> +	if (!tport) {
> +		/*
> +		 * Handle a new data package (i.e., it is not
> +		 * the remaining data without a header).
> +		 * The package does not need to be combined this time.
> +		 */
> +
> +		for (i = 0; i < urb->actual_length; i++) {
> +			hdr = (struct nct_packet_header *)data;

How do you know that there is enough data for struct nct_packet_header
left?

> +			/* Decode the header */
> +
> +			if (serial_priv->status_trans_mode) {
> +				/*
> +				 * Status data is also transmitted via bulk-in
> +				 * pipe.
> +				 */
> +				if (hdr->magic == NCT_HDR_MAGIC &&
> +				    hdr->magic2 == NCT_HDR_MAGIC_STATUS &&
> +				    nct_get_hdr_len(hdr) == 24 && actual_len >= 28) {
> +					/*
> +					 * Notice: actual_len will be decreased,
> +					 * it is equal to urb->actual_length
> +					 * only at the beginning.
> +					 */
> +
> +					/*
> +					 * Status report.
> +					 * It should be a standalone package in
> +					 * one URB
> +					 */
> +					data += sizeof(struct nct_packet_header);
> +					actual_len -= sizeof(struct nct_packet_header);
> +
> +					nps = (struct nct_port_status *)data;
> +
> +					for (j = 0; j < actual_len - 4; j++) {
> +						nct_update_status(serial, (unsigned char *)nps);
> +						nps++;
> +					}
> +
> +					spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
> +					return;
> +				}
> +			}
> +
> +			if (hdr->magic == NCT_HDR_MAGIC &&
> +			    hdr->magic2 == NCT_HDR_MAGIC2 &&
> +			    nct_get_hdr_idx(hdr) <= NCT_MAX_NUM_COM_DEVICES &&
> +			    nct_get_hdr_len(hdr) <= 512)
> +				break;
> +
> +			data++;
> +			actual_len--;
> +			if (!actual_len) {
> +				dev_err(&intf->dev, "%s: Decode serial packet size failed.\n",
> +					__func__);
> +				spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
> +				return;
> +			}
> +		}
> +		/*
> +		 * Search tty port
> +		 * Search the tty device by the idx in header, and check if
> +		 * it is registered or opened.
> +		 * If it is, record them. The record will be used later for
> +		 * 2 purposes:
> +		 *   (1) If the current data package is incomplete, the following
> +		 *       incoming data will not include a header.
> +		 *   (2) To determine which device will be used for transmission.
> +		 */
> +		tport = NULL;
> +		for (i = 0; i < serial->type->num_ports; i++) {
> +			port = serial->port[i];
> +			tport = usb_get_serial_port_data(port);
> +			if (tport->hw_idx != nct_get_hdr_idx(hdr))
> +				continue;
> +
> +			break;
> +		}
> +		if (!tport) {
> +			dev_err(&intf->dev, "%s: Decode serial packet index failed.\n",	__func__);
> +			spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
> +			return;
> +		}
> +		/*
> +		 * Calculate the data length.
> +		 * Then, check if the length specified in the header matches
> +		 * the data length. If not, it indicates that the data we
> +		 * received spans across two (or more) packets.
> +		 */
> +		actual_len -= sizeof(struct nct_packet_header);
> +		data += sizeof(struct nct_packet_header);
> +		/* actual_len: the data length of the data we got this time */
> +		if (nct_get_hdr_len(hdr) > actual_len) {
> +			/*
> +			 * It means the length specified in the header (the
> +			 * custom header) is greater than the length of the
> +			 * data we received.
> +			 * Therefore, the data we received this time does not
> +			 * span across another packet (i.e. no new header).
> +			 */
> +			len = actual_len;
> +			/*
> +			 * cur_len: Record how many data does not handle yet
> +			 */
> +			serial_priv->cur_len = nct_get_hdr_len(hdr) - len;
> +			/*
> +			 * Record the current port. When we got remained data of
> +			 * the package next time
> +			 */
> +			serial_priv->cur_port = tport;
> +		} else {
> +			/*
> +			 * The data we got crosses packages(not belong
> +			 * to the same header). We only handle data by
> +			 * the length in header. And we will handle
> +			 * another package when 'goto "again" '.
> +			 */
> +			len = nct_get_hdr_len(hdr);
> +		}
> +	} else { /*  Handling the remained data which crosses package */
> +		if (serial_priv->cur_len > actual_len) {
> +			/*
> +			 * The unhandled part of the data exceeds the data we
> +			 * received this time. We only handle the data we
> +			 * have, expecting more data to be received later.
> +			 */
> +			len = actual_len;
> +		} else {
> +			/*
> +			 * This means the package has been fully handled.
> +			 * Clear 'cur_port' as no additional data needs to be
> +			 * attached to the current package.
> +			 */
> +			len = serial_priv->cur_len;
> +			serial_priv->cur_port = NULL;
> +		}
> +		serial_priv->cur_len -= len;
> +	}
> +	spin_unlock_irqrestore(&serial_priv->serial_lock, flags);
> +	/*
> +	 * The per-character sysrq handling is too slow for fast devices,
> +	 * so we bypass it in the vast majority of cases where the USB serial is
> +	 * not a console.
> +	 */
> +	if (tport->sysrq) {
> +		for (i = 0; i < len; i++, data++)
> +			tty_insert_flip_char(&port->port, *data, TTY_NORMAL);
> +	} else {
> +		tty_insert_flip_string(&port->port, data, len);
> +		data += len;
> +	}
> +	/*
> +	 * Send data to the tty device (according to the port identified above).
> +	 */
> +	tty_flip_buffer_push(&port->port);
> +	actual_len -= len;
> +
> +	/*
> +	 * It means that the data we received this time contains two or
> +	 * more data packages, so it needs to continue processing the next
> +	 * data packages.
> +	 */
> +	if (actual_len > 0)
> +		goto again;
> +}

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ