Skip to content

Commit 40ffda9

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 98eda50 + 0209b06 commit 40ffda9

File tree

5 files changed

+197
-12
lines changed

5 files changed

+197
-12
lines changed

Documentation/config/core.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,12 @@ core.unsetenvvars::
570570
Defaults to `PERL5LIB` to account for the fact that Git for
571571
Windows insists on using its own Perl interpreter.
572572

573+
core.restrictinheritedhandles::
574+
Windows-only: override whether spawned processes inherit only standard
575+
file handles (`stdin`, `stdout` and `stderr`) or all handles. Can be
576+
`auto`, `true` or `false`. Defaults to `auto`, which means `true` on
577+
Windows 7 and later, and `false` on older Windows versions.
578+
573579
core.createObject::
574580
You can set this to 'link', in which case a hardlink followed by
575581
a delete of the source are used to make sure that object creation

compat/mingw.c

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ enum hide_dotfiles_type {
255255
HIDE_DOTFILES_DOTGITONLY
256256
};
257257

258+
static int core_restrict_inherited_handles = -1;
258259
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
259260
static char *unset_environment_variables;
260261
int core_fscache;
@@ -286,6 +287,15 @@ int mingw_core_config(const char *var, const char *value, void *cb)
286287
return 0;
287288
}
288289

290+
if (!strcmp(var, "core.restrictinheritedhandles")) {
291+
if (value && !strcasecmp(value, "auto"))
292+
core_restrict_inherited_handles = -1;
293+
else
294+
core_restrict_inherited_handles =
295+
git_config_bool(var, value);
296+
return 0;
297+
}
298+
289299
return 0;
290300
}
291301

@@ -1623,8 +1633,13 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
16231633
const char *dir,
16241634
int prepend_cmd, int fhin, int fhout, int fherr)
16251635
{
1626-
STARTUPINFOW si;
1636+
static int restrict_handle_inheritance = -1;
1637+
STARTUPINFOEXW si;
16271638
PROCESS_INFORMATION pi;
1639+
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
1640+
HANDLE stdhandles[3];
1641+
DWORD stdhandles_count = 0;
1642+
SIZE_T size;
16281643
struct strbuf args;
16291644
wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
16301645
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
@@ -1634,6 +1649,16 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
16341649
is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
16351650
const char *strace_env;
16361651

1652+
if (restrict_handle_inheritance < 0)
1653+
restrict_handle_inheritance = core_restrict_inherited_handles;
1654+
/*
1655+
* The following code to restrict which handles are inherited seems
1656+
* to work properly only on Windows 7 and later, so let's disable it
1657+
* on Windows Vista and 2008.
1658+
*/
1659+
if (restrict_handle_inheritance < 0)
1660+
restrict_handle_inheritance = GetVersion() >> 16 >= 7601;
1661+
16371662
do_unset_environment_variables();
16381663

16391664
/* Determine whether or not we are associated to a console */
@@ -1661,11 +1686,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
16611686
CloseHandle(cons);
16621687
}
16631688
memset(&si, 0, sizeof(si));
1664-
si.cb = sizeof(si);
1665-
si.dwFlags = STARTF_USESTDHANDLES;
1666-
si.hStdInput = winansi_get_osfhandle(fhin);
1667-
si.hStdOutput = winansi_get_osfhandle(fhout);
1668-
si.hStdError = winansi_get_osfhandle(fherr);
1689+
si.StartupInfo.cb = sizeof(si);
1690+
si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
1691+
si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
1692+
si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);
1693+
1694+
/* The list of handles cannot contain duplicates */
1695+
if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
1696+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
1697+
if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
1698+
si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
1699+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
1700+
if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
1701+
si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
1702+
si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
1703+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
1704+
if (stdhandles_count)
1705+
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
16691706

16701707
if (*argv && !strcmp(cmd, *argv))
16711708
wcmd[0] = L'\0';
@@ -1727,16 +1764,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
17271764
wenvblk = make_environment_block(deltaenv);
17281765

