Skip to content

Commit

Permalink
Merge branch 'inherit-only-stdhandles'
Browse files Browse the repository at this point in the history
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 <johannes.schindelin@gmx.de>
  • Loading branch information
dscho committed Jun 8, 2018
2 parents ab43a7a + 42a0f53 commit 67569bc
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 20 deletions.
123 changes: 110 additions & 13 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ int mingw_core_config(const char *var, const char *value, void *cb)
}

static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
DECLARE_PROC_ADDR(kernel32.dll, BOOLEAN, CreateSymbolicLinkW, LPCWSTR, LPCWSTR, DWORD);

enum phantom_symlink_result {
PHANTOM_SYMLINK_RETRY,
Expand Down Expand Up @@ -1604,8 +1603,13 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
const char *dir, const char *prepend_cmd,
int fhin, int fhout, int fherr)
{
STARTUPINFOW si;
static int restrict_handle_inheritance = 1;
STARTUPINFOEXW si;
PROCESS_INFORMATION pi;
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
HANDLE stdhandles[3];
DWORD stdhandles_count = 0;
SIZE_T size;
struct strbuf args;
wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL;
unsigned flags = CREATE_UNICODE_ENVIRONMENT;
Expand Down Expand Up @@ -1642,11 +1646,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
CloseHandle(cons);
}
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = winansi_get_osfhandle(fhin);
si.hStdOutput = winansi_get_osfhandle(fhout);
si.hStdError = winansi_get_osfhandle(fherr);
si.StartupInfo.cb = sizeof(si);
si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin);
si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout);
si.StartupInfo.hStdError = winansi_get_osfhandle(fherr);

/* The list of handles cannot contain duplicates */
if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE)
stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput;
if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE &&
si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput)
stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput;
if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE &&
si.StartupInfo.hStdError != si.StartupInfo.hStdInput &&
si.StartupInfo.hStdError != si.StartupInfo.hStdOutput)
stdhandles[stdhandles_count++] = si.StartupInfo.hStdError;
if (stdhandles_count)
si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;

/* executables and the current directory don't support long paths */
if (*argv && !strcmp(cmd, *argv))
Expand Down Expand Up @@ -1705,16 +1721,97 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
wenvblk = make_environment_block(deltaenv);

memset(&pi, 0, sizeof(pi));
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE,
flags, wenvblk, dir ? wdir : NULL, &si, &pi);
if (restrict_handle_inheritance && stdhandles_count &&
(InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
GetLastError() == ERROR_INSUFFICIENT_BUFFER) &&
(attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)
(HeapAlloc(GetProcessHeap(), 0, size))) &&
InitializeProcThreadAttributeList(attr_list, 1, 0, &size) &&
UpdateProcThreadAttribute(attr_list, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
stdhandles,
stdhandles_count * sizeof(HANDLE),
NULL, NULL)) {
si.lpAttributeList = attr_list;
flags |= EXTENDED_STARTUPINFO_PRESENT;
}

ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
stdhandles_count ? TRUE : FALSE,
flags, wenvblk, dir ? wdir : NULL,
&si.StartupInfo, &pi);

/*
* On Windows 2008 R2, it seems that specifying certain types of handles
* (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an
* error. Rather than playing finicky and fragile games, let's just try
* to detect this situation and simply try again without restricting any
* handle inheritance. This is still better than failing to create
* processes.
*/
if (!ret && restrict_handle_inheritance && stdhandles_count) {
DWORD err = GetLastError();
struct strbuf buf = STRBUF_INIT;

if (err != ERROR_NO_SYSTEM_RESOURCES &&
/*
* On Windows 7 and earlier, handles on pipes and character
* devices are inherited automatically, and cannot be
* specified in the thread handle list. Rather than trying
* to catch each and every corner case (and running the
* chance of *still* forgetting a few), let's just fall
* back to creating the process without trying to limit the
* handle inheritance.
*/
!(err == ERROR_INVALID_PARAMETER &&
GetVersion() >> 16 < 9200) &&
!getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) {
DWORD fl = 0;
int i;

setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1);

for (i = 0; i < stdhandles_count; i++) {
HANDLE h = stdhandles[i];
strbuf_addf(&buf, "handle #%d: %p (type %lx, "
"handle info (%d) %lx\n", i, h,
GetFileType(h),
GetHandleInformation(h, &fl),
fl);
}
strbuf_addstr(&buf, "\nThis is a bug; please report it "
"at\nhttps://github.com/git-for-windows/"
"git/issues/new\n\n"
"To suppress this warning, please set "
"the environment variable\n\n"
"\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1"
"\n");
}
restrict_handle_inheritance = 0;
flags &= ~EXTENDED_STARTUPINFO_PRESENT;
ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
TRUE, flags, wenvblk, dir ? wdir : NULL,
&si.StartupInfo, &pi);
if (ret && buf.len) {
errno = err_win_to_posix(GetLastError());
warning("failed to restrict file handles (%ld)\n\n%s",
err, buf.buf);
}
strbuf_release(&buf);
} else if (!ret)
errno = err_win_to_posix(GetLastError());

if (si.lpAttributeList)
DeleteProcThreadAttributeList(si.lpAttributeList);
if (attr_list)
HeapFree(GetProcessHeap(), 0, attr_list);

