Skip to content

Commit 6b36678

Browse files
committed
mingw: spawned processes need to inherit only standard handles
By default, CreateProcess() does not inherit any open file handles, unless the bInheritHandles parameter is set to TRUE. Which we do need to set because we need to pass in stdin/stdout/stderr to talk to the child processes. Sadly, this means that all file handles (unless marked via O_NOINHERIT) are inherited. This lead to problems in GVFS Git, where a long-running read-object hook is used to hydrate missing objects, and depending on the circumstances, might only be called *after* Git opened a file handle. Ideally, we would not open files without O_NOINHERIT unless *really* necessary (i.e. when we want to pass the opened file handle as standard handle into a child process), but apparently it is all-too-easy to introduce incorrect open() calls: this happened, and prevented updating a file after the read-object hook was started because the hook still held a handle on said file. Happily, there is a solution: as described in the "Old New Thing" https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873 there is a way, starting with Windows Vista, that lets us define precisely which handles should be inherited by the child process. And since we bumped the minimum Windows version for use with Git for Windows to Vista with v2.10.1 (i.e. a *long* time ago), we can use this method. So let's do exactly that. We need to make sure that the list of handles to inherit does not contain duplicates; Otherwise CreateProcessW() would fail with ERROR_INVALID_ARGUMENT. While at it, stop setting errno to ENOENT unless it really is the correct value. Also, fall back to not limiting handle inheritance under certain error conditions (e.g. on Windows 7, which is a lot stricter in what handles you can specify to limit to). Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 5e75dec commit 6b36678

File tree

2 files changed

+110
-13
lines changed

2 files changed

+110
-13
lines changed

compat/mingw.c

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,9 +1593,13 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
15931593
const char *dir, const char *prepend_cmd,
15941594
int fhin, int fhout, int fherr)
15951595
{
1596-
static int atexit_handler_initialized;
1597-
STARTUPINFOW si;
1596+
static int atexit_handler_initialized, restrict_handle_inheritance = 1;
1597+
STARTUPINFOEXW si;
15981598
PROCESS_INFORMATION pi;
1599+
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
1600+
HANDLE stdhandles[3];
1601+
DWORD stdhandles_count = 0;
1602+
SIZE_T size;
15991603
struct strbuf args;
16001604
wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
16011605
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
@@ -1643,11 +1647,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
16431647
CloseHandle(cons);
16441648
}
16451649
memset(&si, 0, sizeof(si));
1646-
si.cb = sizeof(si);
1647-
si.dwFlags = STARTF_USESTDHANDLES;
1648-
si.hStdInput = winansi_get_osfhandle(fhin);
1649-
si.hStdOutput = winansi_get_osfhandle(fhout);
1650-
si.hStdError = winansi_get_osfhandle(fherr);
1650+
si.StartupInfo.cb = sizeof(si);
1651+
si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
1652+
si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
1653+
si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);
1654+
1655+
/* The list of handles cannot contain duplicates */
1656+
if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
1657+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
1658+
if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
1659+
si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
1660+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
1661+
if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
1662+
si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
1663+
si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
1664+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
1665+
if (stdhandles_count)
1666+
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
16511667

16521668
/* executables and the current directory don't support long paths */
16531669
if (*argv && !strcmp(cmd, *argv))
@@ -1706,16 +1722,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
17061722
wenvblk = make_environment_block(deltaenv);
17071723

