da5b48ef3c
Always create child user window station and desktop, unless only spawning with restricted token. Also fix formatting of a few comments in child_info_spawn::worker. Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
1196 lines
32 KiB
C++
1196 lines
32 KiB
C++
/* spawn.cc
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#include "winsup.h"
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <process.h>
|
|
#include <sys/wait.h>
|
|
#include <wchar.h>
|
|
#include <ctype.h>
|
|
#include <sys/cygwin.h>
|
|
#include "cygerrno.h"
|
|
#include "security.h"
|
|
#include "sigproc.h"
|
|
#include "pinfo.h"
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "child_info.h"
|
|
#include "environ.h"
|
|
#include "cygtls.h"
|
|
#include "tls_pbuf.h"
|
|
#include "winf.h"
|
|
#include "ntdll.h"
|
|
|
|
static const suffix_info exe_suffixes[] =
|
|
{
|
|
suffix_info ("", 1),
|
|
suffix_info (".exe", 1),
|
|
suffix_info (".com"),
|
|
suffix_info (NULL)
|
|
};
|
|
|
|
/* Add .exe to PROG if not already present and see if that exists.
|
|
If not, return PROG (converted from posix to win32 rules if necessary).
|
|
The result is always BUF.
|
|
|
|
Returns (possibly NULL) suffix */
|
|
|
|
static const char *
|
|
perhaps_suffix (const char *prog, path_conv& buf, int& err, unsigned opt)
|
|
{
|
|
const char *ext;
|
|
|
|
err = 0;
|
|
debug_printf ("prog '%s'", prog);
|
|
buf.check (prog, PC_SYM_FOLLOW | PC_NULLEMPTY | PC_POSIX,
|
|
(opt & FE_DLL) ? stat_suffixes : exe_suffixes);
|
|
|
|
if (buf.isdir ())
|
|
{
|
|
err = EACCES;
|
|
ext = NULL;
|
|
}
|
|
else if (!buf.exists ())
|
|
{
|
|
err = ENOENT;
|
|
ext = NULL;
|
|
}
|
|
else if (buf.known_suffix ())
|
|
ext = buf.get_win32 () + (buf.known_suffix () - buf.get_win32 ());
|
|
else
|
|
ext = strchr (buf.get_win32 (), '\0');
|
|
|
|
debug_printf ("buf %s, suffix found '%s'", (char *) buf.get_win32 (), ext);
|
|
return ext;
|
|
}
|
|
|
|
/* Find an executable name, possibly by appending known executable suffixes
|
|
to it. The path_conv struct 'buf' is filled and contains both, win32 and
|
|
posix path of the target file. Any found suffix is returned in known_suffix.
|
|
Eventually the posix path in buf is overwritten with the exact path as it
|
|
gets constructed for the path search. The reason is that the path is used
|
|
to create argv[0] in av::setup, and this requires that the filename stays
|
|
intact, instead of being resolved if the file is a symlink.
|
|
|
|
If the file is not found and !FE_NNF then the POSIX version of name is
|
|
placed in buf and returned. Otherwise the contents of buf is undefined
|
|
and NULL is returned. */
|
|
const char * __reg3
|
|
find_exec (const char *name, path_conv& buf, const char *search,
|
|
unsigned opt, const char **known_suffix)
|
|
{
|
|
const char *suffix = "";
|
|
const char *retval = NULL;
|
|
tmp_pathbuf tp;
|
|
char *tmp_path;
|
|
char *tmp = tp.c_get ();
|
|
bool has_slash = !!strpbrk (name, "/\\");
|
|
int err = 0;
|
|
|
|
debug_printf ("find_exec (%s)", name);
|
|
|
|
/* Check to see if file can be opened as is first. */
|
|
if ((has_slash || opt & FE_CWD)
|
|
&& (suffix = perhaps_suffix (name, buf, err, opt)) != NULL)
|
|
{
|
|
/* Overwrite potential symlink target with original path.
|
|
See comment preceeding this method. */
|
|
tmp_path = tmp;
|
|
if (!has_slash)
|
|
tmp_path = stpcpy (tmp, "./");
|
|
stpcpy (tmp_path, name);
|
|
buf.set_posix (tmp);
|
|
retval = buf.get_posix ();
|
|
goto out;
|
|
}
|
|
|
|
const char *path;
|
|
/* If it starts with a slash, it's a PATH-like pathlist. Otherwise it's
|
|
the name of an environment variable. */
|
|
if (strchr (search, '/'))
|
|
*stpncpy (tmp, search, NT_MAX_PATH - 1) = '\0';
|
|
else if (has_slash || isdrive (name) || !(path = getenv (search)) || !*path)
|
|
goto errout;
|
|
else
|
|
*stpncpy (tmp, path, NT_MAX_PATH - 1) = '\0';
|
|
|
|
path = tmp;
|
|
debug_printf ("searchpath %s", path);
|
|
|
|
tmp_path = tp.c_get ();
|
|
do
|
|
{
|
|
char *eotmp = strccpy (tmp_path, &path, ':');
|
|
/* An empty path or '.' means the current directory, but we've
|
|
already tried that. */
|
|
if ((opt & FE_CWD) && (tmp_path[0] == '\0'
|
|
|| (tmp_path[0] == '.' && tmp_path[1] == '\0')))
|
|
continue;
|
|
|
|
*eotmp++ = '/';
|
|
stpcpy (eotmp, name);
|
|
|
|
debug_printf ("trying %s", tmp_path);
|
|
|
|
int err1;
|
|
|
|
if ((suffix = perhaps_suffix (tmp_path, buf, err1, opt)) != NULL)
|
|
{
|
|
if (buf.has_acls () && check_file_access (buf, X_OK, true))
|
|
continue;
|
|
/* Overwrite potential symlink target with original path.
|
|
See comment preceeding this method. */
|
|
buf.set_posix (tmp_path);
|
|
retval = buf.get_posix ();
|
|
goto out;
|
|
}
|
|
|
|
}
|
|
while (*path && *++path);
|
|
|
|
errout:
|
|
/* Couldn't find anything in the given path.
|
|
Take the appropriate action based on FE_NNF. */
|
|
if (!(opt & FE_NNF))
|
|
{
|
|
buf.check (name, PC_SYM_FOLLOW | PC_POSIX);
|
|
retval = buf.get_posix ();
|
|
}
|
|
|
|
out:
|
|
debug_printf ("%s = find_exec (%s)", (char *) buf.get_posix (), name);
|
|
if (known_suffix)
|
|
*known_suffix = suffix ?: strchr (buf.get_win32 (), '\0');
|
|
if (!retval && err)
|
|
set_errno (err);
|
|
return retval;
|
|
}
|
|
|
|
/* Utility for child_info_spawn::worker. */
|
|
|
|
static HANDLE
|
|
handle (int fd, bool writing)
|
|
{
|
|
HANDLE h;
|
|
cygheap_fdget cfd (fd);
|
|
|
|
if (cfd < 0)
|
|
h = INVALID_HANDLE_VALUE;
|
|
else if (cfd->close_on_exec ())
|
|
h = INVALID_HANDLE_VALUE;
|
|
else if (!writing)
|
|
h = cfd->get_handle ();
|
|
else
|
|
h = cfd->get_output_handle ();
|
|
|
|
return h;
|
|
}
|
|
|
|
int
|
|
iscmd (const char *argv0, const char *what)
|
|
{
|
|
int n;
|
|
n = strlen (argv0) - strlen (what);
|
|
if (n >= 2 && argv0[1] != ':')
|
|
return 0;
|
|
return n >= 0 && strcasematch (argv0 + n, what) &&
|
|
(n == 0 || isdirsep (argv0[n - 1]));
|
|
}
|
|
|
|
#define ILLEGAL_SIG_FUNC_PTR ((_sig_func_ptr) (-2))
|
|
struct system_call_handle
|
|
{
|
|
_sig_func_ptr oldint;
|
|
_sig_func_ptr oldquit;
|
|
sigset_t oldmask;
|
|
bool is_system_call ()
|
|
{
|
|
return oldint != ILLEGAL_SIG_FUNC_PTR;
|
|
}
|
|
system_call_handle (bool issystem)
|
|
{
|
|
if (!issystem)
|
|
oldint = ILLEGAL_SIG_FUNC_PTR;
|
|
else
|
|
{
|
|
sig_send (NULL, __SIGHOLD);
|
|
oldint = NULL;
|
|
}
|
|
}
|
|
void arm()
|
|
{
|
|
if (is_system_call ())
|
|
{
|
|
sigset_t child_block;
|
|
oldint = signal (SIGINT, SIG_IGN);
|
|
oldquit = signal (SIGQUIT, SIG_IGN);
|
|
sigemptyset (&child_block);
|
|
sigaddset (&child_block, SIGCHLD);
|
|
sigprocmask (SIG_BLOCK, &child_block, &oldmask);
|
|
sig_send (NULL, __SIGNOHOLD);
|
|
}
|
|
}
|
|
~system_call_handle ()
|
|
{
|
|
if (is_system_call ())
|
|
{
|
|
signal (SIGINT, oldint);
|
|
signal (SIGQUIT, oldquit);
|
|
sigprocmask (SIG_SETMASK, &oldmask, NULL);
|
|
}
|
|
}
|
|
# undef cleanup
|
|
};
|
|
|
|
child_info_spawn NO_COPY ch_spawn;
|
|
|
|
int
|
|
child_info_spawn::worker (const char *prog_arg, const char *const *argv,
|
|
const char *const envp[], int mode,
|
|
int in__stdin, int in__stdout)
|
|
{
|
|
bool rc;
|
|
pid_t cygpid;
|
|
int res = -1;
|
|
|
|
/* Check if we have been called from exec{lv}p or spawn{lv}p and mask
|
|
mode to keep only the spawn mode. */
|
|
bool p_type_exec = !!(mode & _P_PATH_TYPE_EXEC);
|
|
mode = _P_MODE (mode);
|
|
|
|
if (prog_arg == NULL)
|
|
{
|
|
syscall_printf ("prog_arg is NULL");
|
|
set_errno (EFAULT); /* As on Linux. */
|
|
return -1;
|
|
}
|
|
if (!prog_arg[0])
|
|
{
|
|
syscall_printf ("prog_arg is empty");
|
|
set_errno (ENOENT); /* Per POSIX */
|
|
return -1;
|
|
}
|
|
|
|
syscall_printf ("mode = %d, prog_arg = %.9500s", mode, prog_arg);
|
|
|
|
/* FIXME: This is no error condition on Linux. */
|
|
if (argv == NULL)
|
|
{
|
|
syscall_printf ("argv is NULL");
|
|
set_errno (EINVAL);
|
|
return -1;
|
|
}
|
|
|
|
av newargv;
|
|
linebuf cmd;
|
|
PWCHAR envblock = NULL;
|
|
path_conv real_path;
|
|
bool reset_sendsig = false;
|
|
|
|
tmp_pathbuf tp;
|
|
PWCHAR runpath = tp.w_get ();
|
|
int c_flags;
|
|
|
|
bool null_app_name = false;
|
|
STARTUPINFOW si = {};
|
|
int looped = 0;
|
|
|
|
system_call_handle system_call (mode == _P_SYSTEM);
|
|
|
|
__try
|
|
{
|
|
child_info_types chtype;
|
|
if (mode == _P_OVERLAY)
|
|
chtype = _CH_EXEC;
|
|
else
|
|
chtype = _CH_SPAWN;
|
|
|
|
moreinfo = cygheap_exec_info::alloc ();
|
|
|
|
/* CreateProcess takes one long string that is the command line (sigh).
|
|
We need to quote any argument that has whitespace or embedded "'s. */
|
|
|
|
int ac;
|
|
for (ac = 0; argv[ac]; ac++)
|
|
/* nothing */;
|
|
|
|
int err;
|
|
const char *ext;
|
|
if ((ext = perhaps_suffix (prog_arg, real_path, err, FE_NADA)) == NULL)
|
|
{
|
|
set_errno (err);
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
|
|
res = newargv.setup (prog_arg, real_path, ext, ac, argv, p_type_exec);
|
|
|
|
if (res)
|
|
__leave;
|
|
|
|
if (!real_path.iscygexec () && ::cygheap->cwd.get_error ())
|
|
{
|
|
small_printf ("Error: Current working directory %s.\n"
|
|
"Can't start native Windows application from here.\n\n",
|
|
::cygheap->cwd.get_error_desc ());
|
|
set_errno (::cygheap->cwd.get_error ());
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
|
|
if (ac == 3 && argv[1][0] == '/' && tolower (argv[1][1]) == 'c' &&
|
|
(iscmd (argv[0], "command.com") || iscmd (argv[0], "cmd.exe")))
|
|
{
|
|
real_path.check (prog_arg);
|
|
cmd.add ("\"");
|
|
if (!real_path.error)
|
|
cmd.add (real_path.get_win32 ());
|
|
else
|
|
cmd.add (argv[0]);
|
|
cmd.add ("\"");
|
|
cmd.add (" ");
|
|
cmd.add (argv[1]);
|
|
cmd.add (" ");
|
|
cmd.add (argv[2]);
|
|
real_path.set_path (argv[0]);
|
|
null_app_name = true;
|
|
}
|
|
else
|
|
{
|
|
if (real_path.iscygexec ())
|
|
{
|
|
moreinfo->argc = newargv.argc;
|
|
moreinfo->argv = newargv;
|
|
}
|
|
if ((wincmdln || !real_path.iscygexec ())
|
|
&& !cmd.fromargv (newargv, real_path.get_win32 (),
|
|
real_path.iscygexec ()))
|
|
{
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
|
|
|
|
if (mode != _P_OVERLAY || !real_path.iscygexec ()
|
|
|| !DuplicateHandle (GetCurrentProcess (), myself.shared_handle (),
|
|
GetCurrentProcess (), &moreinfo->myself_pinfo,
|
|
0, TRUE, DUPLICATE_SAME_ACCESS))
|
|
moreinfo->myself_pinfo = NULL;
|
|
else
|
|
VerifyHandle (moreinfo->myself_pinfo);
|
|
}
|
|
|
|
PROCESS_INFORMATION pi;
|
|
pi.hProcess = pi.hThread = NULL;
|
|
pi.dwProcessId = pi.dwThreadId = 0;
|
|
|
|
/* Set up needed handles for stdio */
|
|
si.dwFlags = STARTF_USESTDHANDLES;
|
|
si.hStdInput = handle ((in__stdin < 0 ? 0 : in__stdin), false);
|
|
si.hStdOutput = handle ((in__stdout < 0 ? 1 : in__stdout), true);
|
|
si.hStdError = handle (2, true);
|
|
|
|
si.cb = sizeof (si);
|
|
|
|
c_flags = GetPriorityClass (GetCurrentProcess ());
|
|
sigproc_printf ("priority class %d", c_flags);
|
|
|
|
c_flags |= CREATE_SEPARATE_WOW_VDM | CREATE_UNICODE_ENVIRONMENT;
|
|
|
|
/* We're adding the CREATE_BREAKAWAY_FROM_JOB flag here to workaround
|
|
issues with the "Program Compatibility Assistant (PCA) Service".
|
|
For some reason, when starting long running sessions from mintty(*),
|
|
the affected svchost.exe process takes more and more memory and at one
|
|
point takes over the CPU. At this point the machine becomes
|
|
unresponsive. The only way to get back to normal is to stop the
|
|
entire mintty session, or to stop the PCA service. However, a process
|
|
which is controlled by PCA is part of a compatibility job, which
|
|
allows child processes to break away from the job. This helps to
|
|
avoid this issue.
|
|
|
|
First we call IsProcessInJob. It fetches the information whether or
|
|
not we're part of a job 20 times faster than QueryInformationJobObject.
|
|
|
|
(*) Note that this is not mintty's fault. It has just been observed
|
|
with mintty in the first place. See the archives for more info:
|
|
http://cygwin.com/ml/cygwin-developers/2012-02/msg00018.html */
|
|
JOBOBJECT_BASIC_LIMIT_INFORMATION jobinfo;
|
|
BOOL is_in_job;
|
|
|
|
if (IsProcessInJob (GetCurrentProcess (), NULL, &is_in_job)
|
|
&& is_in_job
|
|
&& QueryInformationJobObject (NULL, JobObjectBasicLimitInformation,
|
|
&jobinfo, sizeof jobinfo, NULL)
|
|
&& (jobinfo.LimitFlags & (JOB_OBJECT_LIMIT_BREAKAWAY_OK
|
|
| JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)))
|
|
{
|
|
debug_printf ("Add CREATE_BREAKAWAY_FROM_JOB");
|
|
c_flags |= CREATE_BREAKAWAY_FROM_JOB;
|
|
}
|
|
|
|
if (mode == _P_DETACH)
|
|
c_flags |= DETACHED_PROCESS;
|
|
else
|
|
fhandler_console::need_invisible ();
|
|
|
|
if (mode != _P_OVERLAY)
|
|
myself->exec_sendsig = NULL;
|
|
else
|
|
{
|
|
/* Reset sendsig so that any process which wants to send a signal
|
|
to this pid will wait for the new process to become active.
|
|
Save the old value in case the exec fails. */
|
|
if (!myself->exec_sendsig)
|
|
{
|
|
myself->exec_sendsig = myself->sendsig;
|
|
myself->exec_dwProcessId = myself->dwProcessId;
|
|
myself->sendsig = NULL;
|
|
reset_sendsig = true;
|
|
}
|
|
}
|
|
|
|
if (null_app_name)
|
|
runpath = NULL;
|
|
else
|
|
{
|
|
USHORT len = real_path.get_nt_native_path ()->Length / sizeof (WCHAR);
|
|
if (RtlEqualUnicodePathPrefix (real_path.get_nt_native_path (),
|
|
&ro_u_natp, FALSE))
|
|
{
|
|
runpath = real_path.get_wide_win32_path (runpath);
|
|
/* If the executable path length is < MAX_PATH, make sure the long
|
|
path win32 prefix is removed from the path to make subsequent
|
|
not long path aware native Win32 child processes happy. */
|
|
if (len < MAX_PATH + 4)
|
|
{
|
|
if (runpath[5] == ':')
|
|
runpath += 4;
|
|
else if (len < MAX_PATH + 6)
|
|
*(runpath += 6) = L'\\';
|
|
}
|
|
}
|
|
else if (len < NT_MAX_PATH - ro_u_globalroot.Length / sizeof (WCHAR))
|
|
{
|
|
UNICODE_STRING rpath;
|
|
|
|
RtlInitEmptyUnicodeString (&rpath, runpath,
|
|
(NT_MAX_PATH - 1) * sizeof (WCHAR));
|
|
RtlCopyUnicodeString (&rpath, &ro_u_globalroot);
|
|
RtlAppendUnicodeStringToString (&rpath,
|
|
real_path.get_nt_native_path ());
|
|
}
|
|
else
|
|
{
|
|
set_errno (ENAMETOOLONG);
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
}
|
|
|
|
cygbench ("spawn-worker");
|
|
|
|
if (!real_path.iscygexec())
|
|
::cygheap->fdtab.set_file_pointers_for_exec ();
|
|
|
|
/* If we switch the user, merge the user's Windows environment. */
|
|
bool switch_user = ::cygheap->user.issetuid ()
|
|
&& (::cygheap->user.saved_uid
|
|
!= ::cygheap->user.real_uid);
|
|
moreinfo->envp = build_env (envp, envblock, moreinfo->envc,
|
|
real_path.iscygexec (),
|
|
switch_user ? ::cygheap->user.primary_token ()
|
|
: NULL);
|
|
if (!moreinfo->envp || !envblock)
|
|
{
|
|
set_errno (E2BIG);
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
set (chtype, real_path.iscygexec ());
|
|
__stdin = in__stdin;
|
|
__stdout = in__stdout;
|
|
record_children ();
|
|
|
|
si.lpReserved2 = (LPBYTE) this;
|
|
si.cbReserved2 = sizeof (*this);
|
|
|
|
/* Depends on set call above.
|
|
Some file types might need extra effort in the parent after CreateProcess
|
|
and before copying the datastructures to the child. So we have to start
|
|
the child in suspend state, unfortunately, to avoid a race condition. */
|
|
if (!newargv.win16_exe
|
|
&& (!iscygwin () || mode != _P_OVERLAY
|
|
|| ::cygheap->fdtab.need_fixup_before ()))
|
|
c_flags |= CREATE_SUSPENDED;
|
|
/* If a native application should be spawned, we test here if the spawning
|
|
process is running in a console and, if so, if it's a foreground or
|
|
background process. If it's a background process, we start the native
|
|
process with the CREATE_NEW_PROCESS_GROUP flag set. This lets the native
|
|
process ignore Ctrl-C by default. If we don't do that, pressing Ctrl-C
|
|
in a console will break native processes running in the background,
|
|
because the Ctrl-C event is sent to all processes in the console, unless
|
|
they ignore it explicitely. CREATE_NEW_PROCESS_GROUP does that for us. */
|
|
if (!iscygwin () && fhandler_console::exists ()
|
|
&& fhandler_console::tc_getpgid () != myself->pgid)
|
|
c_flags |= CREATE_NEW_PROCESS_GROUP;
|
|
refresh_cygheap ();
|
|
|
|
if (mode == _P_DETACH)
|
|
/* all set */;
|
|
else if (mode != _P_OVERLAY || !my_wr_proc_pipe)
|
|
prefork ();
|
|
else
|
|
wr_proc_pipe = my_wr_proc_pipe;
|
|
|
|
/* Don't allow child to inherit these handles if it's not a Cygwin program.
|
|
wr_proc_pipe will be injected later. parent won't be used by the child
|
|
so there is no reason for the child to have it open as it can confuse
|
|
ps into thinking that children of windows processes are all part of
|
|
the same "execed" process.
|
|
FIXME: Someday, make it so that parent is never created when starting
|
|
non-Cygwin processes. */
|
|
if (!iscygwin ())
|
|
{
|
|
SetHandleInformation (wr_proc_pipe, HANDLE_FLAG_INHERIT, 0);
|
|
SetHandleInformation (parent, HANDLE_FLAG_INHERIT, 0);
|
|
}
|
|
/* FIXME: racy */
|
|
if (mode != _P_OVERLAY)
|
|
SetHandleInformation (my_wr_proc_pipe, HANDLE_FLAG_INHERIT, 0);
|
|
parent_winpid = GetCurrentProcessId ();
|
|
|
|
loop:
|
|
/* When ruid != euid we create the new process under the current original
|
|
account and impersonate in child, this way maintaining the different
|
|
effective vs. real ids.
|
|
FIXME: If ruid != euid and ruid != saved_uid we currently give
|
|
up on ruid. The new process will have ruid == euid. */
|
|
::cygheap->user.deimpersonate ();
|
|
|
|
if (!real_path.iscygexec () && mode == _P_OVERLAY)
|
|
myself->process_state |= PID_NOTCYGWIN;
|
|
|
|
wchar_t wcmd[(size_t) cmd];
|
|
if (!::cygheap->user.issetuid ()
|
|
|| (::cygheap->user.saved_uid == ::cygheap->user.real_uid
|
|
&& ::cygheap->user.saved_gid == ::cygheap->user.real_gid
|
|
&& !::cygheap->user.groups.issetgroups ()
|
|
&& !::cygheap->user.setuid_to_restricted))
|
|
{
|
|
rc = CreateProcessW (runpath, /* image name - with full path */
|
|
cmd.wcs (wcmd),/* what was passed to exec */
|
|
&sec_none_nih, /* process security attrs */
|
|
&sec_none_nih, /* thread security attrs */
|
|
TRUE, /* inherit handles from parent */
|
|
c_flags,
|
|
envblock, /* environment */
|
|
NULL,
|
|
&si,
|
|
&pi);
|
|
}
|
|
else
|
|
{
|
|
/* Give access to myself */
|
|
if (mode == _P_OVERLAY)
|
|
myself.set_acl();
|
|
|
|
HWINSTA hwst = NULL;
|
|
HWINSTA hwst_orig = GetProcessWindowStation ();
|
|
HDESK hdsk = NULL;
|
|
HDESK hdsk_orig = GetThreadDesktop (GetCurrentThreadId ());
|
|
/* Don't create WindowStation and Desktop for restricted child. */
|
|
if (!::cygheap->user.setuid_to_restricted)
|
|
{
|
|
PSECURITY_ATTRIBUTES sa;
|
|
WCHAR sid[128];
|
|
WCHAR wstname[1024] = { L'\0' };
|
|
|
|
sa = sec_user ((PSECURITY_ATTRIBUTES) alloca (1024),
|
|
::cygheap->user.sid ());
|
|
/* We're creating a window station per user, not per logon
|
|
session First of all we might not have a valid logon session
|
|
for the user (logon by create_token), and second, it doesn't
|
|
make sense in terms of security to create a new window
|
|
station for every logon of the same user. It just fills up
|
|
the system with window stations for no good reason. */
|
|
hwst = CreateWindowStationW (::cygheap->user.get_windows_id (sid),
|
|
0, GENERIC_READ | GENERIC_WRITE, sa);
|
|
if (!hwst)
|
|
system_printf ("CreateWindowStation failed, %E");
|
|
else if (!SetProcessWindowStation (hwst))
|
|
system_printf ("SetProcessWindowStation failed, %E");
|
|
else if (!(hdsk = CreateDesktopW (L"Default", NULL, NULL, 0,
|
|
GENERIC_ALL, sa)))
|
|
system_printf ("CreateDesktop failed, %E");
|
|
else
|
|
{
|
|
wcpcpy (wcpcpy (wstname, sid), L"\\Default");
|
|
si.lpDesktop = wstname;
|
|
debug_printf ("Desktop: %W", si.lpDesktop);
|
|
}
|
|
}
|
|
|
|
rc = CreateProcessAsUserW (::cygheap->user.primary_token (),
|
|
runpath, /* image name - with full path */
|
|
cmd.wcs (wcmd),/* what was passed to exec */
|
|
&sec_none_nih, /* process security attrs */
|
|
&sec_none_nih, /* thread security attrs */
|
|
TRUE, /* inherit handles from parent */
|
|
c_flags,
|
|
envblock, /* environment */
|
|
NULL,
|
|
&si,
|
|
&pi);
|
|
if (hwst)
|
|
{
|
|
SetProcessWindowStation (hwst_orig);
|
|
CloseWindowStation (hwst);
|
|
}
|
|
if (hdsk)
|
|
{
|
|
SetThreadDesktop (hdsk_orig);
|
|
CloseDesktop (hdsk);
|
|
}
|
|
}
|
|
|
|
if (mode != _P_OVERLAY)
|
|
SetHandleInformation (my_wr_proc_pipe, HANDLE_FLAG_INHERIT,
|
|
HANDLE_FLAG_INHERIT);
|
|
|
|
/* Set errno now so that debugging messages from it appear before our
|
|
final debugging message [this is a general rule for debugging
|
|
messages]. */
|
|
if (!rc)
|
|
{
|
|
__seterrno ();
|
|
syscall_printf ("CreateProcess failed, %E");
|
|
/* If this was a failed exec, restore the saved sendsig. */
|
|
if (reset_sendsig)
|
|
{
|
|
myself->sendsig = myself->exec_sendsig;
|
|
myself->exec_sendsig = NULL;
|
|
}
|
|
myself->process_state &= ~PID_NOTCYGWIN;
|
|
/* Reset handle inheritance to default when the execution of a'
|
|
non-Cygwin process fails. Only need to do this for _P_OVERLAY
|
|
since the handle will be closed otherwise. Don't need to do
|
|
this for 'parent' since it will be closed in every case.
|
|
See FIXME above. */
|
|
if (!iscygwin () && mode == _P_OVERLAY)
|
|
SetHandleInformation (wr_proc_pipe, HANDLE_FLAG_INHERIT,
|
|
HANDLE_FLAG_INHERIT);
|
|
if (wr_proc_pipe == my_wr_proc_pipe)
|
|
wr_proc_pipe = NULL; /* We still own it: don't nuke in destructor */
|
|
|
|
/* Restore impersonation. In case of _P_OVERLAY this isn't
|
|
allowed since it would overwrite child data. */
|
|
if (mode != _P_OVERLAY)
|
|
::cygheap->user.reimpersonate ();
|
|
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
|
|
/* The CREATE_SUSPENDED case is handled below */
|
|
if (iscygwin () && !(c_flags & CREATE_SUSPENDED))
|
|
strace.write_childpid (pi.dwProcessId);
|
|
|
|
/* Fixup the parent data structures if needed and resume the child's
|
|
main thread. */
|
|
if (::cygheap->fdtab.need_fixup_before ())
|
|
::cygheap->fdtab.fixup_before_exec (pi.dwProcessId);
|
|
|
|
if (mode != _P_OVERLAY)
|
|
cygpid = cygwin_pid (pi.dwProcessId);
|
|
else
|
|
cygpid = myself->pid;
|
|
|
|
/* Print the original program name here so the user can see that too. */
|
|
syscall_printf ("pid %d, prog_arg %s, cmd line %.9500s)",
|
|
rc ? cygpid : (unsigned int) -1, prog_arg, (const char *) cmd);
|
|
|
|
/* Name the handle similarly to proc_subproc. */
|
|
ProtectHandle1 (pi.hProcess, childhProc);
|
|
|
|
if (mode == _P_OVERLAY)
|
|
{
|
|
myself->dwProcessId = pi.dwProcessId;
|
|
strace.execing = 1;
|
|
myself.hProcess = hExeced = pi.hProcess;
|
|
real_path.get_wide_win32_path (myself->progname); // FIXME: race?
|
|
sigproc_printf ("new process name %W", myself->progname);
|
|
if (!iscygwin ())
|
|
close_all_files ();
|
|
}
|
|
else
|
|
{
|
|
myself->set_has_pgid_children ();
|
|
ProtectHandle (pi.hThread);
|
|
pinfo child (cygpid,
|
|
PID_IN_USE | (real_path.iscygexec () ? 0 : PID_NOTCYGWIN));
|
|
if (!child)
|
|
{
|
|
syscall_printf ("pinfo failed");
|
|
if (get_errno () != ENOMEM)
|
|
set_errno (EAGAIN);
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
child->dwProcessId = pi.dwProcessId;
|
|
child.hProcess = pi.hProcess;
|
|
|
|
real_path.get_wide_win32_path (child->progname);
|
|
/* FIXME: This introduces an unreferenced, open handle into the child.
|
|
The purpose is to keep the pid shared memory open so that all of
|
|
the fields filled out by child.remember do not disappear and so
|
|
there is not a brief period during which the pid is not available.
|
|
However, we should try to find another way to do this eventually. */
|
|
DuplicateHandle (GetCurrentProcess (), child.shared_handle (),
|
|
pi.hProcess, NULL, 0, 0, DUPLICATE_SAME_ACCESS);
|
|
child->start_time = time (NULL); /* Register child's starting time. */
|
|
child->nice = myself->nice;
|
|
postfork (child);
|
|
if (!child.remember (mode == _P_DETACH))
|
|
{
|
|
/* FIXME: Child in strange state now */
|
|
CloseHandle (pi.hProcess);
|
|
ForceCloseHandle (pi.hThread);
|
|
res = -1;
|
|
__leave;
|
|
}
|
|
}
|
|
|
|
/* Start the child running */
|
|
if (c_flags & CREATE_SUSPENDED)
|
|
{
|
|
/* Inject a non-inheritable wr_proc_pipe handle into child so that we
|
|
can accurately track when the child exits without keeping this
|
|
process waiting around for it to exit. */
|
|
if (!iscygwin ())
|
|
DuplicateHandle (GetCurrentProcess (), wr_proc_pipe, pi.hProcess, NULL,
|
|
0, false, DUPLICATE_SAME_ACCESS);
|
|
ResumeThread (pi.hThread);
|
|
if (iscygwin ())
|
|
strace.write_childpid (pi.dwProcessId);
|
|
}
|
|
ForceCloseHandle (pi.hThread);
|
|
|
|
sigproc_printf ("spawned windows pid %d", pi.dwProcessId);
|
|
|
|
bool synced;
|
|
if ((mode == _P_DETACH || mode == _P_NOWAIT) && !iscygwin ())
|
|
synced = false;
|
|
else
|
|
/* Just mark a non-cygwin process as 'synced'. We will still eventually
|
|
wait for it to exit in maybe_set_exit_code_from_windows(). */
|
|
synced = iscygwin () ? sync (pi.dwProcessId, pi.hProcess, INFINITE) : true;
|
|
|
|
switch (mode)
|
|
{
|
|
case _P_OVERLAY:
|
|
myself.hProcess = pi.hProcess;
|
|
if (!synced)
|
|
{
|
|
if (!proc_retry (pi.hProcess))
|
|
{
|
|
looped++;
|
|
goto loop;
|
|
}
|
|
close_all_files (true);
|
|
}
|
|
else
|
|
{
|
|
if (iscygwin ())
|
|
close_all_files (true);
|
|
if (!my_wr_proc_pipe
|
|
&& WaitForSingleObject (pi.hProcess, 0) == WAIT_TIMEOUT)
|
|
wait_for_myself ();
|
|
}
|
|
myself.exit (EXITCODE_NOSET);
|
|
break;
|
|
case _P_WAIT:
|
|
case _P_SYSTEM:
|
|
system_call.arm ();
|
|
if (waitpid (cygpid, &res, 0) != cygpid)
|
|
res = -1;
|
|
break;
|
|
case _P_DETACH:
|
|
res = 0; /* Lost all memory of this child. */
|
|
break;
|
|
case _P_NOWAIT:
|
|
case _P_NOWAITO:
|
|
case _P_VFORK:
|
|
res = cygpid;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
if (get_errno () == ENOMEM)
|
|
set_errno (E2BIG);
|
|
else
|
|
set_errno (EFAULT);
|
|
res = -1;
|
|
}
|
|
__endtry
|
|
this->cleanup ();
|
|
if (envblock)
|
|
free (envblock);
|
|
return (int) res;
|
|
}
|
|
|
|
extern "C" int
|
|
cwait (int *result, int pid, int)
|
|
{
|
|
return waitpid (pid, result, 0);
|
|
}
|
|
|
|
/*
|
|
* Helper function for spawn runtime calls.
|
|
* Doesn't search the path.
|
|
*/
|
|
|
|
extern "C" int
|
|
spawnve (int mode, const char *path, const char *const *argv,
|
|
const char *const *envp)
|
|
{
|
|
static char *const empty_env[] = { NULL };
|
|
|
|
int ret;
|
|
|
|
syscall_printf ("spawnve (%s, %s, %p)", path, argv[0], envp);
|
|
|
|
if (!envp)
|
|
envp = empty_env;
|
|
|
|
switch (_P_MODE (mode))
|
|
{
|
|
case _P_OVERLAY:
|
|
ch_spawn.worker (path, argv, envp, mode);
|
|
/* Errno should be set by worker. */
|
|
ret = -1;
|
|
break;
|
|
case _P_VFORK:
|
|
case _P_NOWAIT:
|
|
case _P_NOWAITO:
|
|
case _P_WAIT:
|
|
case _P_DETACH:
|
|
case _P_SYSTEM:
|
|
ret = ch_spawn.worker (path, argv, envp, mode);
|
|
break;
|
|
default:
|
|
set_errno (EINVAL);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* spawn functions as implemented in the MS runtime library.
|
|
* Most of these based on (and copied from) newlib/libc/posix/execXX.c
|
|
*/
|
|
|
|
extern "C" int
|
|
spawnl (int mode, const char *path, const char *arg0, ...)
|
|
{
|
|
int i;
|
|
va_list args;
|
|
const char *argv[256];
|
|
|
|
va_start (args, arg0);
|
|
argv[0] = arg0;
|
|
i = 1;
|
|
|
|
do
|
|
argv[i] = va_arg (args, const char *);
|
|
while (argv[i++] != NULL);
|
|
|
|
va_end (args);
|
|
|
|
return spawnve (mode, path, (char * const *) argv, cur_environ ());
|
|
}
|
|
|
|
extern "C" int
|
|
spawnle (int mode, const char *path, const char *arg0, ...)
|
|
{
|
|
int i;
|
|
va_list args;
|
|
const char * const *envp;
|
|
const char *argv[256];
|
|
|
|
va_start (args, arg0);
|
|
argv[0] = arg0;
|
|
i = 1;
|
|
|
|
do
|
|
argv[i] = va_arg (args, const char *);
|
|
while (argv[i++] != NULL);
|
|
|
|
envp = va_arg (args, const char * const *);
|
|
va_end (args);
|
|
|
|
return spawnve (mode, path, (char * const *) argv, (char * const *) envp);
|
|
}
|
|
|
|
extern "C" int
|
|
spawnlp (int mode, const char *file, const char *arg0, ...)
|
|
{
|
|
int i;
|
|
va_list args;
|
|
const char *argv[256];
|
|
path_conv buf;
|
|
|
|
va_start (args, arg0);
|
|
argv[0] = arg0;
|
|
i = 1;
|
|
|
|
do
|
|
argv[i] = va_arg (args, const char *);
|
|
while (argv[i++] != NULL);
|
|
|
|
va_end (args);
|
|
|
|
return spawnve (mode | _P_PATH_TYPE_EXEC, find_exec (file, buf),
|
|
(char * const *) argv, cur_environ ());
|
|
}
|
|
|
|
extern "C" int
|
|
spawnlpe (int mode, const char *file, const char *arg0, ...)
|
|
{
|
|
int i;
|
|
va_list args;
|
|
const char * const *envp;
|
|
const char *argv[256];
|
|
path_conv buf;
|
|
|
|
va_start (args, arg0);
|
|
argv[0] = arg0;
|
|
i = 1;
|
|
|
|
do
|
|
argv[i] = va_arg (args, const char *);
|
|
while (argv[i++] != NULL);
|
|
|
|
envp = va_arg (args, const char * const *);
|
|
va_end (args);
|
|
|
|
return spawnve (mode | _P_PATH_TYPE_EXEC, find_exec (file, buf),
|
|
(char * const *) argv, envp);
|
|
}
|
|
|
|
extern "C" int
|
|
spawnv (int mode, const char *path, const char * const *argv)
|
|
{
|
|
return spawnve (mode, path, argv, cur_environ ());
|
|
}
|
|
|
|
extern "C" int
|
|
spawnvp (int mode, const char *file, const char * const *argv)
|
|
{
|
|
path_conv buf;
|
|
return spawnve (mode | _P_PATH_TYPE_EXEC, find_exec (file, buf), argv,
|
|
cur_environ ());
|
|
}
|
|
|
|
extern "C" int
|
|
spawnvpe (int mode, const char *file, const char * const *argv,
|
|
const char * const *envp)
|
|
{
|
|
path_conv buf;
|
|
return spawnve (mode | _P_PATH_TYPE_EXEC, find_exec (file, buf), argv, envp);
|
|
}
|
|
|
|
int
|
|
av::setup (const char *prog_arg, path_conv& real_path, const char *ext,
|
|
int argc, const char *const *argv, bool p_type_exec)
|
|
{
|
|
const char *p;
|
|
bool exeext = ascii_strcasematch (ext, ".exe");
|
|
new (this) av (argc, argv);
|
|
if ((exeext && real_path.iscygexec ()) || ascii_strcasematch (ext, ".bat")
|
|
|| (!*ext && ((p = ext - 4) > real_path.get_win32 ())
|
|
&& (ascii_strcasematch (p, ".bat") || ascii_strcasematch (p, ".cmd")
|
|
|| ascii_strcasematch (p, ".btm"))))
|
|
/* no extra checks needed */;
|
|
else
|
|
while (1)
|
|
{
|
|
char *pgm = NULL;
|
|
char *arg1 = NULL;
|
|
char *ptr, *buf;
|
|
OBJECT_ATTRIBUTES attr;
|
|
IO_STATUS_BLOCK io;
|
|
HANDLE h;
|
|
NTSTATUS status;
|
|
LARGE_INTEGER size;
|
|
|
|
status = NtOpenFile (&h, SYNCHRONIZE | GENERIC_READ,
|
|
real_path.get_object_attr (attr, sec_none_nih),
|
|
&io, FILE_SHARE_VALID_FLAGS,
|
|
FILE_SYNCHRONOUS_IO_NONALERT
|
|
| FILE_OPEN_FOR_BACKUP_INTENT
|
|
| FILE_NON_DIRECTORY_FILE);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
/* File is not readable? Doesn't mean it's not executable.
|
|
Test for executability and if so, just assume the file is
|
|
a cygwin executable and go ahead. */
|
|
if (status == STATUS_ACCESS_DENIED && real_path.has_acls ()
|
|
&& check_file_access (real_path, X_OK, true) == 0)
|
|
{
|
|
real_path.set_cygexec (true);
|
|
break;
|
|
}
|
|
SetLastError (RtlNtStatusToDosError (status));
|
|
goto err;
|
|
}
|
|
if (!GetFileSizeEx (h, &size))
|
|
{
|
|
NtClose (h);
|
|
goto err;
|
|
}
|
|
if (size.QuadPart > (LONGLONG) wincap.allocation_granularity ())
|
|
size.LowPart = wincap.allocation_granularity ();
|
|
|
|
HANDLE hm = CreateFileMapping (h, &sec_none_nih, PAGE_READONLY,
|
|
0, 0, NULL);
|
|
NtClose (h);
|
|
if (!hm)
|
|
{
|
|
/* ERROR_FILE_INVALID indicates very likely an empty file. */
|
|
if (GetLastError () == ERROR_FILE_INVALID)
|
|
{
|
|
debug_printf ("zero length file, treat as script.");
|
|
goto just_shell;
|
|
}
|
|
goto err;
|
|
}
|
|
/* Try to map the first 64K of the image. That's enough for the local
|
|
tests, and it's enough for hook_or_detect_cygwin to compute the IAT
|
|
address. */
|
|
buf = (char *) MapViewOfFile (hm, FILE_MAP_READ, 0, 0, size.LowPart);
|
|
if (!buf)
|
|
{
|
|
CloseHandle (hm);
|
|
goto err;
|
|
}
|
|
|
|
{
|
|
__try
|
|
{
|
|
if (buf[0] == 'M' && buf[1] == 'Z')
|
|
{
|
|
WORD subsys;
|
|
unsigned off = (unsigned char) buf[0x18] | (((unsigned char) buf[0x19]) << 8);
|
|
win16_exe = off < sizeof (IMAGE_DOS_HEADER);
|
|
if (!win16_exe)
|
|
real_path.set_cygexec (hook_or_detect_cygwin (buf, NULL,
|
|
subsys, hm));
|
|
else
|
|
real_path.set_cygexec (false);
|
|
UnmapViewOfFile (buf);
|
|
CloseHandle (hm);
|
|
break;
|
|
}
|
|
}
|
|
__except (NO_ERROR)
|
|
{
|
|
UnmapViewOfFile (buf);
|
|
CloseHandle (hm);
|
|
real_path.set_cygexec (false);
|
|
break;
|
|
}
|
|
__endtry
|
|
}
|
|
CloseHandle (hm);
|
|
|
|
debug_printf ("%s is possibly a script", real_path.get_win32 ());
|
|
|
|
ptr = buf;
|
|
if (*ptr++ == '#' && *ptr++ == '!')
|
|
{
|
|
ptr += strspn (ptr, " \t");
|
|
size_t len = strcspn (ptr, "\r\n");
|
|
while (ptr[len - 1] == ' ' || ptr[len - 1] == '\t')
|
|
len--;
|
|
if (len)
|
|
{
|
|
char *namebuf = (char *) alloca (len + 1);
|
|
memcpy (namebuf, ptr, len);
|
|
namebuf[len] = '\0';
|
|
for (ptr = pgm = namebuf; *ptr; ptr++)
|
|
if (!arg1 && (*ptr == ' ' || *ptr == '\t'))
|
|
{
|
|
/* Null terminate the initial command and step over any
|
|
additional white space. If we've hit the end of the
|
|
line, exit the loop. Otherwise, we've found the first
|
|
argument. Position the current pointer on the last known
|
|
white space. */
|
|
*ptr = '\0';
|
|
char *newptr = ptr + 1;
|
|
newptr += strspn (newptr, " \t");
|
|
if (!*newptr)
|
|
break;
|
|
arg1 = newptr;
|
|
ptr = newptr - 1;
|
|
}
|
|
}
|
|
}
|
|
UnmapViewOfFile (buf);
|
|
just_shell:
|
|
if (!pgm)
|
|
{
|
|
if (!p_type_exec)
|
|
{
|
|
/* Not called from exec[lv]p. Don't try to treat as script. */
|
|
debug_printf ("%s is not a valid executable",
|
|
real_path.get_win32 ());
|
|
set_errno (ENOEXEC);
|
|
return -1;
|
|
}
|
|
if (ascii_strcasematch (ext, ".com"))
|
|
break;
|
|
pgm = (char *) "/bin/sh";
|
|
arg1 = NULL;
|
|
}
|
|
|
|
/* Check if script is executable. Otherwise we start non-executable
|
|
scripts successfully, which is incorrect behaviour. */
|
|
if (real_path.has_acls ()
|
|
&& check_file_access (real_path, X_OK, true) < 0)
|
|
return -1; /* errno is already set. */
|
|
|
|
/* Replace argv[0] with the full path to the script if this is the
|
|
first time through the loop. */
|
|
replace0_maybe (prog_arg);
|
|
|
|
/* pointers:
|
|
* pgm interpreter name
|
|
* arg1 optional string
|
|
*/
|
|
if (arg1)
|
|
unshift (arg1);
|
|
|
|
find_exec (pgm, real_path, "PATH", FE_NADA, &ext);
|
|
unshift (real_path.get_posix ());
|
|
}
|
|
if (real_path.iscygexec ())
|
|
dup_all ();
|
|
return 0;
|
|
|
|
err:
|
|
__seterrno ();
|
|
return -1;
|
|
}
|