free(wenvblk);
free(wargs);

if (!ret) {
errno = ENOENT;
if (!ret)
return -1;
}

CloseHandle(pi.hThread);

/*
Expand Down Expand Up @@ -2685,7 +2782,7 @@ int symlink(const char *target, const char *link)
int len;

/* fail if symlinks are disabled or API is not supported (WinXP) */
if (!has_symlinks || !INIT_PROC_ADDR(CreateSymbolicLinkW)) {
if (!has_symlinks) {
errno = ENOSYS;
return -1;
}
Expand Down
15 changes: 15 additions & 0 deletions compat/poll/poll.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@
#ifndef _GL_POLL_H
#define _GL_POLL_H

#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600
/* Vista has its own, socket-only poll() */
#undef POLLIN
#undef POLLPRI
#undef POLLOUT
#undef POLLERR
#undef POLLHUP
#undef POLLNVAL
#undef POLLRDNORM
#undef POLLRDBAND
#undef POLLWRNORM
#undef POLLWRBAND
#define pollfd compat_pollfd
#endif

/* fake a poll(2) environment */
#define POLLIN 0x0001 /* any readable data available */
#define POLLPRI 0x0002 /* OOB/Urgent readable data */
Expand Down
12 changes: 11 additions & 1 deletion compat/winansi.c
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,20 @@ void winansi_init(void)
*/
HANDLE winansi_get_osfhandle(int fd)
{
HANDLE ret;

if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
return hconsole1;
if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
return hconsole2;

return (HANDLE)_get_osfhandle(fd);
ret = (HANDLE)_get_osfhandle(fd);

/*
* There are obviously circumstances under which _get_osfhandle()
* returns (HANDLE)-2. This is not documented anywhere, but that is so
* clearly an invalid handle value that we can just work around this
* and return the correct value for invalid handles.
*/
return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret;
}
4 changes: 0 additions & 4 deletions config.mak.uname
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,6 @@ ifeq ($(uname_S),Windows)
NO_GETTEXT = YesPlease
NO_PYTHON = YesPlease
ETAGS_TARGET = ETAGS
NO_INET_PTON = YesPlease
NO_INET_NTOP = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
NATIVE_CRLF = YesPlease
DEFAULT_HELP_FORMAT = html
Expand Down Expand Up @@ -577,8 +575,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
NO_REGEX = YesPlease
NO_PYTHON = YesPlease
ETAGS_TARGET = ETAGS
NO_INET_PTON = YesPlease
NO_INET_NTOP = YesPlease
NO_POSIX_GOODIES = UnfortunatelyYes
DEFAULT_HELP_FORMAT = html
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
Expand Down
4 changes: 2 additions & 2 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@
#define _SGI_SOURCE 1

#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */
# if defined (_MSC_VER) && !defined(_WIN32_WINNT)
# define _WIN32_WINNT 0x0502
# if !defined(_WIN32_WINNT)
# define _WIN32_WINNT 0x0600
# endif
#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */
#include <winsock2.h>
Expand Down
45 changes: 45 additions & 0 deletions t/helper/test-run-command.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,58 @@ static int testsuite(int argc, const char **argv)
return !!ret;
}

static int inherit_handle(const char *argv0)
{
struct child_process cp = CHILD_PROCESS_INIT;
char path[PATH_MAX];
int tmp;

/* First, open an inheritable handle */
sprintf(path, "out-XXXXXX");
tmp = xmkstemp(path);

argv_array_pushl(&cp.args, argv0, "inherited-handle-child", NULL);
cp.in = -1;
cp.no_stdout = cp.no_stderr = 1;
if (start_command(&cp) < 0)
die("Could not start child process");

/* Then close it, and try to delete it. */
close(tmp);
if (unlink(path))
die("Could not delete '%s'", path);

if (close(cp.in) < 0 || finish_command(&cp) < 0)
die("Child did not finish");

return 0;
}

static int inherit_handle_child(void)
{
struct strbuf buf = STRBUF_INIT;

if (strbuf_read(&buf, 0, 0) < 0)
die("Could not read stdin");
printf("Received %s\n", buf.buf);
strbuf_release(&buf);

return 0;
}

int cmd__run_command(int argc, const char **argv)
{
struct child_process proc = CHILD_PROCESS_INIT;
int jobs;

if (argc > 1 && !strcmp(argv[1], "testsuite"))
exit(testsuite(argc - 1, argv + 1));

if (!strcmp(argv[1], "inherited-handle"))
exit(inherit_handle(argv[0]));
if (!strcmp(argv[1], "inherited-handle-child"))
exit(inherit_handle_child());

if (argc < 3)
return 1;
while (!strcmp(argv[1], "env")) {
Expand Down
4 changes: 4 additions & 0 deletions t/t0061-run-command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ cat >hello-script <<-EOF
EOF
>empty

test_expect_success MINGW 'subprocess inherits only std handles' '
test-run-command inherited-handle
'

test_expect_success 'start_command reports ENOENT' '
test-tool run-command start-command-ENOENT ./does-not-exist
'
Expand Down

0 comments on commit 67569bc

Please sign in to comment.