Re: [PATCH] Cygwin: pty: keep interactive console input for native programs via Cygwin

Johannes Schindelin <[email protected]> Wed, 22 Jul 2026 01:18:16 +0200 (CEST)
Newsgroups gmane.os.cygwin.patches
Message-ID <[email protected]>
Hi Takashi,

On Tue, 21 Jul 2026, Takashi Yano wrote:

> On Mon, 20 Jul 2026 09:43:18 +0000
> "Johannes Schindelin  wrote:
> > From: Johannes Schindelin <[email protected]>
> >=20
> > Currently, when a native Windows program starts a Cygwin program while=
 a
> > pseudo console is active, and the Cygwin program then starts another
> > native Windows program, the final program can lose access to console
> > input. It then behaves as though its standard input were redirected
> > instead of remaining interactive.
> >=20
> > For example, a native `git.exe` may invoke shell aliases (i.e. execute=
 a
> > shell command) that would in turn call interactive Git commands who
> > would no longer work because their standard input appeared to be
> > redirected. This can be demonstrated as follows:
> >=20
> >   git -c 'alias.console-probe=3D!powershell.exe -NoLogo -NoProfile -Co=
mmand "
> >     Write-Output ([Console]::IsInputRedirected)
> >     try {
> >       [void][Console]::KeyAvailable
> >       exit 0
> >     } catch {
> >       exit 1
> >     }
> >   "' console-probe
> >=20
> > Running this command with a Win32 version of `git.exe` currently print=
s
> > `True` and exits with exit code 1. In the latest official release, whe=
re
> > this bug is not present, it prints `False` and results in exit code 0.
> >=20
> > The reason is to be fonud in the archetype code. Reminder: For each
> > pseudo terminal (pty), the archetype is the shared pty fhandler that
> > owns the underlying native handles and supplies them to every
> > per-file-descriptor fhandler for that pty.
> >=20
> > `open_with_arch()` calls `open()`, copies the first pty fhandler's sta=
te
> > into the archetype, and then calls `open_setup()`. At that stage, pcon
> > handle adoption already took place in `open_setup()`. This was not
> > anticipated by 60a88896dc (Cygwin: pty: do not leak nat handles when
> > adopting the pcon's in open_setup(), 2026-06-25), which tried to fix a
> > leak by closing the superseded native handles as they were replaced in
> > `open_setup()`.  Because `open_with_arch()` had already copied those
> > handle values into the archetype, closing them invalidated the
> > archetype's copies.
> >=20
> > The archetype therefore retained stale values for those closed handles=
,
> > which later pty fd fhandlers would inherit. If Windows reuses one of
> > those values for a newly duplicated pcon handle, closing the stale val=
ue
> > closes the new handle instead. The nested native program then receives
> > unusable console input.
> >=20
> > Preserve usable console input by moving the unchanged transactional pc=
on
> > handle adoption to `open()`, before the archetype snapshot. The archet=
ype
> > then receives valid pcon handles, all pty fd fhandlers inherit live
> > handles, and the superseded raw pipe handles are closed exactly once.
> >=20
> > This commit is best viewed with `--color-moved`.
> >=20
> > Fixes: 60a88896dce0 ("Cygwin: pty: do not leak nat handles when
> >  adopting the pcon's in open_setup()")
> > Assisted-by: GPT-5.6 Sol
> > Signed-off-by: Johannes Schindelin <[email protected]>
>=20
> How can I reproduce the issue in cygwin?
> I tried:
> $ git -c 'alias.console-probe=3D!powershell.exe -NoLogo -NoProfile -Comm=
and "
>     Write-Output ([Console]::IsInputRedirected)
>     try {
>       [void][Console]::KeyAvailable
>       exit 0
>     } catch {
>       exit 1
>     }
>   "' console-probe
> False
> $ '/cygdrive/c/Program Files/Git/mingw64/bin/git.exe' -c 'alias.console-=
probe=3D!powershell.exe -NoLogo -NoProfile -Command "
>     Write-Output ([Console]::IsInputRedirected)
>     try {
>       [void][Console]::KeyAvailable
>       exit 0
>     } catch {
>       exit 1
>     }
>   "' console-probe
> False
> $ '/cygdrive/c/Program Files/Git/bin/git.exe'  -c 'alias.console-probe=
=3D!powershell.exe -NoLogo -NoProfile -Command "
>     Write-Output ([Console]::IsInputRedirected)
>     try {
>       [void][Console]::KeyAvailable
>       exit 0
>     } catch {
>       exit 1
>     }
>   "' console-probe
> False
> $
>=20
> All look successfull on master branch...

I'm sorry, I should have clarified several things:

- First of all, this reproducer has to be run in MinTTY.

- Second, it does _not_ actually reproduce the bug with the Cygwin runtime
  built from cygwin/master. It only reproduces in Git for Windows' flavor
  of the MSYS2 runtime because of the backports of:

  - 6eed1ef748 (Cygwin: pty: detect pcon-backed pty for non-Cygwin-spawned
    children, 2026-04-30)

  - b34394d456 (Cygwin: pty: Fixup pty state after a cygwin app exits,
    2026-06-13)

  - 60a88896dc (Cygwin: pty: do not leak nat handles when adopting the
    pcon's in open_setup(), 2026-06-25)

  The combination of these three commits is the trifecta that surfaces
  that bug, and the same issue _does_ reproduce when cherry-picking those
  backports on top of `cygwin-3_6-branch`. For your convenience, I have
  pushed that branch to
  https://github.com/dscho/msys2-runtime/commits/minimal-backports-for-nat=
-leakfix-bug

- The reproducer not only requires the use of Git for Windows' `git.exe`
  (as an easy way to run a Win32 program that calls a non-Win32 Bash that
  in turn calls a Win32 program again, I should probably have come up with
  a command-line that uses powershell.exe instead of git.exe). In
  addition, `git.exe`'s _strong_ preference for _its own_ `sh.exe` needs
  to be side-stepped by defining `MSYSTEM=3DMINGW64` (which stops that
  PATH-editing logic of `git.exe`):

  MSYSTEM=3DMINGW64 PATH=3D/cygdrive/c/Program\ Files/Git/mingw64/bin:/usr=
/bin:$PATH git -c 'alias.console-probe=3D!powershell.exe -NoLogo -NoProfil=
e -Command "Write-Output ([Console]::IsInputRedirected); try { [void][Cons=
ole]::KeyAvailable; exit 0} catch {exit 1}"' console-probe

However, as I mentioned, this does not reproduce the bug on
`cygwin/master`. The reason is that bbd3710fc8 (Cygwin: console: Set
console mode only if std{in,out,err} is console, 2025-07-03) _hides_ the
part of the bug that the reproducer exercises. But it does not _fix_ the
bug, it's just harder to trigger.

The fix I proposed is still necessary: In `open_with_arch()` the order is:
`open()` -> archetype snapshot (`copy_from`) -> `open_setup()`. For a
pcon-activated pty, `open()` installs the cyg master-side duplicates into
the two nat slots; the archetype then snapshots those slots; then
`open_setup()` (as written by `60a88896dc`) closes those two handles and
installs the pcon duplicates in their place. So the shared, long-lived
archetype is left owning two handle values that were just closed.

The next descriptor opened for the same pty takes `copy_from(archetype)`,
inherits those two closed values, and its own `open_setup()` calls
`CloseHandle` on them again: a close of an already-closed, hence
possibly-reused, handle. Worse, in that same `open_setup()` the fresh pcon
duplications are allocated while those values are free, so one can be
handed back the very value that's about to be closed. That's a "close a
stale value, kill a live handle" failure, and nothing in master's
structure prevents it.

Ciao,
Johannes