17291766
memset(&pi, 0, sizeof(pi));
1730-
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE,
1731-
flags, wenvblk, dir ? wdir : NULL, &si, &pi);
1767+
if (restrict_handle_inheritance && stdhandles_count &&
1768+
(InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
1769+
GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
1770+
(attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)
1771+
(HeapAlloc(GetProcessHeap(), 0, size))) &&
1772+
InitializeProcThreadAttributeList(attr_list, 1, 0, &size) &&
1773+
UpdateProcThreadAttribute(attr_list, 0,
1774+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
1775+
stdhandles,
1776+
stdhandles_count * sizeof(HANDLE),
1777+
NULL, NULL)) {
1778+
si.lpAttributeList = attr_list;
1779+
flags |= EXTENDED_STARTUPINFO_PRESENT;
1780+
}
1781+
1782+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1783+
stdhandles_count ? TRUE : FALSE,
1784+
flags, wenvblk, dir ? wdir : NULL,
1785+
&si.StartupInfo, &pi);
1786+
1787+
/*
1788+
* On Windows 2008 R2, it seems that specifying certain types of handles
1789+
* (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an
1790+
* error. Rather than playing finicky and fragile games, let's just try
1791+
* to detect this situation and simply try again without restricting any
1792+
* handle inheritance. This is still better than failing to create
1793+
* processes.
1794+
*/
1795+
if (!ret && restrict_handle_inheritance && stdhandles_count) {
1796+
DWORD err = GetLastError();
1797+
struct strbuf buf = STRBUF_INIT;
1798+
1799+
if (err != ERROR_NO_SYSTEM_RESOURCES &&
1800+
/*
1801+
* On Windows 7 and earlier, handles on pipes and character
1802+
* devices are inherited automatically, and cannot be
1803+
* specified in the thread handle list. Rather than trying
1804+
* to catch each and every corner case (and running the
1805+
* chance of *still* forgetting a few), let's just fall
1806+
* back to creating the process without trying to limit the
1807+
* handle inheritance.
1808+
*/
1809+
!(err == ERROR_INVALID_PARAMETER &&
1810+
GetVersion() >> 16 < 9200) &&
1811+
!getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) {
1812+
DWORD fl = 0;
1813+
int i;
1814+
1815+
setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1);
1816+
1817+
for (i = 0; i < stdhandles_count; i++) {
1818+
HANDLE h = stdhandles[i];
1819+
strbuf_addf(&buf, "handle #%d: %p (type %lx, "
1820+
"handle info (%d) %lx\n", i, h,
1821+
GetFileType(h),
1822+
GetHandleInformation(h, &fl),
1823+
fl);
1824+
}
1825+
strbuf_addstr(&buf, "\nThis is a bug; please report it "
1826+
"at\nhttps://github.com/git-for-windows/"
1827+
"git/issues/new\n\n"
1828+
"To suppress this warning, please set "
1829+
"the environment variable\n\n"
1830+
"\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1"
1831+
"\n");
1832+
}
1833+
restrict_handle_inheritance = 0;
1834+
flags &= ~EXTENDED_STARTUPINFO_PRESENT;
1835+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1836+
TRUE, flags, wenvblk, dir ? wdir : NULL,
1837+
&si.StartupInfo, &pi);
1838+
if (ret && buf.len) {
1839+
errno = err_win_to_posix(GetLastError());
1840+
warning("failed to restrict file handles (%ld)\n\n%s",
1841+
err, buf.buf);
1842+
}
1843+
strbuf_release(&buf);
1844+
} else if (!ret)
1845+
errno = err_win_to_posix(GetLastError());
1846+
1847+
if (si.lpAttributeList)
1848+
DeleteProcThreadAttributeList(si.lpAttributeList);
1849+
if (attr_list)
1850+
HeapFree(GetProcessHeap(), 0, attr_list);
17321851

17331852
free(wenvblk);
17341853
free(wargs);
17351854

1736-
if (!ret) {
1737-
errno = ENOENT;
1855+
if (!ret)
17381856
return -1;
1739-
}
1857+
17401858
CloseHandle(pi.hThread);
17411859

17421860
/*

compat/winansi.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,10 +662,20 @@ void winansi_init(void)
662662
*/
663663
HANDLE winansi_get_osfhandle(int fd)
664664
{
665+
HANDLE ret;
666+
665667
if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
666668
return hconsole1;
667669
if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
668670
return hconsole2;
669671

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

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 2>err &&
1721
test_i18ngrep "\./does-not-exist" err

0 commit comments

Comments
 (0)