[Patch] Fixing the PROCESS_DUP_HANDLE security hole.
Pierre A. Humblet
pierre@phumblet.no-ip.org
Fri Nov 12 03:53:00 GMT 2004
Now that 1.5.12 is out, here is a patch to fix the
PROCESS_DUP_HANDLE security hole. It uses a new approach
to reparenting: the parent duplicates the exec'ed process
handle when signaled by the child.
It also handles correctly the case of a quick re-exec
(2 simultaneous reparenting), which is a weak point of
the current version.
Pierre
P.S.: I have no news about the recent patch to /bin/kill -f
2004-11-12 Pierre Humblet <pierre.humblet@ieee.org>
* pinfo.h (_pinfo::isreparenting): New element.
(_pinfo::ppid_sendsig): Ditto.
(_pinfo::exit): Suppress second argument.
* child_info.h: Update CURR_CHILD_INFO_MAGIC.
(child_info::pppid_sendsig): New element.
* sigproc.h: Add __SIGREPARENT.
(enum procstuff): Add PROC_REPARENT.
* pinfo.cc (_pinfo::exit): Suppress second argument.
If required, send reparenting signal and wait.
* spawn.cc (spawn_guts): Implement new reparenting strategy.
* sigproc.cc (proc_subproc): Reduce access to vchild->pid_handle
and vchild->ppid_handle. Set ppid_sendsig by duplication.
Add PROC_REPARENT case and simplify PROC_CHILDTERMINATED case.
(sig_send): Use ppid_sendsig to signal parent.
(init_child_info): Set pppid_sendsig.
(wait_sig): Add __SIGREPARENT case.
* dcrto.cc (dll_crt0_0): Close pppid_sendsig.
-------------- next part --------------
Index: pinfo.h
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/pinfo.h,v
retrieving revision 1.64
diff -u -p -r1.64 pinfo.h
--- pinfo.h 12 Sep 2004 03:55:42 -0000 1.64
+++ pinfo.h 29 Oct 2004 18:56:55 -0000
@@ -36,12 +36,16 @@ public:
constants below. */
DWORD process_state;
- /* If hProcess is set, it's because it came from a
- CreateProcess call. This means it's process relative
- to the thing which created the process. That's ok because
- we only use this handle from the parent. */
+ /* hProcess comes from a CreateProcess or DuplicateHandle call.
+ This means it's process relative to the thing which created
+ the process. That's ok because we only use this handle in
+ the parent, duplicating it if was temporarily set by the
+ child during reparenting. */
HANDLE hProcess;
+ /* A reparenting operation is in progress */
+ volatile bool isreparenting;
+
#define PINFO_REDIR_SIZE ((char *) &myself.procinfo->hProcess - (char *) myself.procinfo)
/* Handle associated with initial Windows pid which started it all. */
@@ -88,7 +92,7 @@ public:
HANDLE tothem;
HANDLE fromthem;
- void exit (UINT n, bool norecord = 0) __attribute__ ((noreturn, regparm(2)));
+ void exit (UINT n) __attribute__ ((noreturn, regparm(1)));
inline void set_has_pgid_children ()
{
@@ -118,6 +122,7 @@ public:
/* signals */
HANDLE sendsig;
+ HANDLE ppid_sendsig;
private:
sigset_t sig_mask;
CRITICAL_SECTION lock;
Index: sigproc.h
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/sigproc.h,v
retrieving revision 1.70
diff -u -p -r1.70 sigproc.h
--- sigproc.h 12 Sep 2004 03:47:57 -0000 1.70
+++ sigproc.h 29 Oct 2004 18:56:55 -0000
@@ -26,7 +26,8 @@ enum
__SIGDELETE = -(NSIG + 5),
__SIGFLUSHFAST = -(NSIG + 6),
__SIGHOLD = -(NSIG + 7),
- __SIGNOHOLD = -(NSIG + 8)
+ __SIGNOHOLD = -(NSIG + 8),
+ __SIGREPARENT = -(NSIG + 9)
};
#endif
@@ -38,7 +39,8 @@ enum procstuff
PROC_CHILDTERMINATED = 2, // a child died
PROC_CLEARWAIT = 3, // clear all waits - signal arrived
PROC_WAIT = 4, // setup for wait() for subproc
- PROC_NOTHING = 5 // nothing, really
+ PROC_REPARENT = 5, // reparenting signal arrived
+ PROC_NOTHING = 6 // nothing, really
};
struct sigpacket
Index: child_info.h
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/child_info.h,v
retrieving revision 1.45
diff -u -p -r1.45 child_info.h
--- child_info.h 12 Sep 2004 18:10:15 -0000 1.45
+++ child_info.h 29 Oct 2004 18:56:55 -0000
@@ -29,7 +29,7 @@ enum
#define EXEC_MAGIC_SIZE sizeof(child_info)
-#define CURR_CHILD_INFO_MAGIC 0x19c16fb6U
+#define CURR_CHILD_INFO_MAGIC 0xb2f6689eU
/* NOTE: Do not make gratuitous changes to the names or organization of the
below class. The layout is checksummed to determine compatibility between
@@ -46,6 +46,7 @@ public:
HANDLE user_h;
HANDLE parent;
HANDLE pppid_handle;
+ HANDLE pppid_sendsig;
init_cygheap *cygheap;
void *cygheap_max;
DWORD cygheap_reserve_sz;
Index: pinfo.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/pinfo.cc,v
retrieving revision 1.121
diff -u -p -r1.121 pinfo.cc
--- pinfo.cc 5 Oct 2004 02:10:15 -0000 1.121
+++ pinfo.cc 29 Oct 2004 18:56:56 -0000
@@ -102,22 +102,38 @@ pinfo_init (char **envp, int envc)
}
void
-_pinfo::exit (UINT n, bool norecord)
+_pinfo::exit (UINT n)
{
exit_state = ES_FINAL;
cygthread::terminate ();
- if (norecord)
- sigproc_terminate ();
if (this)
{
- if (!norecord)
- process_state = PID_EXITED;
-
+ extern HANDLE hExeced;
/* FIXME: There is a potential race between an execed process and its
parent here. I hated to add a mutex just for this, though. */
struct rusage r;
fill_rusage (&r, hMainProc);
add_rusage (&rusage_self, &r);
+ if (!isreparenting)
+ process_state = PID_EXITED;
+ else if (hExeced)
+ {
+ /* Tell parent about my successor */
+ DWORD nb;
+ struct sigpacket pack;
+ pack.pid = pid;
+ pack.wakeup = NULL;
+ pack.si.si_signo = __SIGREPARENT;
+ hProcess = hExeced;
+ if (!WriteFile (myself->ppid_sendsig, &pack, sizeof (pack), &nb, NULL)
+ || nb != sizeof (pack))
+ debug_printf ("WriteFile for pipe %p to pid %d failed, %E",
+ myself->ppid_sendsig, ppid);
+ else
+ /* Wait to be terminated by my parent */
+ WaitForSingleObject (myself->ppid_handle, INFINITE);
+ }
+ isreparenting = false;
}
sigproc_printf ("Calling ExitProcess %d", n);
@@ -199,7 +215,7 @@ pinfo::init (pid_t n, DWORD flag, HANDLE
else
{
if (GetLastError () == ERROR_INVALID_HANDLE)
- api_fatal ("MapViewOfFileEx(%p, in_h %p) failed, %E", h, in_h);
+ api_fatal ("MapViewOfFileEx(%p, in_h %p) failed, %E", h, in_h);
else
{
debug_printf ("MapViewOfFileEx(%p, in_h %p) failed, %E", h, in_h);
Index: sigproc.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/sigproc.cc,v
retrieving revision 1.201
diff -u -p -r1.201 sigproc.cc
--- sigproc.cc 20 Sep 2004 04:58:36 -0000 1.201
+++ sigproc.cc 29 Oct 2004 18:56:56 -0000
@@ -275,6 +275,8 @@ mychild (int pid)
/* Handle all subprocess requests
*/
#define vchild (*((pinfo *) val))
+#define wval ((waitq *) val)
+#define pval ((pid_t) val)
int __stdcall
proc_subproc (DWORD what, DWORD val)
{
@@ -285,8 +287,6 @@ proc_subproc (DWORD what, DWORD val)
waitq *w;
int thiszombie;
-#define wval ((waitq *) val)
-
sigproc_printf ("args: %x, %d", what, val);
if (!get_proc_lock (what, val)) // Serialize access to this function
@@ -308,14 +308,18 @@ proc_subproc (DWORD what, DWORD val)
}
pchildren[nchildren] = vchild;
hchildren[nchildren] = vchild->hProcess;
- if (!DuplicateHandle (hMainProc, vchild->hProcess, hMainProc, &vchild->pid_handle,
- 0, 0, DUPLICATE_SAME_ACCESS))
+ if (!DuplicateHandle (hMainProc, vchild->hProcess, hMainProc,
+ &vchild->pid_handle, 0, 0, 0))
system_printf ("Couldn't duplicate child handle for pid %d, %E", vchild->pid);
ProtectHandle1 (vchild->pid_handle, pid_handle);
- if (!DuplicateHandle (hMainProc, hMainProc, vchild->hProcess, &vchild->ppid_handle,
- SYNCHRONIZE | PROCESS_DUP_HANDLE, TRUE, 0))
+ if (!DuplicateHandle (hMainProc, hMainProc, vchild->hProcess,
+ &vchild->ppid_handle, SYNCHRONIZE, TRUE, 0))
system_printf ("Couldn't duplicate my handle<%p> for pid %d, %E", hMainProc, vchild->pid);
+ if (!DuplicateHandle (hMainProc, myself->sendsig, vchild->hProcess,
+ &vchild->ppid_sendsig, FILE_GENERIC_WRITE, TRUE, 0))
+ system_printf ("Couldn't duplicate my sendsig handle<%p> for pid %d, %E", myself->sendsig, vchild->pid);
+ debug_printf("Duplicated myself->sendsig %x to vchild->ppid_sendsig %x", myself->sendsig, vchild->ppid_sendsig);
vchild->ppid = myself->pid;
vchild->uid = myself->uid;
vchild->gid = myself->gid;
@@ -332,31 +336,43 @@ proc_subproc (DWORD what, DWORD val)
wake_wait_subproc ();
break;
+ case PROC_REPARENT:
+ /* Cygwin implements an exec() as a "handoff" from one windows
+ process to another. */
+ for (int i = 0; i < nchildren; i++)
+ if (pchildren[i]->pid == pval)
+ {
+ HANDLE h = hchildren[i];
+ DWORD res = DuplicateHandle (h, pchildren[i]->hProcess, hMainProc, &pchildren[i]->hProcess,
+ 0, FALSE, DUPLICATE_SAME_ACCESS); /* want query, sync and duplicate */
+ pchildren[i]->isreparenting = false;
+ if (!res)
+ system_printf("Reparenting pid %d[%d] DuplicateHandle, %E", val, i);
+ else
+ {
+ sigproc_printf ("pid %d[%d], reparented old hProcess %p, new %p",
+ pval, i, h, pchildren[i]->hProcess);
+ ProtectHandle1 (pchildren[i]->hProcess, childhProc);
+ hchildren[i] = pchildren[i]->hProcess;
+ wake_wait_subproc ();
+ }
+ TerminateProcess (h, 0);
+ if (res)
+ ForceCloseHandle1 (h, childhProc);
+ goto out;
+ }
+ system_printf ("Unknown child, %d", pval);
+ break;
+
/* A child process had terminated.
- Possibly this is just due to an exec(). Cygwin implements an exec()
- as a "handoff" from one windows process to another. If child->hProcess
- is different from what is recorded in hchildren, then this is an exec().
- Otherwise this is a normal child termination event.
(called from wait_subproc thread) */
case PROC_CHILDTERMINATED:
- if (hchildren[val] != pchildren[val]->hProcess)
- {
- sigproc_printf ("pid %d[%d], reparented old hProcess %p, new %p",
- pchildren[val]->pid, val, hchildren[val], pchildren[val]->hProcess);
- HANDLE h = hchildren[val];
- hchildren[val] = pchildren[val]->hProcess; /* Filled out by child */
- ForceCloseHandle1 (h, childhProc);
- ProtectHandle1 (pchildren[val]->hProcess, childhProc);
- rc = 0;
- goto out; // This was an exec()
- }
-
sigproc_printf ("pid %d[%d] terminated, handle %p, nchildren %d, nzombies %d",
pchildren[val]->pid, val, hchildren[val], nchildren, nzombies);
- thiszombie = nzombies;
- zombies[nzombies] = pchildren[val]; // Add to zombie array
- zombies[nzombies++]->process_state = PID_ZOMBIE;// Walking dead
+ thiszombie = nzombies++;
+ zombies[thiszombie] = pchildren[val]; // Add to zombie array
+ zombies[thiszombie]->hProcess = hchildren[val]; /* hProcess may be invalid */
sigproc_printf ("zombifying [%d], pid %d, handle %p, nchildren %d",
val, pchildren[val]->pid, hchildren[val], nchildren);
@@ -370,12 +386,16 @@ proc_subproc (DWORD what, DWORD val)
filled up our table or if we're ignoring SIGCHLD, then we immediately
remove the process and move on. Otherwise, this process becomes a zombie
which must be reaped by a wait() call. FIXME: This is a very inelegant
- way to deal with this and could lead to process hangs. */
- if (nzombies >= NZOMBIES)
+ way to deal with this and could lead to process hangs.
+ Also discard processes that have exec'ed but failed reparenting. */
+ if (nzombies >= NZOMBIES || zombies[thiszombie]->isreparenting)
{
- sigproc_printf ("zombie table overflow %d", thiszombie);
+ sigproc_printf ("zombie discarded %d", thiszombie);
+ zombies[thiszombie]->ppid = 1; /* Important if reparenting failed */
remove_zombie (thiszombie);
}
+ else
+ zombies[thiszombie]->process_state = PID_ZOMBIE;// Walking dead
/* Don't scan the wait queue yet. Caller will send SIGCHLD to this process.
This will cause an eventual scan of waiters. */
@@ -705,6 +725,8 @@ sig_send (_pinfo *p, siginfo_t& si, _cyg
if (its_me)
sendsig = myself->sendsig;
+ else if (p->pid == myself->ppid)
+ sendsig = myself->ppid_sendsig;
else
{
for (int i = 0; !p->dwProcessId && i < 10000; i++)
@@ -796,7 +818,7 @@ sig_send (_pinfo *p, siginfo_t& si, _cyg
rc = WAIT_OBJECT_0;
sigproc_printf ("Not waiting for sigcomplete. its_me %d signal %d",
its_me, si.si_signo);
- if (!its_me)
+ if (!(its_me || p->pid == myself->ppid))
ForceCloseHandle (sendsig);
}
@@ -865,6 +887,7 @@ init_child_info (DWORD chtype, child_inf
ch->type = chtype;
ch->subproc_ready = subproc_ready;
ch->pppid_handle = myself->ppid_handle;
+ ch->pppid_sendsig = myself->ppid_sendsig;
ch->fhandler_union_cb = sizeof (fhandler_union);
ch->user_h = cygwin_user_h;
}
@@ -1130,7 +1153,6 @@ wait_sig (VOID *self)
#endif
continue;
}
-
sigset_t dummy_mask;
if (!pack.mask)
{
@@ -1174,6 +1196,9 @@ wait_sig (VOID *self)
clearwait = true;
}
break;
+ case __SIGREPARENT:
+ proc_subproc (PROC_REPARENT, (DWORD) pack.pid);
+ break;
default:
if (pack.si.si_signo < 0)
sig_clear (-pack.si.si_signo);
Index: spawn.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/spawn.cc,v
retrieving revision 1.153
diff -u -p -r1.153 spawn.cc
--- spawn.cc 7 Oct 2004 16:49:30 -0000 1.153
+++ spawn.cc 29 Oct 2004 18:56:57 -0000
@@ -629,10 +629,23 @@ spawn_guts (const char * prog_arg, const
flags |= DETACHED_PROCESS;
if (mode != _P_OVERLAY)
flags |= CREATE_SUSPENDED;
-#if 0 //someday
else
- myself->dwProcessId = 0;
+ {
+ /* An exec'ing exec'ed process must wait for its parent to have
+ reparented. Such a wait is very unlikely, so we simply poll.
+ As with the previous method, if the initial exec'ing process is
+ terminated from Windows after exec'ing, reparenting may not take
+ place. That will be detected by the logical parent at process
+ termination, and it will set ppid to 1. If the logical parent is
+ not alive, there is no point in waiting. */
+ while (myself->isreparenting && myself->ppid != 1
+ && my_parent_is_alive ())
+ low_priority_sleep (0);
+ myself->isreparenting = true;
+#if 0 //someday
+ myself->dwProcessId = 0;
#endif
+ }
/* Some file types (currently only sockets) need extra effort in the
parent after CreateProcess and before copying the datastructures
@@ -732,10 +745,13 @@ spawn_guts (const char * prog_arg, const
{
__seterrno ();
syscall_printf ("CreateProcess failed, %E");
-#if 0 // someday
if (mode == _P_OVERLAY)
- myself->dwProcessId = GetCurrentProcessId ();
+ {
+ myself->isreparenting = false;
+#if 0 // someday
+ myself->dwProcessId = GetCurrentProcessId ();
#endif
+ }
if (subproc_ready)
ForceCloseHandle (subproc_ready);
cygheap_setup_for_child_cleanup (newheap, &ciresrv, 0);
@@ -833,95 +849,65 @@ spawn_guts (const char * prog_arg, const
sigproc_printf ("spawned windows pid %d", pi.dwProcessId);
- bool exited;
-
res = 0;
- exited = false;
- MALLOC_CHECK;
- if (mode == _P_OVERLAY)
- {
- int nwait = 3;
- HANDLE waitbuf[3] = {pi.hProcess, signal_arrived, subproc_ready};
- for (int i = 0; i < 100; i++)
- {
- switch (WaitForMultipleObjects (nwait, waitbuf, FALSE, INFINITE))
- {
- case WAIT_OBJECT_0:
- sigproc_printf ("subprocess exited");
- DWORD exitcode;
- if (!GetExitCodeProcess (pi.hProcess, &exitcode))
- exitcode = 1;
- res |= exitcode;
- exited = true;
- break;
- case WAIT_OBJECT_0 + 1:
- sigproc_printf ("signal arrived");
- reset_signal_arrived ();
- continue;
- case WAIT_OBJECT_0 + 2:
- if (my_parent_is_alive ())
- res |= EXIT_REPARENTING;
- else if (!myself->ppid_handle)
- {
- nwait = 2;
- sigproc_terminate ();
- continue;
- }
- break;
- case WAIT_FAILED:
- system_printf ("wait failed: nwait %d, pid %d, winpid %d, %E",
- nwait, myself->pid, myself->dwProcessId);
- system_printf ("waitbuf[0] %p %d", waitbuf[0],
- WaitForSingleObject (waitbuf[0], 0));
- system_printf ("waitbuf[1] %p %d", waitbuf[1],
- WaitForSingleObject (waitbuf[1], 0));
- system_printf ("waitbuf[w] %p %d", waitbuf[2],
- WaitForSingleObject (waitbuf[2], 0));
- set_errno (ECHILD);
- try_to_debug ();
- return -1;
- }
- break;
- }
-
- ForceCloseHandle (subproc_ready);
-
- sigproc_printf ("res %p", res);
-
- if (res & EXIT_REPARENTING)
- {
- /* Try to reparent child process.
- * Make handles to child available to parent process and exit with
- * EXIT_REPARENTING status. Wait() syscall in parent will then wait
- * for newly created child.
- */
- HANDLE oldh = myself->hProcess;
- HANDLE h = myself->ppid_handle;
- sigproc_printf ("parent handle %p", h);
- int rc = DuplicateHandle (hMainProc, pi.hProcess, h, &myself->hProcess,
- 0, FALSE, DUPLICATE_SAME_ACCESS);
- sigproc_printf ("%d = DuplicateHandle, oldh %p, newh %p",
- rc, oldh, myself->hProcess);
- VerifyHandle (myself->hProcess);
- if (!rc && my_parent_is_alive ())
- {
- system_printf ("Reparent failed, parent handle %p, %E", h);
- system_printf ("my dwProcessId %d, myself->dwProcessId %d",
- GetCurrentProcessId (), myself->dwProcessId);
- system_printf ("old hProcess %p, hProcess %p", oldh, myself->hProcess);
- }
- }
-
- }
-
+
MALLOC_CHECK;
switch (mode)
{
case _P_OVERLAY:
- ForceCloseHandle1 (pi.hProcess, childhProc);
- myself->exit (res, 1);
- break;
+ {
+ int nwait = 3;
+ HANDLE waitbuf[3] = {pi.hProcess, signal_arrived, subproc_ready};
+ for (int i = 0; i < 100; i++)
+ {
+ switch (WaitForMultipleObjects (nwait, waitbuf, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ sigproc_printf ("subprocess exited");
+ DWORD exitcode;
+ if (!GetExitCodeProcess (pi.hProcess, &exitcode))
+ exitcode = 1;
+ res = exitcode;
+ ForceCloseHandle1 (pi.hProcess, childhProc);
+ myself->isreparenting = false;
+ break;
+ case WAIT_OBJECT_0 + 1:
+ sigproc_printf ("signal arrived");
+ reset_signal_arrived ();
+ continue;
+ case WAIT_OBJECT_0 + 2:
+ if (myself->ppid_handle)
+ break;
+ else
+ {
+ nwait = 2;
+ sigproc_terminate ();
+ myself->isreparenting = false;
+ continue;
+ }
+ break;
+ case WAIT_FAILED:
+ system_printf ("wait failed: nwait %d, pid %d, winpid %d, %E",
+ nwait, myself->pid, myself->dwProcessId);
+ system_printf ("waitbuf[0] %p %d", waitbuf[0],
+ WaitForSingleObject (waitbuf[0], 0));
+ system_printf ("waitbuf[1] %p %d", waitbuf[1],
+ WaitForSingleObject (waitbuf[1], 0));
+ system_printf ("waitbuf[w] %p %d", waitbuf[2],
+ WaitForSingleObject (waitbuf[2], 0));
+ set_errno (ECHILD);
+ try_to_debug ();
+ myself->isreparenting = false;
+ return -1;
+ }
+ break;
+ }
+
+ ForceCloseHandle (subproc_ready);
+ sigproc_terminate ();
+ myself->exit (res);
+ }
case _P_WAIT:
case _P_SYSTEM:
if (waitpid (cygpid, (int *) &res, 0) != cygpid)
Index: dcrt0.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/dcrt0.cc,v
retrieving revision 1.223
diff -u -p -r1.223 dcrt0.cc
--- dcrt0.cc 7 Oct 2004 16:59:02 -0000 1.223
+++ dcrt0.cc 29 Oct 2004 18:56:57 -0000
@@ -700,7 +700,10 @@ dll_crt0_0 ()
if (close_hexec_proc)
CloseHandle (spawn_info->hexec_proc);
if (close_ppid_handle)
- CloseHandle (child_proc_info->pppid_handle);
+ {
+ CloseHandle (child_proc_info->pppid_handle);
+ CloseHandle (child_proc_info->pppid_sendsig);
+ }
}
_cygtls::init ();
More information about the Cygwin-patches
mailing list