[git commit] telnet: fix false positive "Error writing to foreign host"

Denys Vlasenko vda.linux at googlemail.com
Tue Feb 24 22:47:52 UTC 2026


commit: https://git.busybox.net/busybox/commit/?id=c920ecd73df764ade3d02444d24222d6244dc826
branch: https://git.busybox.net/busybox/log/?h=master

function                                             old     new   delta
write_to_net                                         620     625      +5

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 networking/telnet.c | 62 +++++++++++++++++++++++++++++------------------------
 1 file changed, 34 insertions(+), 28 deletions(-)

diff --git a/networking/telnet.c b/networking/telnet.c
index 66fc0bdf1..a0eadc91b 100644
--- a/networking/telnet.c
+++ b/networking/telnet.c
@@ -164,8 +164,8 @@ struct globals {
 #define INITIAL_SENT    (1 << 2)
 #define DO_TERMIOS      (1 << 3)
 
-	byte            word_aligned_bytes[2];
-#define changed         word_aligned_bytes[0]
+	byte                word_aligned_bytes[2];
+#define changes_seen        word_aligned_bytes[0]
 #define CHANGED_ECHO        (1 << 0)
 #define CHANGED_NAWS        (1 << 1)
 // These happen only once:
@@ -173,14 +173,17 @@ struct globals {
 #define CHANGED_TTYPE       (1 << 3)
 #define CHANGED_NEW_ENVIRON (1 << 4)
 // The second byte is changed async, by signal handler:
-#define got_SIGWINCH    word_aligned_bytes[1]
-#define G_changed_word  (*(uint16_t*)G.word_aligned_bytes)
+#define got_SIGWINCH        word_aligned_bytes[1]
+#define G_changes_or_WINCH  (*(uint16_t*)G.word_aligned_bytes)
+// The "shared word" trick is unsafe on only-word-store arches such as DEC Alpha or SHARC,
+// but Alpha retired in 2004 and SHARC does not run linux (it's a DSP).
 
 	byte            optstate_ECHO;
-#define OPT_ECHO_ON      1
-// We operate tty in rawmode only when echo ON (IOW: we saw server say that)
+// On program start:
+#define OPT_ECHO_UNKNOWN 0xff
 #define OPT_ECHO_OFF     0
-#define OPT_ECHO_UNKNOWN 0xff /* on program start */
+// We operate tty in rawmode only when echo ON (IOW: we saw server say that)
+#define OPT_ECHO_ON      1
 
 	byte            echo_sga_response_size;
 
@@ -405,7 +408,7 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
 {
 	int count;
 
-	log1("changed:%x flags:%x", G.changed, G.flags);
+	log1("changed:%x flags:%x", G.changes_seen, G.flags);
 
 	count = remaining_free_bytes(conn->size);
 	// As soon as we see any DO/DONT/WILL/WONT known to us,
@@ -417,26 +420,26 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
 	// only if that option was requested.
 	// We repeatedly send only NAWS (when our window changes).
 	// Repeated DO requests are ignored.
-	if (G.changed
+	if (G.changes_seen
 	 && (count >= G.echo_sga_response_size)
 	) {
-		if (G.changed & CHANGED_ECHO) {
+		if (G.changes_seen & CHANGED_ECHO) {
 			// Server said WILL/WONT ECHO - confirm every time
 			log1("C:%s ECHO", G.optstate_ECHO ? "DO" : "DONT");
 			put_iac3_IAC_x_y(G.optstate_ECHO ? DO : DONT, TELOPT_ECHO);
 		}
-		if (G.changed & CHANGED_SGA) {
+		if (G.changes_seen & CHANGED_SGA) {
 			// Server said WILL SGA - confirm once
 			log1("C:DO SGA");
 			put_iac3_IAC_x_y(DO, TELOPT_SGA);
 			G.flags |= FLAGS_SGA_SEEN; // remember we did it
-			G.changed -= CHANGED_SGA;
+			G.changes_seen -= CHANGED_SGA;
 		}
-		G.changed &= ~(CHANGED_ECHO|CHANGED_SGA);
+		G.changes_seen &= ~(CHANGED_ECHO|CHANGED_SGA);
 
 		if (!(G.flags & INITIAL_SENT)) {
 			// From now on, we'll only do DO/DONT ECHO and maybe DO SGA
-			// in the "if (G.changed)" block.
+			// in the "if (G.changes_seen)" block.
 			G.flags |= INITIAL_SENT;
 			G.echo_sga_response_size = (G.flags & FLAGS_SGA_SEEN) ? 3 : 6;
 
@@ -466,9 +469,9 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
 	// Therefore we always send WILL X before SB X...
 #if ENABLE_FEATURE_TELNET_WIDTH
 	if (remaining_free_bytes(conn->size) > MAX_NAWS_SIZE) {
-		if (G.changed & CHANGED_NAWS) {
+		if (G.changes_seen & CHANGED_NAWS) {
 			G.flags |= FLAGS_NAWS_ON; // remember we did it
-			G.changed -= CHANGED_NAWS;
+			G.changes_seen -= CHANGED_NAWS;
 			goto generate_naws;
 		}
 		// Handle window resize: send updated NAWS
@@ -484,23 +487,23 @@ static void handle_changes_in_options(stdin_to_net_t *conn)
 	}
 #endif
 #if ENABLE_FEATURE_TELNET_TTYPE
-	if ((G.changed & CHANGED_TTYPE)
+	if ((G.changes_seen & CHANGED_TTYPE)
 	 && remaining_free_bytes(conn->size) > 6 + 2 * strlen(G.ttype)
 	) {
 		log1("C:SB %s '%s'", "TTYPE", G.ttype);
 		put_iac_subopt(TELOPT_TTYPE, G.ttype);
 		G.ttype = NULL; // remember we did it
-		G.changed -= CHANGED_TTYPE;
+		G.changes_seen -= CHANGED_TTYPE;
 	}
 #endif
 #if ENABLE_FEATURE_TELNET_AUTOLOGIN
-	if ((G.changed & CHANGED_NEW_ENVIRON)
+	if ((G.changes_seen & CHANGED_NEW_ENVIRON)
 	 && remaining_free_bytes(conn->size) > 12 + 2 * strlen(G.autologin)
 	) {
 		log1("C:SB %s '%s'", "NEW_ENVIRON", G.autologin);
 		put_iac_subopt_autologin(G.autologin);
 		G.autologin = NULL; // remember we did it
-		G.changed -= CHANGED_NEW_ENVIRON;
+		G.changes_seen -= CHANGED_NEW_ENVIRON;
 	}
 #endif
 }
@@ -556,7 +559,7 @@ static void show_menu(void)
 			announce_rawmode(); /* no "_and_switch_": we are already in rawmode */
  echo_changed:
 			if (G.flags & INITIAL_SENT)
-				G.changed |= CHANGED_ECHO; /* inform the server at next send */
+				G.changes_seen |= CHANGED_ECHO; /* inform the server at next send */
 			return;
 		}
 		break;
@@ -663,7 +666,7 @@ static int have_data_to_write_to_net(void *this)
 		ioloop_remove_conn(conn->io, (connection_t*)conn);
 		return -1;
 	}
-	return conn->size > 0 || G_changed_word != 0;
+	return conn->size > 0 || G_changes_or_WINCH != 0;
 }
 
 static int write_to_net(void *this)
@@ -672,10 +675,13 @@ static int write_to_net(void *this)
 	int count;
 
 	/* Do we have option or NAWS changes to handle? */
-	if (G_changed_word)
+	if (G_changes_or_WINCH)
 		handle_changes_in_options(conn); /* yes */
 
 	count = MIN(BUFSIZE - conn->wridx, conn->size);
+	// can be zero due to handle_changes_in_options()
+	if (count == 0)
+		return 0;
 	count = safe_write(conn->write_fd, BUF_TTY2NET + conn->wridx, count);
 	if (count <= 0) {
 		if (count < 0 && errno == EAGAIN)
@@ -814,7 +820,7 @@ static int read_from_net(void *this)
 					break;
 				case TELOPT_SGA: /* Remote option: "suppress go ahead" */
 					if (will && !(G.flags & FLAGS_SGA_SEEN))
-						G.changed |= CHANGED_SGA;
+						G.changes_seen |= CHANGED_SGA;
 					break;
 				}
 			} else if (conn->negotiation_verb == DO) {
@@ -823,19 +829,19 @@ static int read_from_net(void *this)
 				case TELOPT_TTYPE: /* Local option: we send terminal type */
 					log1("TTYPE:'%s' %ssetting CHANGED_TTYPE", G.ttype, G.ttype ? "" : "not ");
 					if (G.ttype)
-						G.changed |= CHANGED_TTYPE;
+						G.changes_seen |= CHANGED_TTYPE;
 					break;
 #endif
 #if ENABLE_FEATURE_TELNET_AUTOLOGIN
 				case TELOPT_NEW_ENVIRON: /* Local option: we send username */
 					if (G.autologin)
-						G.changed |= CHANGED_NEW_ENVIRON;
+						G.changes_seen |= CHANGED_NEW_ENVIRON;
 					break;
 #endif
 #if ENABLE_FEATURE_TELNET_WIDTH
 				case TELOPT_NAWS: /* Local option: we send window size */
 					if (!(G.flags & FLAGS_NAWS_ON))
-						G.changed |= CHANGED_NAWS;
+						G.changes_seen |= CHANGED_NAWS;
 					break;
 #endif
 				}
@@ -873,7 +879,7 @@ static int read_from_net(void *this)
 
 	if (oldstate_ECHO != G.optstate_ECHO) {
 		/* Tell net writer to generate a confirmation */
-		G.changed |= CHANGED_ECHO;
+		G.changes_seen |= CHANGED_ECHO;
 		/* Print the banner and set termios */
 		if (G.optstate_ECHO == OPT_ECHO_ON)
 			announce_and_switch_to_rawmode();


More information about the busybox-cvs mailing list