[PATCH 2/2] fork: plug a use after free of the returned process pointer
Mateusz Guzik
mjguzik at gmail.com
Tue Feb 2 04:07:55 UTC 2016
From: Mateusz Guzik <mjg at freebsd.org>
fork1 required its callers to pass a pointer to struct proc * which would
be set to the new process (if any). procdesc and racct manipulation also
used said pointer.
However, the process could have exited prior to do_fork return and be
automatically reaped, thus making this a use-after-free.
Fix the problem by letting callers indicate whether they want the pid or
the struct proc, return the process in stopped state for the latter case.
---
sys/compat/cloudabi/cloudabi_proc.c | 2 -
sys/kern/kern_fork.c | 104 +++++++++++++++++++-----------------
sys/kern/kern_racct.c | 3 +-
sys/sys/proc.h | 1 +
4 files changed, 58 insertions(+), 52 deletions(-)
diff --git a/sys/compat/cloudabi/cloudabi_proc.c b/sys/compat/cloudabi/cloudabi_proc.c
index 010efca..2bc50ca 100644
--- a/sys/compat/cloudabi/cloudabi_proc.c
+++ b/sys/compat/cloudabi/cloudabi_proc.c
@@ -77,12 +77,10 @@ cloudabi_sys_proc_fork(struct thread *td,
{
struct fork_req fr = {};
struct filecaps fcaps = {};
- struct proc *p2;
int error, fd;
cap_rights_init(&fcaps.fc_rights, CAP_FSTAT, CAP_EVENT);
fr.fr_flags = RFFDG | RFPROC | RFPROCDESC;
- fr.fr_procp = &p2;
fr.fr_pd_fd = &fd;
fr.fr_pd_fcaps = &fcaps;
error = fork1(td, &fr);
diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c
index 3b51b7f..d0c3837 100644
--- a/sys/kern/kern_fork.c
+++ b/sys/kern/kern_fork.c
@@ -102,14 +102,13 @@ int
sys_fork(struct thread *td, struct fork_args *uap)
{
struct fork_req fr = {};
- int error;
- struct proc *p2;
+ int error, pid;
fr.fr_flags = RFFDG | RFPROC;
- fr.fr_procp = &p2;
+ fr.fr_pidp = &pid;
error = fork1(td, &fr);
if (error == 0) {
- td->td_retval[0] = p2->p_pid;
+ td->td_retval[0] = pid;
td->td_retval[1] = 0;
}
return (error);
@@ -122,11 +121,10 @@ sys_pdfork(td, uap)
struct pdfork_args *uap;
{
struct fork_req fr = {};
- int error, fd;
- struct proc *p2;
+ int error, fd, pid;
fr.fr_flags = RFFDG | RFPROC | RFPROCDESC;
- fr.fr_procp = &p2;
+ fr.fr_pidp = &pid;
fr.fr_pd_fd = &fd;
fr.fr_pd_flags = uap->flags;
/*
@@ -136,7 +134,7 @@ sys_pdfork(td, uap)
*/
error = fork1(td, &fr);
if (error == 0) {
- td->td_retval[0] = p2->p_pid;
+ td->td_retval[0] = pid;
td->td_retval[1] = 0;
error = copyout(&fd, uap->fdp, sizeof(fd));
}
@@ -148,14 +146,13 @@ int
sys_vfork(struct thread *td, struct vfork_args *uap)
{
struct fork_req fr = {};
- int error;
- struct proc *p2;
+ int error, pid;
fr.fr_flags = RFFDG | RFPROC | RFPPWAIT | RFMEM;
- fr.fr_procp = &p2;
+ fr.fr_pidp = &pid;
error = fork1(td, &fr);
if (error == 0) {
- td->td_retval[0] = p2->p_pid;
+ td->td_retval[0] = pid;
td->td_retval[1] = 0;
}
return (error);
@@ -165,8 +162,7 @@ int
sys_rfork(struct thread *td, struct rfork_args *uap)
{
struct fork_req fr = {};
- struct proc *p2;
- int error;
+ int error, pid;
/* Don't allow kernel-only flags. */
if ((uap->flags & RFKERNELONLY) != 0)
@@ -174,10 +170,10 @@ sys_rfork(struct thread *td, struct rfork_args *uap)
AUDIT_ARG_FFLAGS(uap->flags);
fr.fr_flags = uap->flags;
- fr.fr_procp = &p2;
+ fr.fr_pidp = &pid;
error = fork1(td, &fr);
if (error == 0) {
- td->td_retval[0] = p2 ? p2->p_pid : 0;
+ td->td_retval[0] = pid;
td->td_retval[1] = 0;
}
return (error);
@@ -378,20 +374,21 @@ fail:
}
static void
-do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
- struct vmspace *vm2, int pdflags)
+do_fork(struct thread *td, struct fork_req *fr, struct proc *p2, struct thread *td2,
+ struct vmspace *vm2, struct file *fp_procdesc)
{
struct proc *p1, *pptr;
- int p2_held, trypid;
+ int trypid;
struct filedesc *fd;
struct filedesc_to_leader *fdtol;
struct sigacts *newsigacts;
+ int flags;
sx_assert(&proctree_lock, SX_SLOCKED);
sx_assert(&allproc_lock, SX_XLOCKED);
- p2_held = 0;
p1 = td->td_proc;
+ flags = fr->fr_flags;
trypid = fork_findpid(flags);
@@ -690,7 +687,7 @@ do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
* However, don't do this until after fork(2) can no longer fail.
*/
if (flags & RFPROCDESC)
- procdesc_new(p2, pdflags);
+ procdesc_new(p2, fr->fr_pd_flags);
/*
* Both processes are set up, now check if any loadable modules want
@@ -718,6 +715,11 @@ do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
if ((flags & RFMEM) == 0 && dtrace_fasttrap_fork)
dtrace_fasttrap_fork(p1, p2);
#endif
+ /*
+ * Hold the process so that it cannot exit after we make it runnable,
+ * but before we wait for the debugger.
+ */
+ _PHOLD(p2);
if ((p1->p_flag & (P_TRACED | P_FOLLOWFORK)) == (P_TRACED |
P_FOLLOWFORK)) {
/*
@@ -730,24 +732,12 @@ do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
td->td_dbgflags |= TDB_FORK;
td->td_dbg_forked = p2->p_pid;
td2->td_dbgflags |= TDB_STOPATFORK;
- _PHOLD(p2);
- p2_held = 1;
}
if (flags & RFPPWAIT) {
td->td_pflags |= TDP_RFPPWAIT;
td->td_rfppwait_p = p2;
}
PROC_UNLOCK(p2);
- if ((flags & RFSTOPPED) == 0) {
- /*
- * If RFSTOPPED not requested, make child runnable and
- * add to run queue.
- */
- thread_lock(td2);
- TD_SET_CAN_RUN(td2);
- sched_add(td2, SRQ_BORING);
- thread_unlock(td2);
- }
/*
* Now can be swapped.
@@ -761,14 +751,34 @@ do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
knote_fork(&p1->p_klist, p2->p_pid);
SDT_PROBE3(proc, , , create, p2, p1, flags);
+ if (flags & RFPROCDESC) {
+ procdesc_finit(p2->p_procdesc, fp_procdesc);
+ fdrop(fp_procdesc, td);
+ }
+
+ if ((flags & RFSTOPPED) == 0) {
+ /*
+ * If RFSTOPPED not requested, make child runnable and
+ * add to run queue.
+ */
+ thread_lock(td2);
+ TD_SET_CAN_RUN(td2);
+ sched_add(td2, SRQ_BORING);
+ thread_unlock(td2);
+ if (fr->fr_pidp != NULL)
+ *fr->fr_pidp = p2->p_pid;
+ } else {
+ *fr->fr_procp = p2;
+ }
+
+ PROC_LOCK(p2);
/*
* Wait until debugger is attached to child.
*/
- PROC_LOCK(p2);
while ((td2->td_dbgflags & TDB_STOPATFORK) != 0)
cv_wait(&p2->p_dbgwait, &p2->p_mtx);
- if (p2_held)
- _PRELE(p2);
+ _PRELE(p2);
+ racct_proc_fork_done(p2);
PROC_UNLOCK(p2);
}
@@ -788,6 +798,11 @@ fork1(struct thread *td, struct fork_req *fr)
flags = fr->fr_flags;
pages = fr->fr_pages;
+ if ((flags & RFSTOPPED) != 0)
+ MPASS(fr->fr_procp != NULL && fr->fr_pidp == NULL);
+ else
+ MPASS(fr->fr_procp == NULL);
+
/* Check for the undefined or unimplemented flags. */
if ((flags & ~(RFFLAGS | RFTSIGFLAGS(RFTSIGMASK))) != 0)
return (EINVAL);
@@ -821,7 +836,10 @@ fork1(struct thread *td, struct fork_req *fr)
* certain parts of a process from itself.
*/
if ((flags & RFPROC) == 0) {
- *fr->fr_procp = NULL;
+ if (fr->fr_procp != NULL)
+ *fr->fr_procp = NULL;
+ else if (fr->fr_pidp != NULL)
+ *fr->fr_pidp = 0;
return (fork_norfproc(td, flags));
}
@@ -949,17 +967,7 @@ fork1(struct thread *td, struct fork_req *fr)
lim_cur(td, RLIMIT_NPROC));
}
if (ok) {
- do_fork(td, flags, newproc, td2, vm2, fr->fr_pd_flags);
-
- /*
- * Return child proc pointer to parent.
- */
- *fr->fr_procp = newproc;
- if (flags & RFPROCDESC) {
- procdesc_finit(newproc->p_procdesc, fp_procdesc);
- fdrop(fp_procdesc, td);
- }
- racct_proc_fork_done(newproc);
+ do_fork(td, fr, newproc, td2, vm2, fp_procdesc);
return (0);
}
diff --git a/sys/kern/kern_racct.c b/sys/kern/kern_racct.c
index 0c7c0c4..ce7e2a4 100644
--- a/sys/kern/kern_racct.c
+++ b/sys/kern/kern_racct.c
@@ -957,16 +957,15 @@ void
racct_proc_fork_done(struct proc *child)
{
+ PROC_LOCK_ASSERT(child, MA_OWNED);
#ifdef RCTL
if (!racct_enable)
return;
- PROC_LOCK(child);
mtx_lock(&racct_lock);
rctl_enforce(child, RACCT_NPROC, 0);
rctl_enforce(child, RACCT_NTHR, 0);
mtx_unlock(&racct_lock);
- PROC_UNLOCK(child);
#endif
}
diff --git a/sys/sys/proc.h b/sys/sys/proc.h
index ac96566..039fd39 100644
--- a/sys/sys/proc.h
+++ b/sys/sys/proc.h
@@ -910,6 +910,7 @@ struct proc *zpfind(pid_t); /* Find zombie process by id. */
struct fork_req {
int fr_flags;
int fr_pages;
+ int *fr_pidp;
struct proc **fr_procp;
int *fr_pd_fd;
int fr_pd_flags;
--
2.7.0
More information about the freebsd-hackers
mailing list