From 1f802f3a8ca16bb59961cfc6d7d02c392c0e7612 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 pointless 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 d74cb3d56a3064b29bdbfc78a909f00212ba6586 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 b8fb9bd8a6f462fba86977a2c298fd6c42d12468 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 f6fe626cd4278b2fad2e4c828bdb4072ddd427ba 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 f89df7d72f..fab6662eef 100644 --- a/winsup/cygwin/path.cc +++ b/winsup/cygwin/path.cc @@ -2946,7 +2946,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 433048248b1055ee95891ce9a732b0e53475baa2 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 2cc5716882..3467aa6a3e 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -609,7 +609,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 1113cd76caf1f0e1d578c13f650d86b65f3654a7 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);