[newlib-cygwin/cygwin-3_6-branch] Cygwin: pty: Fix race issue between starting and exiting non-cygwin apps

Takashi Yano via Cygwin-cvs <[email protected]> Wed, 24 Jun 2026 12:33:24 +0000 (GMT)
Newsgroups gmane.os.cygwin.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=3Dnewlib-cygwin.git;h=3Da828b4ef175=
3b3d7b6a581c1c4239dd7c9d320d5

commit a828b4ef1753b3d7b6a581c1c4239dd7c9d320d5
Author: Takashi Yano <[email protected]>
Date:   Thu Jun 11 00:41:34 2026 +0900

    Cygwin: pty: Fix race issue between starting and exiting non-cygwin apps
   =20
    Without this patch, when a non-cygwin program (A) is about to exit, and
    another non-cygwin program (B) is started, input transferring between
    cyg-pipe and nat-pipe may not work as expected. When the non-cygwin
    program (A) exits, input transferring from nat-pipe to cyg-pipe will be
    performed. However, the the non-cygwin program (B) will performs input
    transferring from cyg-pipe to nat-pipe at the same time. The mechanism
    of the problem is as follows.
     1) The the non-cygwin program (A) checks current input pipe state,
        then it is nat-pipe since the this program is a non-cygwin program.
        The program (A) also checks if any handover target exists, but
        it is not found since the program (B) is not started yet. So,
        the program (A) decided to transfer input form nat-pipe to cyg-
        pipe.
     2) Before the non-cygwin (A) program performs input transferring,
        if the non-cygwin program (B) is started and checks the input
        pipe state, it is nat-pipe state, so the non-cygwin program (B)
        does not perform input transferring.
     3) However, just after that, the non-cygwin program (A) performs
        input transferring from nat-pipe to cyg-pipe, so typeahead input
        will be stored in cyg-pipe.
     4) The non-cygwin program (B) cannot read the typeahead input
        because it is now in the cyg-pipe.
   =20
    The following code demonstrates the issue.
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
   =20
      int main(int argc, char *argv[])
      {
        int n =3D 1;
        if (argc > 1)
          n =3D atoi(argv[1]);
        if (fork()) {
          execlp("cmd.exe", "cmd", NULL);
          perror("execlp(\"cmd\"): ");
        }
        for (int i=3D0; i<n; i++) {
          if (fork() =3D=3D 0) {
            execlp("./non-cygwin-app.exe", "non-cygwin-app", "0", NULL);
            perror("execlp(\"non-cygwin-app\"): ");
          }
        }
        return 0;
      }
   =20
    Transferring input itself is guarded by input_mutex, but the pre-
    check is not. With this patch, the guard is enhanced so that the
    state check and transferring input are done in atomic way.
   =20
    Fixes: f20641789427 ("Cygwin: pty: Reduce unecessary input transfer.")
    Signed-off-by: Takashi Yano <[email protected]>
    Reviewed-by: Mark Geisert <[email protected]>
    (cherry picked from commit f3eecb723bed090b73753b3d428329e90c960aac)

Diff:
---
 winsup/cygwin/fhandler/pty.cc           | 98 +++++++++++++++++++----------=
----
 winsup/cygwin/local_includes/fhandler.h |  2 +
 winsup/cygwin/release/3.6.10            |  2 +
 3 files changed, 61 insertions(+), 41 deletions(-)

diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc
index c3895aac8..b1e42dafb 100644
--- a/winsup/cygwin/fhandler/pty.cc
+++ b/winsup/cygwin/fhandler/pty.cc
@@ -492,8 +492,7 @@ fhandler_pty_master::accept_input ()
=20
   HANDLE write_to =3D get_output_handle ();
   tmp_pathbuf tp;
