To: linux-m68k@lists.linux-m68k.org
Subject: L68K: Race condition in N_TTY line disciplin
X-Yow: Ask me the DIFFERENCE between PHIL SILVERS and ALEXANDER HAIG!!
From: Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
Date: 15 May 1998 11:15:37 +0200
Sender: owner-linux-m68k@phil.uni-sb.de

Hi!

This patch fixes a race condition in the N_TTY line disciplin, due to the
sharing of tty->read_cnt between the reader and the writer of the tty.  I
have already send that patch to linux-kernel, but nobody cares, of course.


Andreas.

----------------------------------------------------------------------
--- linux-2.1.100/drivers/char/n_tty.c.~1~	Wed Mar 18 19:45:51 1998
+++ linux-2.1.100/drivers/char/n_tty.c	Sun May 10 16:13:59 1998
@@ -59,10 +59,10 @@
 
 static inline void put_tty_queue(unsigned char c, struct tty_struct *tty)
 {
-	if (tty->read_cnt < N_TTY_BUF_SIZE) {
+	if (atomic_read(&tty->read_cnt) < N_TTY_BUF_SIZE) {
 		tty->read_buf[tty->read_head] = c;
 		tty->read_head = (tty->read_head + 1) & (N_TTY_BUF_SIZE-1);
-		tty->read_cnt++;
+		atomic_inc(&tty->read_cnt);
 	}
 }
 
@@ -85,7 +85,8 @@
  */
 static void reset_buffer_flags(struct tty_struct *tty)
 {
-	tty->read_head = tty->read_tail = tty->read_cnt = 0;
+	tty->read_head = tty->read_tail = 0;
+	atomic_set(&tty->read_cnt, 0);
 	tty->canon_head = tty->canon_data = tty->erasing = 0;
 	memset(&tty->read_flags, 0, sizeof tty->read_flags);
 	check_unthrottle(tty);
@@ -120,7 +121,7 @@
 			tty->canon_head - tty->read_tail :
 			tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail);
 	}
-	return tty->read_cnt;
+	return atomic_read(&tty->read_cnt);
 }
 
 /*
@@ -293,14 +294,16 @@
 		kill_type = WERASE;
 	else {
 		if (!L_ECHO(tty)) {
-			tty->read_cnt -= ((tty->read_head - tty->canon_head) &
-					  (N_TTY_BUF_SIZE - 1));
+			atomic_sub(((tty->read_head - tty->canon_head) &
+				    (N_TTY_BUF_SIZE - 1)),
+				   &tty->read_cnt);
 			tty->read_head = tty->canon_head;
 			return;
 		}
 		if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) {
-			tty->read_cnt -= ((tty->read_head - tty->canon_head) &
-					  (N_TTY_BUF_SIZE - 1));
+			atomic_sub(((tty->read_head - tty->canon_head) &
+				    (N_TTY_BUF_SIZE - 1)),
+				   &tty->read_cnt);
 			tty->read_head = tty->canon_head;
 			finish_erasing(tty);
 			echo_char(KILL_CHAR(tty), tty);
@@ -324,7 +327,7 @@
 				break;
 		}
 		tty->read_head = head;
-		tty->read_cnt--;
+		atomic_dec(&tty->read_cnt);
 		if (L_ECHO(tty)) {
 			if (L_ECHOPRT(tty)) {
 				if (!tty->erasing) {
@@ -482,7 +485,7 @@
 		finish_erasing(tty);
 		tty->lnext = 0;
 		if (L_ECHO(tty)) {
-			if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+			if (atomic_read(&tty->read_cnt) >= N_TTY_BUF_SIZE-1) {
 				put_char('\a', tty); /* beep if no space */
 				return;
 			}
