aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Hurley <peter@hurleysoftware.com>2013-03-11 16:44:45 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-03-18 19:52:24 -0400
commite7f3880cd9b98c5bf9391ae7acdec82b75403776 (patch)
treecb8a2d9cff9d756ea7d0947ad58318a65b218ee0
parentbe3971166d93a401105952672dab2eac6542cb57 (diff)
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>
-rw-r--r--drivers/net/ppp/ppp_async.c2
-rw-r--r--drivers/net/ppp/ppp_synctty.c2
-rw-r--r--drivers/tty/tty_ioctl.c28
3 files changed, 21 insertions, 11 deletions
diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c
index a031f6b456b4..9c889e0303dd 100644
--- a/drivers/net/ppp/ppp_async.c
+++ b/drivers/net/ppp/ppp_async.c
@@ -314,7 +314,7 @@ ppp_asynctty_ioctl(struct tty_struct *tty, struct file *file,
314 /* flush our buffers and the serial port's buffer */ 314 /* flush our buffers and the serial port's buffer */
315 if (arg == TCIOFLUSH || arg == TCOFLUSH) 315 if (arg == TCIOFLUSH || arg == TCOFLUSH)
316 ppp_async_flush_output(ap); 316 ppp_async_flush_output(ap);
317 err = tty_perform_flush(tty, arg); 317 err = n_tty_ioctl_helper(tty, file, cmd, arg);
318 break; 318 break;
319 319
320 case FIONREAD: 320 case FIONREAD:
diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c
index 1a12033d2efa..bdf3b13a71a8 100644
--- a/drivers/net/ppp/ppp_synctty.c
+++ b/drivers/net/ppp/ppp_synctty.c
@@ -355,7 +355,7 @@ ppp_synctty_ioctl(struct tty_struct *tty, struct file *file,
355 /* flush our buffers and the serial port's buffer */ 355 /* flush our buffers and the serial port's buffer */
356 if (arg == TCIOFLUSH || arg == TCOFLUSH) 356 if (arg == TCIOFLUSH || arg == TCOFLUSH)
357 ppp_sync_flush_output(ap); 357 ppp_sync_flush_output(ap);
358 err = tty_perform_flush(tty, arg); 358 err = n_tty_ioctl_helper(tty, file, cmd, arg);
359 break; 359 break;
360 360
361 case FIONREAD: 361 case FIONREAD:
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
index 28715e48b2f7..d119034877de 100644
--- a/drivers/tty/tty_ioctl.c
+++ b/drivers/tty/tty_ioctl.c
@@ -1122,14 +1122,12 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file,
1122} 1122}
1123EXPORT_SYMBOL_GPL(tty_mode_ioctl); 1123EXPORT_SYMBOL_GPL(tty_mode_ioctl);
1124 1124
1125int tty_perform_flush(struct tty_struct *tty, unsigned long arg) 1125
1126/* Caller guarantees ldisc reference is held */
1127static int __tty_perform_flush(struct tty_struct *tty, unsigned long arg)
1126{ 1128{
1127 struct tty_ldisc *ld; 1129 struct tty_ldisc *ld = tty->ldisc;
1128 int retval = tty_check_change(tty);
1129 if (retval)
1130 return retval;
1131 1130
1132 ld = tty_ldisc_ref_wait(tty);
1133 switch (arg) { 1131 switch (arg) {
1134 case TCIFLUSH: 1132 case TCIFLUSH:
1135 if (ld && ld->ops->flush_buffer) { 1133 if (ld && ld->ops->flush_buffer) {
@@ -1147,12 +1145,24 @@ int tty_perform_flush(struct tty_struct *tty, unsigned long arg)
1147 tty_driver_flush_buffer(tty); 1145 tty_driver_flush_buffer(tty);
1148 break; 1146 break;
1149 default: 1147 default:
1150 tty_ldisc_deref(ld);
1151 return -EINVAL; 1148 return -EINVAL;
1152 } 1149 }
1153 tty_ldisc_deref(ld);
1154 return 0; 1150 return 0;
1155} 1151}
1152
1153int tty_perform_flush(struct tty_struct *tty, unsigned long arg)
1154{
1155 struct tty_ldisc *ld;
1156 int retval = tty_check_change(tty);
1157 if (retval)
1158 return retval;
1159
1160 ld = tty_ldisc_ref_wait(tty);
1161 retval = __tty_perform_flush(tty, arg);
1162 if (ld)
1163 tty_ldisc_deref(ld);
1164 return retval;
1165}
1156EXPORT_SYMBOL_GPL(tty_perform_flush); 1166EXPORT_SYMBOL_GPL(tty_perform_flush);
1157 1167
1158int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file, 1168int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
@@ -1191,7 +1201,7 @@ int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
1191 } 1201 }
1192 return 0; 1202 return 0;
1193 case TCFLSH: 1203 case TCFLSH:
1194 return tty_perform_flush(tty, arg); 1204 return __tty_perform_flush(tty, arg);
1195 default: 1205 default:
1196 /* Try the mode commands */ 1206 /* Try the mode commands */
1197 return tty_mode_ioctl(tty, file, cmd, arg); 1207 return tty_mode_ioctl(tty, file, cmd, arg);