-  if (to_be_read_from_nat_pipe ()
-      && get_ttyp ()->pty_input_state =3D=3D tty::to_nat)
+  if (get_ttyp ()->pty_input_state =3D=3D tty::to_nat)
     {
       /* This code is reached if non-cygwin app is foreground and
 	 pseudo console is not enabled. */
@@ -1109,18 +1108,18 @@ fhandler_pty_slave::reset_switch_to_nat_pipe (void)
 	  mutex_timeout =3D INFINITE;
 	  if (isHybrid)
 	    {
+	      WaitForSingleObject (input_mutex, mutex_timeout);
 	      if (get_ttyp ()->getpgid () =3D=3D myself->pgid
 		  && GetStdHandle (STD_INPUT_HANDLE) =3D=3D get_handle ()
 		  && get_ttyp ()->pty_input_state_eq (tty::to_nat))
 		{
-		  WaitForSingleObject (input_mutex, mutex_timeout);
 		  acquire_attach_mutex (mutex_timeout);
 		  transfer_input (tty::to_cyg, get_handle_nat (), get_ttyp (),
 				  input_available_event,
 				  input_transferred_to_cyg);
 		  release_attach_mutex ();
-		  ReleaseMutex (input_mutex);
 		}
+	      ReleaseMutex (input_mutex);
 	      if (get_ttyp ()->master_is_running_as_service
 		  && get_ttyp ()->pcon_activated)
 		/* If the master is running as service, re-attaching to
@@ -2181,6 +2180,22 @@ fhandler_pty_master::close (int flag)
   return 0;
 }
=20
+line_edit_status
+fhandler_pty_master::line_edit_maybe (const char *ptr, size_t len,
+				      termios &ti, ssize_t *n)
+{
+  DWORD m;
+  if (get_ttyp ()->req_xfer_input
+      && get_ttyp ()->pty_input_state_eq (tty::to_nat))
+    {
+      WriteFile (to_slave_nat, ptr, len, &m, NULL);
+      *n =3D (ssize_t) m;
+      return line_edit_ok;
+    }
+  else
+    return line_edit (ptr, len, ti, n);
+}
+
 ssize_t
 fhandler_pty_master::write (const void *ptr, size_t len)
 {
@@ -2198,6 +2213,25 @@ fhandler_pty_master::write (const void *ptr, size_t =
len)
=20
   get_ttyp ()->discard_input =3D false;
=20
+  /* This input transfer is needed when cygwin-app which is started from
+     non-cygwin app is terminated while pseudo console is disabled. */
+  if (!get_ttyp ()->pcon_activated && !get_ttyp ()->pcon_start
+      && to_be_read_from_nat_pipe ())
+    {
+      WaitForSingleObject (input_mutex, mutex_timeout);
+      if (get_ttyp ()->nat_fg (get_ttyp ()->getpgid ())
+	  && get_ttyp ()->pty_input_state =3D=3D tty::to_cyg)
+	{
+	  acquire_attach_mutex (mutex_timeout);
+	  fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
+					      get_ttyp (),
+					      input_available_event,
+					      input_transferred_to_cyg);
+	  release_attach_mutex ();
+	}
+      ReleaseMutex (input_mutex);
+    }
+
   if (get_ttyp ()->pcon_start)
     { /* Reaches here when pseudo console initialization is on going. */
       /* Pseudo condole support uses "CSI6n" to get cursor position.
@@ -2218,7 +2252,7 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
 	  if (p[i] =3D=3D '\033')
 	    {
 	      if (ixput)
-		line_edit (wpbuf, ixput, ti, &ret);
+		line_edit_maybe (wpbuf, ixput, ti, &ret);
 	      ixput =3D 0;
 	      state =3D 1;
 	      wp_tid =3D _my_tls.thread_id;
@@ -2236,7 +2270,7 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
 		}
 	    }
 	  else
-	    line_edit (p + i, 1, ti, &ret);
+	    line_edit_maybe (p + i, 1, ti, &ret);
 	  len =3D orig_len - i - 1;
 	  ptr =3D p + i + 1;
 	  if (state =3D=3D 1 && wp_tid =3D=3D _my_tls.thread_id && p[i] =3D=3D 'R=
')
@@ -2259,6 +2293,7 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
=20
       if (!get_ttyp ()->pcon_start)
 	{ /* Pseudo console initialization has been done in above code. */
+	  WaitForSingleObject (input_mutex, mutex_timeout);
 	  pinfo pp (get_ttyp ()->pcon_start_pid);
 	  if (get_ttyp ()->switch_to_nat_pipe
 	      && pp && pp->pgid =3D=3D get_ttyp ()->getpgid ()
@@ -2268,8 +2303,9 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
 		{
 		  HANDLE pcon_handle_ready_event =3D
 		    get_ttyp ()->pcon_handle_ready_event;
-		  get_handle_from_process (get_ttyp ()->nat_pipe_owner_pid,
-					   pcon_handle_ready_event);
+		  pcon_handle_ready_event =3D
+		    get_handle_from_process (get_ttyp ()->nat_pipe_owner_pid,
+					     pcon_handle_ready_event);
 		  if (pcon_handle_ready_event)
 		    {
 		      cygwait (pcon_handle_ready_event, INFINITE);
@@ -2280,7 +2316,6 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
=20
 	      /* This accept_input() call is needed in order to transfer input
 		 which is not accepted yet to non-cygwin pipe. */
-	      WaitForSingleObject (input_mutex, mutex_timeout);
 	      if (get_readahead_valid ())
 		accept_input ();
 	      acquire_attach_mutex (mutex_timeout);
@@ -2289,9 +2324,9 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
 						  input_available_event,
 						  input_transferred_to_cyg);
 	      release_attach_mutex ();
-	      ReleaseMutex (input_mutex);
 	    }
 	  get_ttyp ()->req_xfer_input =3D false;
+	  ReleaseMutex (input_mutex);
 	  get_ttyp ()->pcon_start_pid =3D 0;
 	}
       if (len =3D=3D 0)
@@ -2301,7 +2336,7 @@ fhandler_pty_master::write (const void *ptr, size_t l=
en)
   /* Write terminal input to to_slave_nat pipe instead of output_handle
      if current application is native console application. */
   WaitForSingleObject (input_mutex, mutex_timeout);
-  if (to_be_read_from_nat_pipe () && get_ttyp ()->pcon_activated
+  if (get_ttyp ()->pcon_activated
       && get_ttyp ()->pty_input_state =3D=3D tty::to_nat)
     { /* Reaches here when non-cygwin app is foreground and pseudo console
 	 is activated. */
@@ -2385,20 +2420,6 @@ fhandler_pty_master::write (const void *ptr, size_t =
len)
   /* The code path reaches here when pseudo console is not activated
      or cygwin process is foreground even though pseudo console is
      activated. */
-
-  /* This input transfer is needed when cygwin-app which is started from
-     non-cygwin app is terminated if pseudo console is disabled. */
-  if (to_be_read_from_nat_pipe () && !get_ttyp ()->pcon_activated
-      && get_ttyp ()->nat_fg (get_ttyp ()->getpgid ())
-      && get_ttyp ()->pty_input_state =3D=3D tty::to_cyg)
-    {
-      acquire_attach_mutex (mutex_timeout);
-      fhandler_pty_slave::transfer_input (tty::to_nat, from_master,
-					  get_ttyp (), input_available_event,
-					  input_transferred_to_cyg);
-      release_attach_mutex ();
-    }
-
   line_edit_status status =3D line_edit (p, len, ti, &ret);
   ReleaseMutex (input_mutex);
=20
@@ -4337,9 +4358,9 @@ fhandler_pty_slave::setup_for_non_cygwin_app (bool no=
pcon,
 					      const WCHAR *envblock,
 					      bool stdin_is_ptys)
 {
+  WaitForSingleObject (pipe_sw_mutex, INFINITE);
   if (disable_pcon || !term_has_pcon_cap (envblock))
     nopcon =3D true;
-  WaitForSingleObject (pipe_sw_mutex, INFINITE);
   /* Setting switch_to_nat_pipe is necessary even if pseudo console
      will not be activated. */
   fhandler_base *fh =3D ::cygheap->fdtab[0];
@@ -4355,16 +4376,16 @@ fhandler_pty_slave::setup_for_non_cygwin_app (bool =
nopcon,
     pcon_enabled =3D setup_pseudoconsole ();
   ReleaseMutex (pipe_sw_mutex);
   /* For pcon enabled case, transfer_input() is called in master::write() =
*/
+  WaitForSingleObject (input_mutex, mutex_timeout);
   if (!pcon_enabled && get_ttyp ()->getpgid () =3D=3D myself->pgid
       && stdin_is_ptys && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
     {
-      WaitForSingleObject (input_mutex, mutex_timeout);
       acquire_attach_mutex (mutex_timeout);
       transfer_input (tty::to_nat, get_handle (), get_ttyp (),
 		      input_available_event, input_transferred_to_cyg);
       release_attach_mutex ();
-      ReleaseMutex (input_mutex);
     }
+  ReleaseMutex (input_mutex);
 }
=20
 void
@@ -4373,22 +4394,22 @@ fhandler_pty_slave::cleanup_for_non_cygwin_app (han=
dle_set_t *p, tty *ttyp,
 						DWORD force_switch_to)
 {
   ttyp->wait_fwd ();
+  WaitForSingleObject (p->pipe_sw_mutex, INFINITE);
+  WaitForSingleObject (p->input_mutex, mutex_timeout);
   if (nat_pipe_owner_self (ttyp->nat_pipe_owner_pid))
     {
       DWORD switch_to =3D get_winpid_to_hand_over (ttyp, force_switch_to);
       if ((!switch_to && (ttyp->pcon_activated || stdin_is_ptys))
 	  && ttyp->pty_input_state_eq (tty::to_nat))
 	{
-	  WaitForSingleObject (p->input_mutex, mutex_timeout);
 	  acquire_attach_mutex (mutex_timeout);
 	  transfer_input (tty::to_cyg, p->from_master_nat, ttyp,
 			  p->input_available_event,
 			  p->input_transferred_to_cyg);
 	  release_attach_mutex ();
-	  ReleaseMutex (p->input_mutex);
 	}
     }
-  WaitForSingleObject (p->pipe_sw_mutex, INFINITE);
+  ReleaseMutex (p->input_mutex);
   if (ttyp->pcon_activated)
     close_pseudoconsole (ttyp, force_switch_to);
   else
@@ -4402,27 +4423,23 @@ fhandler_pty_slave::setpgid_aux (pid_t pid)
   reset_switch_to_nat_pipe ();
=20
   WaitForSingleObject (pipe_sw_mutex, INFINITE);
+  WaitForSingleObject (input_mutex, mutex_timeout);
   bool was_nat_fg =3D get_ttyp ()->nat_fg (tc ()->pgid);
   bool nat_fg =3D get_ttyp ()->nat_fg (pid);
   if (!was_nat_fg && nat_fg && get_ttyp ()->switch_to_nat_pipe
       && get_ttyp ()->pty_input_state_eq (tty::to_cyg))
     {
-      ReleaseMutex (pipe_sw_mutex);
-      WaitForSingleObject (input_mutex, mutex_timeout);
       acquire_attach_mutex (mutex_timeout);
       transfer_input (tty::to_nat, get_handle (), get_ttyp (),
 		      input_available_event, input_transferred_to_cyg);
       release_attach_mutex ();
-      ReleaseMutex (input_mutex);
     }
   else if (was_nat_fg && !nat_fg && get_ttyp ()->switch_to_nat_pipe
 	   && get_ttyp ()->pty_input_state_eq (tty::to_nat))
     {
-      ReleaseMutex (pipe_sw_mutex);
       bool attach_restore =3D false;
       HANDLE from =3D get_handle_nat ();
       DWORD resume_pid =3D 0;
-      WaitForSingleObject (input_mutex, mutex_timeout);
       if (get_ttyp ()->pcon_activated && get_ttyp ()->nat_pipe_owner_pid
 	  && !get_console_process_id (get_ttyp ()->nat_pipe_owner_pid, true))
 	{
@@ -4440,10 +4457,9 @@ fhandler_pty_slave::setpgid_aux (pid_t pid)
 	resume_from_temporarily_attach (resume_pid);
       else
 	release_attach_mutex ();
-      ReleaseMutex (input_mutex);
     }
-  else
-    ReleaseMutex (pipe_sw_mutex);
+  ReleaseMutex (input_mutex);
+  ReleaseMutex (pipe_sw_mutex);
 }
=20
 bool
@@ -4453,8 +4469,8 @@ fhandler_pty_master::need_send_ctrl_c_event ()
      apps will be done in pseudo console, therefore, sending it in
      fhandler_pty_master::write() duplicates that event for non-cygwin
      apps. So return false if pseudo console is activated. */
-  return !(to_be_read_from_nat_pipe () && get_ttyp ()->pcon_activated
-    && get_ttyp ()->pty_input_state =3D=3D tty::to_nat);
+  return !(get_ttyp ()->pcon_activated
+	   && get_ttyp ()->pty_input_state =3D=3D tty::to_nat);
 }
=20
 void
diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_=
includes/fhandler.h
index 09e04c14f..3a4bfac17 100644
--- a/winsup/cygwin/local_includes/fhandler.h
+++ b/winsup/cygwin/local_includes/fhandler.h
@@ -2632,6 +2632,8 @@ public:
   void get_master_fwd_thread_param (master_fwd_thread_param_t *p);
   bool need_send_ctrl_c_event ();
   void apply_line_edit_to_transferred_input ();
+  line_edit_status line_edit_maybe (const char *p, size_t len, termios&,
+				    ssize_t *n);
 };
=20
 class fhandler_dev_null: public fhandler_base
diff --git a/winsup/cygwin/release/3.6.10 b/winsup/cygwin/release/3.6.10
index 482073192..e37ccb391 100644
--- a/winsup/cygwin/release/3.6.10
+++ b/winsup/cygwin/release/3.6.10
@@ -14,3 +14,5 @@ Fixes:
=20
 - Fix broken cursor position report resonse when a non-cygwin app starts.
   Addresses: https://cygwin.com/pipermail/cygwin/2026-June/259776.html
+
+- Fix race issue between starting and exiting non-cygwin apps in pty.