⬢ DragonFlyBSD Kernel Audit
DF-5422 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/u4b/serial/usb_serial.c
+++ b/sys/bus/u4b/serial/usb_serial.c
@@ -956,6 +956,25 @@
 	    &sc->sc_close_task[0].hdr,
 	    &sc->sc_close_task[1].hdr);
 
+	/*
+	 * DF-5422: drain pending TX output while t_oproc is still live.
+	 * ucom_close() must not clear UCOM_FLAG_HL_READY (which turns
+	 * ucom_start() into a no-op, usb_serial.c:1734) before the tty
+	 * output queue has drained, else l_close() -> ttylclose() ->
+	 * ttywflush() -> ttywait() can sleep forever on a queue nobody
+	 * wakes ("ttywai").  Bound the drain so an evil/dead device
+	 * cannot wedge close(2) indefinitely.
+	 */
+	lwkt_gettoken(&tp->t_token);
+	if (tp->t_outq.c_cc != 0) {
+		int save_timo = tp->t_timeout;
+
+		tp->t_timeout = 5;	/* seconds */
+		ttywait(tp);
+		tp->t_timeout = save_timo;
+	}
+	lwkt_reltoken(&tp->t_token);
+
 	sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
 
 	if (sc->sc_callback->ucom_stop_read) {
@@ -1929,6 +1948,15 @@
 			break;
 		}
 	}
+
+	/*
+	 * DF-5422: the USB TX completion is the only place that observes
+	 * the final drain of t_outq -- wake TSA_OCOMPLETE/TSA_OLOWAT
+	 * sleepers (close(2)/drain ioctls) or they sleep forever.
+	 */
+	if (tp->t_outq.c_cc == 0)
+		ttwwakeup(tp);
+
 	lwkt_reltoken(&tp->t_token);
 
 	actlen[0] = offset - offset_orig;