@@ -561,7 +564,7 @@
 		}
 		if (c == '\n') {
 			if (L_ECHO(tty) || L_ECHONL(tty)) {
-				if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+				if (atomic_read(&tty->read_cnt) >= N_TTY_BUF_SIZE-1) {
 					put_char('\a', tty);
 					return;
 				}
@@ -581,7 +584,7 @@
 			 * XXX are EOL_CHAR and EOL2_CHAR echoed?!?
 			 */
 			if (L_ECHO(tty)) {
-				if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+				if (atomic_read(&tty->read_cnt) >= N_TTY_BUF_SIZE-1) {
 					put_char('\a', tty);
 					return;
 				}
@@ -612,7 +615,7 @@
 	
 	finish_erasing(tty);
 	if (L_ECHO(tty)) {
-		if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+		if (atomic_read(&tty->read_cnt) >= N_TTY_BUF_SIZE-1) {
 			put_char('\a', tty); /* beep if no space */
 			return;
 		}
@@ -634,7 +637,7 @@
 
 static int n_tty_receive_room(struct tty_struct *tty)
 {
-	int	left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
+	int	left = N_TTY_BUF_SIZE - atomic_read(&tty->read_cnt) - 1;
 
 	/*
 	 * If we are doing input canonicalization, and there are no
@@ -662,19 +665,20 @@
 		return;
 
 	if (tty->real_raw) {
-		i = MIN(count, MIN(N_TTY_BUF_SIZE - tty->read_cnt,
+		int read_cnt = atomic_read(&tty->read_cnt);
+		i = MIN(count, MIN(N_TTY_BUF_SIZE - read_cnt,
 				   N_TTY_BUF_SIZE - tty->read_head));
 		memcpy(tty->read_buf + tty->read_head, cp, i);
 		tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
-		tty->read_cnt += i;
+		read_cnt += i;
 		cp += i;
 		count -= i;
 
-		i = MIN(count, MIN(N_TTY_BUF_SIZE - tty->read_cnt,
+		i = MIN(count, MIN(N_TTY_BUF_SIZE - read_cnt,
 			       N_TTY_BUF_SIZE - tty->read_head));
 		memcpy(tty->read_buf + tty->read_head, cp, i);
 		tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
-		tty->read_cnt += i;
+		atomic_set(&tty->read_cnt, read_cnt + i);
 	} else {
 		for (i=count, p = cp, f = fp; i; i--, p++) {
 			if (f)
@@ -703,7 +707,7 @@
 			tty->driver.flush_chars(tty);
 	}
 
-	if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) {
+	if (!tty->icanon && (atomic_read(&tty->read_cnt) >= tty->minimum_to_wake)) {
 		if (tty->fasync)
 			kill_fasync(tty->fasync, SIGIO);
 		if (tty->read_wait)
@@ -828,7 +832,7 @@
 	if (tty->icanon) {
 		if (tty->canon_data)
 			return 1;
-	} else if (tty->read_cnt >= (amt ? amt : 1))
+	} else if (atomic_read(&tty->read_cnt) >= (amt ? amt : 1))
 		return 1;
 
 	return 0;
@@ -848,14 +852,16 @@
 
 {
 	ssize_t n;
+	int read_cnt = atomic_read(&tty->read_cnt);
 
-	n = MIN(*nr, MIN(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail));
+	n = MIN(*nr, MIN(read_cnt, N_TTY_BUF_SIZE - tty->read_tail));
 	if (!n)
 		return;
 	/* N.B. copy_to_user may work only partially */
 	n -= copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
 	tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
-	tty->read_cnt -= n;
+	/* tty->read_cnt may have changed while we slept. */
+	atomic_sub(n, &tty->read_cnt);
 	*b += n;
 	*nr -= n;
 }
@@ -973,7 +979,7 @@
 
 		if (tty->icanon) {
 			/* N.B. avoid overrun if nr == 0 */
-			while (nr && tty->read_cnt) {
+			while (nr && atomic_read(&tty->read_cnt)) {
  				int eol;
 
 				eol = test_and_clear_bit(tty->read_tail,
@@ -981,7 +987,7 @@
 				c = tty->read_buf[tty->read_tail];
 				tty->read_tail = ((tty->read_tail+1) &
 						  (N_TTY_BUF_SIZE-1));
-				tty->read_cnt--;
+				atomic_dec(&tty->read_cnt);
 
 				if (!eol || (c != __DISABLED_CHAR)) {
 					put_user(c, b++);
--- linux-2.1.100/drivers/char/tty_ioctl.c.~1~	Wed Mar 18 19:49:37 1998
+++ linux-2.1.100/drivers/char/tty_ioctl.c	Sun May 10 16:27:05 1998
@@ -120,7 +120,7 @@
 		tty->erasing = 0;
 	}
 	sti();
-	if (canon_change && !L_ICANON(tty) && tty->read_cnt)
+	if (canon_change && !L_ICANON(tty) && atomic_read(&tty->read_cnt))
 		/* Get characters left over from canonical mode. */
 		wake_up_interruptible(&tty->read_wait);
 
@@ -477,7 +477,7 @@
 					tty->driver.chars_in_buffer(tty) : 0,
 					(int *) arg);
 		case TIOCINQ:
-			retval = tty->read_cnt;
+			retval = atomic_read(&tty->read_cnt);
 			if (L_ICANON(tty))
 				retval = inq_canon(tty);
 			return put_user(retval, (unsigned int *) arg);
--- linux-2.1.100/include/linux/tty.h.~1~	Wed Mar 18 20:08:11 1998
+++ linux-2.1.100/include/linux/tty.h	Sun May 10 16:05:32 1998
@@ -24,6 +24,7 @@
 #include <linux/serialP.h>
 
 #include <asm/system.h>
+#include <asm/atomic.h>
 
 
 /*
@@ -254,7 +255,7 @@
 	char *read_buf;
 	int read_head;
 	int read_tail;
-	int read_cnt;
+	atomic_t read_cnt;
 	unsigned long read_flags[N_TTY_BUF_SIZE/(8*sizeof(unsigned long))];
 	int canon_data;
 	unsigned long canon_head;
