Skip to content

Commit b36526f

Browse files
committed
Merge branch 'inherit-only-stdhandles'
When spawning child processes, we do want them to inherit the standard handles so that we can talk to them. We do *not* want them to inherit any other handle, as that would hold a lock to the respective files (preventing them from being renamed, modified or deleted), and the child process would not know how to access that handle anyway. Happily, there is an API to make that happen. It is supported in Windows Vista and later, which is exactly what we promise to support in Git for Windows for the time being. This also means that we lift, at long last, the target Windows version from Windows XP to Windows Vista. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 6420432 + 448e644 commit b36526f

File tree

4 files changed

+171
-12
lines changed

4 files changed

+171
-12
lines changed

compat/mingw.c

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,8 +1583,13 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
15831583
const char *dir,
15841584
int prepend_cmd, int fhin, int fhout, int fherr)
15851585
{
1586-
STARTUPINFOW si;
1586+
static int restrict_handle_inheritance = 1;
1587+
STARTUPINFOEXW si;
15871588
PROCESS_INFORMATION pi;
1589+
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
1590+
HANDLE stdhandles[3];
1591+
DWORD stdhandles_count = 0;
1592+
SIZE_T size;
15881593
struct strbuf args;
15891594
wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
15901595
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
@@ -1621,11 +1626,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
16211626
CloseHandle(cons);
16221627
}
16231628
memset(&si, 0, sizeof(si));
1624-
si.cb = sizeof(si);
1625-
si.dwFlags = STARTF_USESTDHANDLES;
1626-
si.hStdInput = winansi_get_osfhandle(fhin);
1627-
si.hStdOutput = winansi_get_osfhandle(fhout);
1628-
si.hStdError = winansi_get_osfhandle(fherr);
1629+
si.StartupInfo.cb = sizeof(si);
1630+
si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
1631+
si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
1632+
si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);
1633+
1634+
/* The list of handles cannot contain duplicates */
1635+
if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
1636+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
1637+
if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
1638+
si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
1639+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
1640+
if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
1641+
si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
1642+
si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
1643+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
1644+
if (stdhandles_count)
1645+
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
16291646

16301647
/* executables and the current directory don't support long paths */
16311648
if (*argv && !strcmp(cmd, *argv))
@@ -1684,16 +1701,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
16841701
wenvblk = make_environment_block(deltaenv);
16851702

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

16901789
free(wenvblk);
16911790
free(wargs);
16921791

1693-
if (!ret) {
1694-
errno = ENOENT;
1792+
if (!ret)
16951793
return -1;
1696-
}
1794+
16971795
CloseHandle(pi.hThread);
16981796

16991797
/*

compat/winansi.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,10 +660,20 @@ void winansi_init(void)
660660
*/
661661
HANDLE winansi_get_osfhandle(int fd)
662662
{
663+
HANDLE ret;
664+
663665
if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
664666
return hconsole1;
665667
if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
666668
return hconsole2;
667669

668-
return (HANDLE)_get_osfhandle(fd);
670+
ret = (HANDLE)_get_osfhandle(fd);
671+
672+
/*
673+
* There are obviously circumstances under which _get_osfhandle()
674+
* returns (HANDLE)-2. This is not documented anywhere, but that is so
675+
* clearly an invalid handle value that we can just work around this
676+
* and return the correct value for invalid handles.
677+
*/
678+
return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret;
669679
}

t/helper/test-run-command.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,58 @@ static int task_finished(int result,
5050
return 1;
5151
}
5252

53+
static int inherit_handle(const char *argv0)
54+
{
55+
struct child_process cp = CHILD_PROCESS_INIT;
56+
char path[PATH_MAX];
57+
int tmp;
58+
59+
/* First, open an inheritable handle */
60+
xsnprintf(path, sizeof(path), "out-XXXXXX");
61+
tmp = xmkstemp(path);
62+
63+
argv_array_pushl(&cp.args,
64+
"test-tool", argv0, "inherited-handle-child", NULL);
65+
cp.in = -1;
66+
cp.no_stdout = cp.no_stderr = 1;
67+
if (start_command(&cp) < 0)
68+
die("Could not start child process");
69+
70+
/* Then close it, and try to delete it. */
71+
close(tmp);
72+
if (unlink(path))
73+
die("Could not delete '%s'", path);
74+
75+
if (close(cp.in) < 0 || finish_command(&cp) < 0)
76+
die("Child did not finish");
77+
78+
return 0;
79+
}
80+
81+
static int inherit_handle_child(void)
82+
{
83+
struct strbuf buf = STRBUF_INIT;
84+
85+
if (strbuf_read(&buf, 0, 0) < 0)
86+
die("Could not read stdin");
87+
printf("Received %s\n", buf.buf);
88+
strbuf_release(&buf);
89+
90+
return 0;
91+
}
92+
5393
int cmd__run_command(int argc, const char **argv)
5494
{
5595
struct child_process proc = CHILD_PROCESS_INIT;
5696
int jobs;
5797

98+
if (argc < 2)
99+
return 1;
100+
if (!strcmp(argv[1], "inherited-handle"))
101+
exit(inherit_handle(argv[0]));
102+
if (!strcmp(argv[1], "inherited-handle-child"))
103+
exit(inherit_handle_child());
104+
58105
if (argc < 3)
59106
return 1;
60107
while (!strcmp(argv[1], "env")) {

t/t0061-run-command.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ cat >hello-script <<-EOF
1212
cat hello-script
1313
EOF
1414

15+
test_expect_success MINGW 'subprocess inherits only std handles' '
16+
test-tool run-command inherited-handle
17+
'
18+
1519
test_expect_success 'start_command reports ENOENT (slash)' '
1620
test-tool run-command start-command-ENOENT ./does-not-exist
1721
'

0 commit comments

Comments
 (0)