[PATCH] Cygwin: pty: keep interactive console input for native programs via Cygwin
Johannes Schindelin
Johannes.Schindelin@gmx.de
Tue Jul 21 23:18:16 GMT 2026
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 <johannes.schindelin@gmx.de>
> >
> > 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.
> >
> > 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:
> >
> > git -c 'alias.console-probe=!powershell.exe -NoLogo -NoProfile -Command "
> > Write-Output ([Console]::IsInputRedirected)
> > try {
> > [void][Console]::KeyAvailable
> > exit 0
> > } catch {
> > exit 1
> > }
> > "' console-probe
> >
> > Running this command with a Win32 version of `git.exe` currently prints
> > `True` and exits with exit code 1. In the latest official release, where
> > this bug is not present, it prints `False` and results in exit code 0.
> >
> > 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.
> >
> > `open_with_arch()` calls `open()`, copies the first pty fhandler's state
> > 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.
> >
> > 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 value
> > closes the new handle instead. The nested native program then receives
> > unusable console input.
> >
> > Preserve usable console input by moving the unchanged transactional pcon
> > handle adoption to `open()`, before the archetype snapshot. The archetype
> > then receives valid pcon handles, all pty fd fhandlers inherit live
> > handles, and the superseded raw pipe handles are closed exactly once.
> >
> > This commit is best viewed with `--color-moved`.
> >
> > 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 <johannes.schindelin@gmx.de>
>
> How can I reproduce the issue in cygwin?
> I tried:
> $ git -c 'alias.console-probe=!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/mingw64/bin/git.exe' -c 'alias.console-probe=!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=!powershell.exe -NoLogo -NoProfile -Command "
> Write-Output ([Console]::IsInputRedirected)
> try {
> [void][Console]::KeyAvailable
> exit 0
> } catch {
> exit 1
> }
> "' console-probe
> False
> $
>
> 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=MINGW64` (which stops that
PATH-editing logic of `git.exe`):
MSYSTEM=MINGW64 PATH=/cygdrive/c/Program\ Files/Git/mingw64/bin:/usr/bin:$PATH git -c 'alias.console-probe=!powershell.exe -NoLogo -NoProfile -Command "Write-Output ([Console]::IsInputRedirected); try { [void][Console]::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
More information about the Cygwin-patches
mailing list