Skip to content

Commit 8883c0c

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 0bd8712 + 1cce3d7 commit 8883c0c

File tree

5 files changed

+194
-12
lines changed

5 files changed

+194
-12
lines changed

Documentation/config/core.txt

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

562+
core.restrictinheritedhandles::
563+
Windows-only: override whether spawned processes inherit only standard
564+
file handles (`stdin`, `stdout` and `stderr`) or all handles. Can be
565+
`auto`, `true` or `false`. Defaults to `auto`, which means `true` on
566+
Windows 7 and later, and `false` on older Windows versions.
567+
562568
core.createObject::
563569
You can set this to 'link', in which case a hardlink followed by
564570
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
@@ -225,6 +225,7 @@ enum hide_dotfiles_type {
225225
HIDE_DOTFILES_DOTGITONLY
226226
};
227227

228+
static int core_restrict_inherited_handles = -1;
228229
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
229230
static char *unset_environment_variables;
230231

@@ -244,6 +245,15 @@ int mingw_core_config(const char *var, const char *value, void *cb)
244245
return 0;
245246
}
246247

248+
if (!strcmp(var, "core.restrictinheritedhandles")) {
249+
if (value && !strcasecmp(value, "auto"))
250+
core_restrict_inherited_handles = -1;
251+
else
252+
core_restrict_inherited_handles =
253+
git_config_bool(var, value);
254+
return 0;
255+
}
256+
247257
return 0;
248258
}
249259

@@ -1468,8 +1478,13 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14681478
const char *dir,
14691479
int prepend_cmd, int fhin, int fhout, int fherr)
14701480
{
1471-
STARTUPINFOW si;
1481+
static int restrict_handle_inheritance = -1;
1482+
STARTUPINFOEXW si;
14721483
PROCESS_INFORMATION pi;
1484+
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
1485+
HANDLE stdhandles[3];
1486+
DWORD stdhandles_count = 0;
1487+
SIZE_T size;
14731488
struct strbuf args;
14741489
wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
14751490
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
@@ -1480,6 +1495,16 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14801495
quote_arg_msys2 : quote_arg_msvc;
14811496
const char *strace_env;
14821497

1498+
if (restrict_handle_inheritance < 0)
1499+
restrict_handle_inheritance = core_restrict_inherited_handles;
1500+
/*
1501+
* The following code to restrict which handles are inherited seems
1502+
* to work properly only on Windows 7 and later, so let's disable it
1503+
* on Windows Vista and 2008.
1504+
*/
1505+
if (restrict_handle_inheritance < 0)
1506+
restrict_handle_inheritance = GetVersion() >> 16 >= 7601;
1507+
14831508
do_unset_environment_variables();
14841509

14851510
/* Determine whether or not we are associated to a console */
@@ -1507,11 +1532,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
15071532
CloseHandle(cons);
15081533
}
15091534
memset(&si, 0, sizeof(si));
1510-
si.cb = sizeof(si);
1511-
si.dwFlags = STARTF_USESTDHANDLES;
1512-
si.hStdInput = winansi_get_osfhandle(fhin);
1513-
si.hStdOutput = winansi_get_osfhandle(fhout);
1514-
si.hStdError = winansi_get_osfhandle(fherr);
1535+
si.StartupInfo.cb = sizeof(si);
1536+
si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
1537+
si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
1538+
si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);
1539+
1540+
/* The list of handles cannot contain duplicates */
1541+
if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
1542+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
1543+
if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
1544+
si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
1545+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
1546+
if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
1547+
si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
1548+
si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
1549+
stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
1550+
if (stdhandles_count)
1551+
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
15151552

15161553
if (*argv && !strcmp(cmd, *argv))
15171554
wcmd[0] = L'\0';
@@ -1569,16 +1606,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
15691606
wenvblk = make_environment_block(deltaenv);
15701607