17081724
memset(&pi, 0, sizeof(pi));
1709-
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE,
1710-
flags, wenvblk, dir ? wdir : NULL, &si, &pi);
1725+
if (restrict_handle_inheritance && stdhandles_count &&
1726+
(InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
1727+
GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
1728+
(attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)
1729+
(HeapAlloc(GetProcessHeap(), 0, size))) &&
1730+
InitializeProcThreadAttributeList(attr_list, 1, 0, &size) &&
1731+
UpdateProcThreadAttribute(attr_list, 0,
1732+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
1733+
stdhandles,
1734+
stdhandles_count * sizeof(HANDLE),
1735+
NULL, NULL)) {
1736+
si.lpAttributeList = attr_list;
1737+
flags |= EXTENDED_STARTUPINFO_PRESENT;
1738+
}
1739+
1740+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1741+
stdhandles_count ? TRUE : FALSE,
1742+
flags, wenvblk, dir ? wdir : NULL,
1743+
&si.StartupInfo, &pi);
1744+
1745+
/*
1746+
* On Windows 2008 R2, it seems that specifying certain types of handles
1747+
* (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an
1748+
* error. Rather than playing finicky and fragile games, let's just try
1749+
* to detect this situation and simply try again without restricting any
1750+
* handle inheritance. This is still better than failing to create
1751+
* processes.
1752+
*/
1753+
if (!ret && restrict_handle_inheritance && stdhandles_count) {
1754+
DWORD err = GetLastError();
1755+
struct strbuf buf = STRBUF_INIT;
1756+
1757+
if (err != ERROR_NO_SYSTEM_RESOURCES &&
1758+
/*
1759+
* On Windows 7 and earlier, handles on pipes and character
1760+
* devices are inherited automatically, and cannot be
1761+
* specified in the thread handle list. Rather than trying
1762+
* to catch each and every corner case (and running the
1763+
* chance of *still* forgetting a few), let's just fall
1764+
* back to creating the process without trying to limit the
1765+
* handle inheritance.
1766+
*/
1767+
!(err == ERROR_INVALID_PARAMETER &&
1768+
GetVersion() >> 16 < 9200) &&
1769+
!getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) {
1770+
DWORD fl = 0;
1771+
int i;
1772+
1773+
setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1);
1774+
1775+
for (i = 0; i < stdhandles_count; i++) {
1776+
HANDLE h = stdhandles[i];
1777+
strbuf_addf(&buf, "handle #%d: %p (type %lx, "
1778+
"handle info (%d) %lx\n", i, h,
1779+
GetFileType(h),
1780+
GetHandleInformation(h, &fl),
1781+
fl);
1782+
}
1783+
strbuf_addstr(&buf, "\nThis is a bug; please report it "
1784+
"at\nhttps://github.com/git-for-windows/"
1785+
"git/issues/new\n\n"
1786+
"To suppress this warning, please set "
1787+
"the environment variable\n\n"
1788+
"\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1"
1789+
"\n");
1790+
}
1791+
restrict_handle_inheritance = 0;
1792+
flags &= ~EXTENDED_STARTUPINFO_PRESENT;
1793+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1794+
TRUE, flags, wenvblk, dir ? wdir : NULL,
1795+
&si.StartupInfo, &pi);
1796+
if (ret && buf.len) {
1797+
errno = err_win_to_posix(GetLastError());
1798+
warning("failed to restrict file handles (%ld)\n\n%s",
1799+
err, buf.buf);
1800+
}
1801+
strbuf_release(&buf);
1802+
} else if (!ret)
1803+
errno = err_win_to_posix(GetLastError());
1804+
1805+
if (si.lpAttributeList)
1806+
DeleteProcThreadAttributeList(si.lpAttributeList);
1807+
if (attr_list)
1808+
HeapFree(GetProcessHeap(), 0, attr_list);
17111809

17121810
free(wenvblk);
17131811
free(wargs);
17141812

1715-
if (!ret) {
1716-
errno = ENOENT;
1813+
if (!ret)
17171814
return -1;
1718-
}
1815+
17191816
CloseHandle(pi.hThread);
17201817

17211818
/*

t/t0061-run-command.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ cat >hello-script <<-EOF
1313
EOF
1414
>empty
1515

16-
test_expect_failure MINGW 'subprocess inherits only std handles' '
16+
test_expect_success MINGW 'subprocess inherits only std handles' '
1717
test-run-command inherited-handle
1818
'
1919

0 commit comments

Comments
 (0)