serial: core: prevent division by zero by always returning non-zero baud rate

If a device has a minimum baud rate > 9600 bauds, and a new termios baud
rate of 0 (hang up) is requested, uart_get_baud_rate() will return 0.
Most drivers do not check this return value and call uart_update_timeout()
with this zero baud rate, which will trigger a "Division by zero in kernel"
fault:

  stty -F /dev/ttySC0 0

  Division by zero in kernel.
  ...

Fix by returning the larger of 9600 or min for the B0 case. This now
ensures that a non-zero baud rate is returned, and greatly simplifies the
code.

Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
Link: https://patch.msgid.link/20260410152022.2146488-6-hugo@hugovil.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Hugo Villeneuve 2026-04-10 11:20:13 -04:00 committed by Greg Kroah-Hartman
parent 1097aedec7
commit 091ea8e5d3

View File

@ -462,11 +462,10 @@ EXPORT_SYMBOL(uart_update_timeout);
*
* Decode the termios structure into a numeric baud rate, taking account of the
* magic 38400 baud rate (with spd_* flags), and mapping the %B0 rate to 9600
* baud.
* baud or min argument, whichever is greater.
*
* If the new baud rate is invalid, try the @old termios setting. If it's still
* invalid, clip to the nearest chip supported rate.
* If that is also invalid 0 is returned.
*
* The @termios structure is updated to reflect the baud rate we're actually
* going to be using. Don't do this for the case where B0 is requested ("hang
@ -481,7 +480,6 @@ uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
unsigned int try;
unsigned int baud;
unsigned int altbaud;
int hung_up = 0;
upf_t flags = port->flags & UPF_SPD_MASK;
switch (flags) {
@ -515,10 +513,8 @@ uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
/*
* Special case: B0 rate.
*/
if (baud == 0) {
hung_up = 1;
baud = 9600;
}
if (baud == 0)
return max(min, 9600);
if (baud >= min && baud <= max)
return baud;
@ -530,9 +526,7 @@ uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
termios->c_cflag &= ~CBAUD;
if (old) {
baud = tty_termios_baud_rate(old);
if (!hung_up)
tty_termios_encode_baud_rate(termios,
baud, baud);
tty_termios_encode_baud_rate(termios, baud, baud);
old = NULL;
continue;
}
@ -541,15 +535,16 @@ uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,
* As a last resort, if the range cannot be met then clip to
* the nearest chip supported rate.
*/
if (!hung_up) {
if (baud <= min)
baud = min + 1;
else
baud = max - 1;
if (baud <= min)
baud = min + 1;
else
baud = max - 1;
tty_termios_encode_baud_rate(termios, baud, baud);
}
tty_termios_encode_baud_rate(termios, baud, baud);
}
/* Should never happen */
WARN_ON(1);
return 0;
}
EXPORT_SYMBOL(uart_get_baud_rate);