From 23748c821a767c4a4e694c32e88ecd7a6c99f5cf Mon Sep 17 00:00:00 2001 From: Takashi Yano Date: Fri, 19 Dec 2025 11:26:34 +0900 Subject: [PATCH 1/6] Cygwin: termios: Make is_console_app() return true for unknown If is_console_app() returns false, it means the app is GUI. In this case, standard handles would not be setup for non-cygwin app. Therefore, it is safer to return true for unknown case. Setting-up standard handles for GUI apps is poinless indeed, but not unsafe. Fixes: bb4285206207 ("Cygwin: pty: Implement new pseudo console support.") Reviewed-by: Johannes Schindelin Signed-off-by: Takashi Yano Signed-off-by: Johannes Schindelin --- winsup/cygwin/fhandler/termios.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc index a3cecdb6fc..645aa1afa8 100644 --- a/winsup/cygwin/fhandler/termios.cc +++ b/winsup/cygwin/fhandler/termios.cc @@ -719,7 +719,9 @@ is_console_app (const WCHAR *filename) wchar_t *e = wcsrchr (filename, L'.'); if (e && (wcscasecmp (e, L".bat") == 0 || wcscasecmp (e, L".cmd") == 0)) return true; - return false; + /* Return true for unknown to avoid standard handles from being unset. + Setting-up standard handles for GUI apps is pointless, but not unsafe. */ + return true; } int From dd892f002a1189cfeac8340f1d4eece5b9ab90ba Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 19 Dec 2025 11:26:35 +0900 Subject: [PATCH 2/6] Cygwin: is_console_app(): do handle errors When that function was introduced in bb4285206207 (Cygwin: pty: Implement new pseudo console support., 2020-08-19) (back then, it was added to `spawn.cc`, later it was moved to `fhandler/termios.cc` in 32d6a6cb5f1e (Cygwin: pty, console: Encapsulate spawn.cc code related to pty/console., 2022-11-19)), it was implemented with strong assumptions that neither creating the file handle nor reading 1024 bytes from said handle could fail. This assumption, however, is incorrect. Concretely, I encountered the case where `is_console_app()` needed to open an app execution alias, failed to do so, and still tried to read from the invalid handle. Let's add some error handling to that function. Fixes: bb4285206207 (Cygwin: pty: Implement new pseudo console support., 2020-08-19) Co-authored-by: Takashi Yano Signed-off-by: Johannes Schindelin --- winsup/cygwin/fhandler/termios.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc index 645aa1afa8..61d01e9310 100644 --- a/winsup/cygwin/fhandler/termios.cc +++ b/winsup/cygwin/fhandler/termios.cc @@ -707,10 +707,14 @@ is_console_app (const WCHAR *filename) HANDLE h; h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (h == INVALID_HANDLE_VALUE) + return true; char buf[1024]; DWORD n; - ReadFile (h, buf, sizeof (buf), &n, 0); + BOOL res = ReadFile (h, buf, sizeof (buf), &n, 0); CloseHandle (h); + if (!res) + return true; /* The offset of Subsystem is the same for both IMAGE_NT_HEADERS32 and IMAGE_NT_HEADERS64, so only IMAGE_NT_HEADERS32 is used here. */ IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4); From 697457a34b4fc7ee422fc90de2f7e782882eec6d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 19 Dec 2025 11:26:36 +0900 Subject: [PATCH 3/6] Cygwin: is_console_app(): deal with the `.bat`/`.cmd` file extensions first This function contains special handling of these file extensions, treating them as console applications always, even if the first 1024 bytes do not contain a PE header with the console bits set. However, Batch and Command files are never expected to have such a header, therefore opening them and reading their first bytes is a waste of time. Let's honor the best practice to deal with easy conditions that allow early returns first. Fixes: bb4285206207 (Cygwin: pty: Implement new pseudo console suppot., 2020-08-19) Reviewed-by: Takashi Yano Signed-off-by: Johannes Schindelin --- winsup/cygwin/fhandler/termios.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc index 61d01e9310..8f98e42a45 100644 --- a/winsup/cygwin/fhandler/termios.cc +++ b/winsup/cygwin/fhandler/termios.cc @@ -704,6 +704,9 @@ fhandler_termios::fstat (struct stat *buf) static bool is_console_app (const WCHAR *filename) { + wchar_t *e = wcsrchr (filename, L'.'); + if (e && (wcscasecmp (e, L".bat") == 0 || wcscasecmp (e, L".cmd") == 0)) + return true; HANDLE h; h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); @@ -720,9 +723,6 @@ is_console_app (const WCHAR *filename) IMAGE_NT_HEADERS32 *p = (IMAGE_NT_HEADERS32 *) memmem (buf, n, "PE\0\0", 4); if (p && (char *) &p->OptionalHeader.DllCharacteristics <= buf + n) return p->OptionalHeader.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI; - wchar_t *e = wcsrchr (filename, L'.'); - if (e && (wcscasecmp (e, L".bat") == 0 || wcscasecmp (e, L".cmd") == 0)) - return true; /* Return true for unknown to avoid standard handles from being unset. Setting-up standard handles for GUI apps is pointless, but not unsafe. */ return true; From 835bcd9d01ac6bf5e0bf7047f6f808c617ef9635 Mon Sep 17 00:00:00 2001 From: Takashi Yano Date: Fri, 19 Dec 2025 11:26:37 +0900 Subject: [PATCH 4/6] Cygwin: path: Implement path_conv::is_app_execution_alias() An app execution alias cannot be opened for read (CreateFile() with GENERIC_READ fails with ERROR_CANT_ACCESS_FILE) because it does not resolve the reparse point for app execution alias. Therefore, we need to know if the path is an app execution alias when opening it. This patch adds new api path_conv::is_app_execution_alias() for that purpose. Reviewed-by: Johannes Schindelin Signed-off-by: Takashi Yano Signed-off-by: Johannes Schindelin --- winsup/cygwin/local_includes/path.h | 5 +++++ winsup/cygwin/path.cc | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/winsup/cygwin/local_includes/path.h b/winsup/cygwin/local_includes/path.h index a9ce2c7e4b..ad142ddd3a 100644 --- a/winsup/cygwin/local_includes/path.h +++ b/winsup/cygwin/local_includes/path.h @@ -79,6 +79,7 @@ enum path_types PATH_SOCKET = _BIT ( 5), /* AF_UNIX socket file */ PATH_RESOLVE_PROCFD = _BIT ( 6), /* fd symlink via /proc */ PATH_REP_NOAPI = _BIT ( 7), /* rep. point unknown to WinAPI */ + PATH_APPEXECLINK = _BIT ( 8), /* rep. point app execution alias */ PATH_DONT_USE = _BIT (31) /* conversion to signed happens. */ }; @@ -214,6 +215,10 @@ class path_conv { return (path_flags & (PATH_REP | PATH_REP_NOAPI)) == PATH_REP; } + int is_app_execution_alias () const + { + return path_flags & PATH_APPEXECLINK; + } int isfifo () const {return dev.is_device (FH_FIFO);} int iscygdrive () const {return dev.is_device (FH_CYGDRIVE);} diff --git a/winsup/cygwin/path.cc b/winsup/cygwin/path.cc index 8aff97acbe..405d4ba29f 100644 --- a/winsup/cygwin/path.cc +++ b/winsup/cygwin/path.cc @@ -2661,7 +2661,7 @@ check_reparse_point_target (HANDLE h, bool remote, PREPARSE_DATA_BUFFER rp, if (i == 2 && n > 0 && n < size) { RtlInitCountedUnicodeString (psymbuf, buf, n * sizeof (WCHAR)); - return PATH_SYMLINK | PATH_REP; + return PATH_SYMLINK | PATH_REP | PATH_APPEXECLINK; } if (i == 2) break; From a18fe29ee4493d6f8e340a3f18a65b913f0962b1 Mon Sep 17 00:00:00 2001 From: Takashi Yano Date: Fri, 19 Dec 2025 11:26:38 +0900 Subject: [PATCH 5/6] Cygwin: termios: Change argument of fhandler_termios::spawn_worker() This patch changes the argument for passsing a path to an app to fhandler_termios::spawn_worker() from const WCHAR *runpath to path_conv &pc. The purpose of this patch is to prepare for a subsequent patch, that is intended to fix a bug in executing Microsoft Store apps. Reviewed-by: Johannes Schindelin Signed-off-by: Takashi Yano Signed-off-by: Johannes Schindelin --- winsup/cygwin/fhandler/termios.cc | 14 +++++++++----- winsup/cygwin/local_includes/fhandler.h | 2 +- winsup/cygwin/spawn.cc | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc index 8f98e42a45..e6e6419297 100644 --- a/winsup/cygwin/fhandler/termios.cc +++ b/winsup/cygwin/fhandler/termios.cc @@ -702,13 +702,17 @@ fhandler_termios::fstat (struct stat *buf) } static bool -is_console_app (const WCHAR *filename) +is_console_app (path_conv &pc) { - wchar_t *e = wcsrchr (filename, L'.'); + tmp_pathbuf tp; + WCHAR *native_path = tp.w_get (); + pc.get_wide_win32_path (native_path); + + wchar_t *e = wcsrchr (native_path, L'.'); if (e && (wcscasecmp (e, L".bat") == 0 || wcscasecmp (e, L".cmd") == 0)) return true; HANDLE h; - h = CreateFileW (filename, GENERIC_READ, FILE_SHARE_READ, + h = CreateFileW (native_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (h == INVALID_HANDLE_VALUE) return true; @@ -761,7 +765,7 @@ fhandler_termios::ioctl (unsigned int cmd, void *varg) void fhandler_termios::spawn_worker::setup (bool iscygwin, HANDLE h_stdin, - const WCHAR *runpath, bool nopcon, + path_conv &pc, bool nopcon, bool reset_sendsig, const WCHAR *envblock) { @@ -800,7 +804,7 @@ fhandler_termios::spawn_worker::setup (bool iscygwin, HANDLE h_stdin, ptys->setup_locale (); } } - if (!iscygwin && ptys_primary && is_console_app (runpath)) + if (!iscygwin && ptys_primary && is_console_app (pc)) { if (h_stdin == ptys_primary->get_handle_nat ()) stdin_is_ptys = true; diff --git a/winsup/cygwin/local_includes/fhandler.h b/winsup/cygwin/local_includes/fhandler.h index 5e8f3d30f3..9fa73899c5 100644 --- a/winsup/cygwin/local_includes/fhandler.h +++ b/winsup/cygwin/local_includes/fhandler.h @@ -2035,7 +2035,7 @@ class fhandler_termios: public fhandler_base spawn_worker () : ptys_need_cleanup (false), cons_need_cleanup (false), stdin_is_ptys (false), ptys_ttyp (NULL) {} - void setup (bool iscygwin, HANDLE h_stdin, const WCHAR *runpath, + void setup (bool iscygwin, HANDLE h_stdin, path_conv &pc, bool nopcon, bool reset_sendsig, const WCHAR *envblock); bool need_cleanup () { return ptys_need_cleanup || cons_need_cleanup; } void cleanup (); diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index 71add8755c..7d993d0810 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -579,7 +579,7 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, bool no_pcon = mode != _P_OVERLAY && mode != _P_WAIT; term_spawn_worker.setup (iscygwin (), handle (fileno_stdin, false), - runpath, no_pcon, reset_sendsig, envblock); + real_path, no_pcon, reset_sendsig, envblock); /* Set up needed handles for stdio */ si.dwFlags = STARTF_USESTDHANDLES; From f27c915a9496feafef9fc8a3f624c6b1ba3bc124 Mon Sep 17 00:00:00 2001 From: Takashi Yano Date: Fri, 19 Dec 2025 11:26:39 +0900 Subject: [PATCH 6/6] Cygwin: termios: Handle app execution alias in is_console_app() Microsoft Store apps are run via app execution aliases, i.e. special reparse points. Currently, spawn.cc does not resolve a reparse point when retrieving the path of app after the commit f74dc93c6359, that disabled to follow windows reparse point by adding PC_SYM_NOFOLLOW_REP flag. However, unlike proper reparse point, app execution aliases are not resolved when trying to open the file via CreateFile(). As a result, if the path, that is_console_app() received, is the reparse point for an app execution alias, the func retuned false due to open-failure because CreateFile() cannot open an app execution alias, while it can open normal reparse point. If is_console_app() returns false, standard handles for console app (such as WSL) would not be setup. This causes that the console input cannot be transfered to the non-cygwin app. This patch fixes the issue by locally converting the path once again using option PC_SYM_FOLLOW (without PC_SYM_NOFOLLOW_REP), which is used inside is_console_app() to resolve the reparse point, if the path is an app execution alias. Fixes: f74dc93c6359 ("fix native symlink spawn passing wrong arg0") Reviewed-by: Johannes Schindelin Signed-off-by: Takashi Yano Signed-off-by: Johannes Schindelin --- winsup/cygwin/fhandler/termios.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/winsup/cygwin/fhandler/termios.cc b/winsup/cygwin/fhandler/termios.cc index e6e6419297..01d00daed5 100644 --- a/winsup/cygwin/fhandler/termios.cc +++ b/winsup/cygwin/fhandler/termios.cc @@ -711,6 +711,15 @@ is_console_app (path_conv &pc) wchar_t *e = wcsrchr (native_path, L'.'); if (e && (wcscasecmp (e, L".bat") == 0 || wcscasecmp (e, L".cmd") == 0)) return true; + + if (pc.is_app_execution_alias ()) + { + UNICODE_STRING upath; + RtlInitUnicodeString (&upath, native_path); + path_conv target (&upath, PC_SYM_FOLLOW); + target.get_wide_win32_path (native_path); + } + HANDLE h; h = CreateFileW (native_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);