15711608
memset(&pi, 0, sizeof(pi));
1572-
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE,
1573-
flags, wenvblk, dir ? wdir : NULL, &si, &pi);
1609+
if (restrict_handle_inheritance && stdhandles_count &&
1610+
(InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
1611+
GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
1612+
(attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)
1613+
(HeapAlloc(GetProcessHeap(), 0, size))) &&
1614+
InitializeProcThreadAttributeList(attr_list, 1, 0, &size) &&
1615+
UpdateProcThreadAttribute(attr_list, 0,
1616+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
1617+
stdhandles,
1618+
stdhandles_count * sizeof(HANDLE),
1619+
NULL, NULL)) {
1620+
si.lpAttributeList = attr_list;
1621+
flags |= EXTENDED_STARTUPINFO_PRESENT;
1622+
}
1623+
1624+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1625+
stdhandles_count ? TRUE : FALSE,
1626+
flags, wenvblk, dir ? wdir : NULL,
1627+
&si.StartupInfo, &pi);
1628+
1629+
/*
1630+
* On Windows 2008 R2, it seems that specifying certain types of handles
1631+
* (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an
1632+
* error. Rather than playing finicky and fragile games, let's just try
1633+
* to detect this situation and simply try again without restricting any
1634+
* handle inheritance. This is still better than failing to create
1635+
* processes.
1636+
*/
1637+
if (!ret && restrict_handle_inheritance && stdhandles_count) {
1638+
DWORD err = GetLastError();
1639+
struct strbuf buf = STRBUF_INIT;
1640+
1641+
if (err != ERROR_NO_SYSTEM_RESOURCES &&
1642+
/*
1643+
* On Windows 7 and earlier, handles on pipes and character
1644+
* devices are inherited automatically, and cannot be
1645+
* specified in the thread handle list. Rather than trying
1646+
* to catch each and every corner case (and running the
1647+
* chance of *still* forgetting a few), let's just fall
1648+
* back to creating the process without trying to limit the
1649+
* handle inheritance.
1650+
*/
1651+
!(err == ERROR_INVALID_PARAMETER &&
1652+
GetVersion() >> 16 < 9200) &&
1653+
!getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) {
1654+
DWORD fl = 0;
1655+
int i;
1656+
1657+
setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1);
1658+
1659+
for (i = 0; i < stdhandles_count; i++) {
1660+
HANDLE h = stdhandles[i];
1661+
strbuf_addf(&buf, "handle #%d: %p (type %lx, "
1662+
"handle info (%d) %lx\n", i, h,
1663+
GetFileType(h),
1664+
GetHandleInformation(h, &fl),
1665+
fl);
1666+
}
1667+
strbuf_addstr(&buf, "\nThis is a bug; please report it "
1668+
"at\nhttps://github.com/git-for-windows/"
1669+
"git/issues/new\n\n"
1670+
"To suppress this warning, please set "
1671+
"the environment variable\n\n"
1672+
"\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1"
1673+
"\n");
1674+
}
1675+
restrict_handle_inheritance = 0;
1676+
flags &= ~EXTENDED_STARTUPINFO_PRESENT;
1677+
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
1678+
TRUE, flags, wenvblk, dir ? wdir : NULL,
1679+
&si.StartupInfo, &pi);
1680+
if (ret && buf.len) {
1681+
errno = err_win_to_posix(GetLastError());
1682+
warning("failed to restrict file handles (%ld)\n\n%s",
1683+
err, buf.buf);
1684+
}
1685+
strbuf_release(&buf);
1686+
} else if (!ret)
1687+
errno = err_win_to_posix(GetLastError());
1688+
1689+
if (si.lpAttributeList)
1690+
DeleteProcThreadAttributeList(si.lpAttributeList);
1691+
if (attr_list)
1692+
HeapFree(GetProcessHeap(), 0, attr_list);
15741693

15751694
free(wenvblk);
15761695
free(wargs);
15771696

1578-
if (!ret) {
1579-
errno = ENOENT;
1697+
if (!ret)
15801698
return -1;
1581-
}
1699+
15821700
CloseHandle(pi.hThread);
15831701

15841702
/*

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: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,57 @@ static int quote_echo(int argc, const char **argv)
328328
return 0;
329329
}
330330

331+
static int inherit_handle(const char *argv0)
332+
{
333+
struct child_process cp = CHILD_PROCESS_INIT;
334+
char path[PATH_MAX];
335+
int tmp;
336+
337+
/* First, open an inheritable handle */
338+
xsnprintf(path, sizeof(path), "out-XXXXXX");
339+
tmp = xmkstemp(path);
340+
341+
argv_array_pushl(&cp.args,
342+
"test-tool", argv0, "inherited-handle-child", NULL);
343+
cp.in = -1;
344+
cp.no_stdout = cp.no_stderr = 1;
345+
if (start_command(&cp) < 0)
346+
die("Could not start child process");
347+
348+
/* Then close it, and try to delete it. */
349+
close(tmp);
350+
if (unlink(path))
351+
die("Could not delete '%s'", path);
352+
353+
if (close(cp.in) < 0 || finish_command(&cp) < 0)
354+
die("Child did not finish");
355+
356+
return 0;
357+
}
358+
359+
static int inherit_handle_child(void)
360+
{
361+
struct strbuf buf = STRBUF_INIT;
362+
363+
if (strbuf_read(&buf, 0, 0) < 0)
364+
die("Could not read stdin");
365+
printf("Received %s\n", buf.buf);
366+
strbuf_release(&buf);
367+
368+
return 0;
369+
}
370+
331371
int cmd__run_command(int argc, const char **argv)
332372
{
333373
struct child_process proc = CHILD_PROCESS_INIT;
334374
int jobs;
335375

336376
if (argc > 1 && !strcmp(argv[1], "testsuite"))
337377
exit(testsuite(argc - 1, argv + 1));
378+
if (!strcmp(argv[1], "inherited-handle"))
379+
exit(inherit_handle(argv[0]));
380+
if (!strcmp(argv[1], "inherited-handle-child"))
381+
exit(inherit_handle_child());
338382

339383
if (argc >= 2 && !strcmp(argv[1], "quote-stress-test"))
340384
return !!quote_stress_test(argc - 1, argv + 1);

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)