Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tty: Fix recursive deadlock in tty_perform_flush()

tty_perform_flush() can deadlock when called while holding
a line discipline reference. By definition, all ldisc drivers
hold a ldisc reference, so calls originating from ldisc drivers
must not block for a ldisc reference.

The deadlock can occur when:
CPU 0 | CPU 1
|
tty_ldisc_ref(tty) |
.... | <line discipline halted>
tty_ldisc_ref_wait(tty) |
|

CPU 0 cannot progess because it cannot obtain an ldisc reference
with the line discipline has been halted (thus no new references
are granted).
CPU 1 cannot progress because an outstanding ldisc reference
has not been released.

An in-tree call-tree audit of tty_perform_flush() [1] shows 5
ldisc drivers calling tty_perform_flush() indirectly via
n_tty_ioctl_helper() and 2 ldisc drivers calling directly.
A single tty driver safely uses the function.

[1]
Recursive usage:

/* These functions are line discipline ioctls and thus
* recursive wrt line discipline references */

tty_perform_flush() - ./drivers/tty/tty_ioctl.c
n_tty_ioctl_helper()
hci_uart_tty_ioctl(default) - drivers/bluetooth/hci_ldisc.c (N_HCI)
n_hdlc_tty_ioctl(default) - drivers/tty/n_hdlc.c (N_HDLC)
gsmld_ioctl(default) - drivers/tty/n_gsm.c (N_GSM0710)
n_tty_ioctl(default) - drivers/tty/n_tty.c (N_TTY)
gigaset_tty_ioctl(default) - drivers/isdn/gigaset/ser-gigaset.c (N_GIGASET_M101)
ppp_synctty_ioctl(TCFLSH) - drivers/net/ppp/pps_synctty.c
ppp_asynctty_ioctl(TCFLSH) - drivers/net/ppp/ppp_async.c

Non-recursive use:

tty_perform_flush() - drivers/tty/tty_ioctl.c
ipw_ioctl(TCFLSH) - drivers/tty/ipwireless/tty.c
/* This function is a tty i/o ioctl method, which
* is invoked by tty_ioctl() */

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Peter Hurley and committed by
Greg Kroah-Hartman
e7f3880c be397116

+22 -12
+1 -1
drivers/net/ppp/ppp_async.c
··· 314 314 /* flush our buffers and the serial port's buffer */ 315 315 if (arg == TCIOFLUSH || arg == TCOFLUSH) 316 316 ppp_async_flush_output(ap); 317 - err = tty_perform_flush(tty, arg); 317 + err = n_tty_ioctl_helper(tty, file, cmd, arg); 318 318 break; 319 319 320 320 case FIONREAD:
+1 -1
drivers/net/ppp/ppp_synctty.c
··· 355 355 /* flush our buffers and the serial port's buffer */ 356 356 if (arg == TCIOFLUSH || arg == TCOFLUSH) 357 357 ppp_sync_flush_output(ap); 358 - err = tty_perform_flush(tty, arg); 358 + err = n_tty_ioctl_helper(tty, file, cmd, arg); 359 359 break; 360 360 361 361 case FIONREAD:
+20 -10
drivers/tty/tty_ioctl.c
··· 1122 1122 } 1123 1123 EXPORT_SYMBOL_GPL(tty_mode_ioctl); 1124 1124 1125 - int tty_perform_flush(struct tty_struct *tty, unsigned long arg) 1126 - { 1127 - struct tty_ldisc *ld; 1128 - int retval = tty_check_change(tty); 1129 - if (retval) 1130 - return retval; 1131 1125 1132 - ld = tty_ldisc_ref_wait(tty); 1126 + /* Caller guarantees ldisc reference is held */ 1127 + static int __tty_perform_flush(struct tty_struct *tty, unsigned long arg) 1128 + { 1129 + struct tty_ldisc *ld = tty->ldisc; 1130 + 1133 1131 switch (arg) { 1134 1132 case TCIFLUSH: 1135 1133 if (ld && ld->ops->flush_buffer) { ··· 1145 1147 tty_driver_flush_buffer(tty); 1146 1148 break; 1147 1149 default: 1148 - tty_ldisc_deref(ld); 1149 1150 return -EINVAL; 1150 1151 } 1151 - tty_ldisc_deref(ld); 1152 1152 return 0; 1153 + } 1154 + 1155 + int tty_perform_flush(struct tty_struct *tty, unsigned long arg) 1156 + { 1157 + struct tty_ldisc *ld; 1158 + int retval = tty_check_change(tty); 1159 + if (retval) 1160 + return retval; 1161 + 1162 + ld = tty_ldisc_ref_wait(tty); 1163 + retval = __tty_perform_flush(tty, arg); 1164 + if (ld) 1165 + tty_ldisc_deref(ld); 1166 + return retval; 1153 1167 } 1154 1168 EXPORT_SYMBOL_GPL(tty_perform_flush); 1155 1169 ··· 1201 1191 } 1202 1192 return 0; 1203 1193 case TCFLSH: 1204 - return tty_perform_flush(tty, arg); 1194 + return __tty_perform_flush(tty, arg); 1205 1195 default: 1206 1196 /* Try the mode commands */ 1207 1197 return tty_mode_ioctl(tty, file, cmd, arg);