Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 1 Feb 2016 06:28:09 +0100
From:      Mateusz Guzik <mjguzik@gmail.com>
To:        freebsd-hackers@freebsd.org
Cc:        kib@freebsd.org
Subject:   Re: [PATCH 2/2] fork: plug a use after free of the returned process pointer
Message-ID:  <20160201052809.GA7127@dft-labs.eu>
In-Reply-To: <1454303584-20941-3-git-send-email-mjguzik@gmail.com>
References:  <1454303584-20941-1-git-send-email-mjguzik@gmail.com> <1454303584-20941-3-git-send-email-mjguzik@gmail.com>

next in thread | previous in thread | raw e-mail | index | archive | help
On Mon, Feb 01, 2016 at 06:13:04AM +0100, Mateusz Guzik wrote:

Ops, the diff excluded racct change (see the end).

> From: Mateusz Guzik <mjg@freebsd.org>
> 
> fork1 required its callers to pass a point 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.
> 
> Fix the problem by changing the API to let callers indicate whether they
> want the pid or the struct proc, return the process in stopped state for
> the latter case.
> 
> The process is held after its thread is marked as runnable to prevent it
> from exiting untill all work is done.
> ---
>  sys/compat/cloudabi/cloudabi_proc.c |   3 +-
>  sys/compat/linux/linux_fork.c       |   7 ++-
>  sys/kern/init_main.c                |   2 +-
>  sys/kern/kern_fork.c                | 108 ++++++++++++++++++++----------------
>  sys/kern/kern_kthread.c             |   2 +-
>  sys/sys/proc.h                      |   2 +-
>  6 files changed, 67 insertions(+), 57 deletions(-)
> 
> diff --git a/sys/compat/cloudabi/cloudabi_proc.c b/sys/compat/cloudabi/cloudabi_proc.c
> index e98471b..d8c3bef 100644
> --- a/sys/compat/cloudabi/cloudabi_proc.c
> +++ b/sys/compat/cloudabi/cloudabi_proc.c
> @@ -78,13 +78,12 @@ cloudabi_sys_proc_fork(struct thread *td,
>  {
>  	struct procdesc_req pdr;
>  	struct filecaps fcaps = {};
> -	struct proc *p2;
>  	int error;
>  
>  	pdr.pdr_flags = 0;
>  	pdr.pdr_fcaps = &fcaps;
>  	cap_rights_init(&fcaps.fc_rights, CAP_FSTAT, CAP_EVENT);
> -	error = fork1(td, RFFDG | RFPROC | RFPROCDESC, 0, &p2, &pdr);
> +	error = fork1(td, RFFDG | RFPROC | RFPROCDESC, 0, NULL, NULL, &pdr);
>  	if (error != 0)
>  		return (error);
>  	/* Return the file descriptor to the parent process. */
> diff --git a/sys/compat/linux/linux_fork.c b/sys/compat/linux/linux_fork.c
> index 7cbe216..0c0f800 100644
> --- a/sys/compat/linux/linux_fork.c
> +++ b/sys/compat/linux/linux_fork.c
> @@ -73,7 +73,8 @@ linux_fork(struct thread *td, struct linux_fork_args *args)
>  		printf(ARGS(fork, ""));
>  #endif
>  
> -	if ((error = fork1(td, RFFDG | RFPROC | RFSTOPPED, 0, &p2, NULL)) != 0)
> +	if ((error = fork1(td, RFFDG | RFPROC | RFSTOPPED, 0, &p2, NULL,
> +	    NULL)) != 0)
>  		return (error);
>  
>  	td2 = FIRST_THREAD_IN_PROC(p2);
> @@ -106,7 +107,7 @@ linux_vfork(struct thread *td, struct linux_vfork_args *args)
>  #endif
>  
>  	if ((error = fork1(td, RFFDG | RFPROC | RFMEM | RFPPWAIT | RFSTOPPED,
> -	    0, &p2, NULL)) != 0)
> +	    0, &p2, NULL, NULL)) != 0)
>  		return (error);
>  
>  	td2 = FIRST_THREAD_IN_PROC(p2);
> @@ -169,7 +170,7 @@ linux_clone_proc(struct thread *td, struct linux_clone_args *args)
>  	if (args->flags & LINUX_CLONE_VFORK)
>  		ff |= RFPPWAIT;
>  
> -	error = fork1(td, ff, 0, &p2, NULL);
> +	error = fork1(td, ff, 0, &p2, NULL, NULL);
>  	if (error)
>  		return (error);
>  
> diff --git a/sys/kern/init_main.c b/sys/kern/init_main.c
> index 7d0443a..d4fdcc0 100644
> --- a/sys/kern/init_main.c
> +++ b/sys/kern/init_main.c
> @@ -833,7 +833,7 @@ create_init(const void *udata __unused)
>  	int error;
>  
>  	error = fork1(&thread0, RFFDG | RFPROC | RFSTOPPED, 0, &initproc,
> -	    NULL);
> +	    NULL, NULL);
>  	if (error)
>  		panic("cannot fork init: %d\n", error);
>  	KASSERT(initproc->p_pid == 1, ("create_init: initproc->p_pid != 1"));
> diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c
> index 8cc56b7..d15f517 100644
> --- a/sys/kern/kern_fork.c
> +++ b/sys/kern/kern_fork.c
> @@ -101,12 +101,11 @@ struct fork_args {
>  int
>  sys_fork(struct thread *td, struct fork_args *uap)
>  {
> -	int error;
> -	struct proc *p2;
> +	int error, pid;
>  
> -	error = fork1(td, RFFDG | RFPROC, 0, &p2, NULL);
> +	error = fork1(td, RFFDG | RFPROC, 0, NULL, &pid, NULL);
>  	if (error == 0) {
> -		td->td_retval[0] = p2->p_pid;
> +		td->td_retval[0] = pid;
>  		td->td_retval[1] = 0;
>  	}
>  	return (error);
> @@ -119,8 +118,7 @@ sys_pdfork(td, uap)
>  	struct pdfork_args *uap;
>  {
>  	struct procdesc_req pdr;
> -	struct proc *p2;
> -	int error;
> +	int error, pid;
>  
>  	/*
>  	 * It is necessary to return fd by reference because 0 is a valid file
> @@ -129,9 +127,9 @@ sys_pdfork(td, uap)
>  	 */
>  	pdr.pdr_flags = uap->flags;
>  	pdr.pdr_fcaps = NULL;
> -	error = fork1(td, RFFDG | RFPROC | RFPROCDESC, 0, &p2, &pdr);
> +	error = fork1(td, RFFDG | RFPROC | RFPROCDESC, 0, NULL, &pid, &pdr);
>  	if (error == 0) {
> -		td->td_retval[0] = p2->p_pid;
> +		td->td_retval[0] = pid;
>  		td->td_retval[1] = 0;
>  		error = copyout(&pdr.pdr_fd, uap->fdp, sizeof(pdr.pdr_fd));
>  	}
> @@ -142,13 +140,12 @@ sys_pdfork(td, uap)
>  int
>  sys_vfork(struct thread *td, struct vfork_args *uap)
>  {
> -	int error, flags;
> -	struct proc *p2;
> +	int error, pid;
>  
> -	flags = RFFDG | RFPROC | RFPPWAIT | RFMEM;
> -	error = fork1(td, flags, 0, &p2, NULL);
> +	error = fork1(td, RFFDG | RFPROC | RFPPWAIT | RFMEM, 0, NULL, &pid,
> +	    NULL);
>  	if (error == 0) {
> -		td->td_retval[0] = p2->p_pid;
> +		td->td_retval[0] = pid;
>  		td->td_retval[1] = 0;
>  	}
>  	return (error);
> @@ -157,17 +154,16 @@ sys_vfork(struct thread *td, struct vfork_args *uap)
>  int
>  sys_rfork(struct thread *td, struct rfork_args *uap)
>  {
> -	struct proc *p2;
> -	int error;
> +	int error, pid;
>  
>  	/* Don't allow kernel-only flags. */
>  	if ((uap->flags & RFKERNELONLY) != 0)
>  		return (EINVAL);
>  
>  	AUDIT_ARG_FFLAGS(uap->flags);
> -	error = fork1(td, uap->flags, 0, &p2, NULL);
> +	error = fork1(td, uap->flags, 0, NULL, &pid, NULL);
>  	if (error == 0) {
> -		td->td_retval[0] = p2 ? p2->p_pid : 0;
> +		td->td_retval[0] = pid;
>  		td->td_retval[1] = 0;
>  	}
>  	return (error);
> @@ -369,10 +365,11 @@ fail:
>  
>  static void
>  do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
> -    struct vmspace *vm2, int pdflags)
> +    struct vmspace *vm2, struct proc **procp, int *procpid, int pdflags,
> +    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;
> @@ -380,7 +377,6 @@ do_fork(struct thread *td, int flags, struct proc *p2, struct thread *td2,
>  	sx_assert(&proctree_lock, SX_SLOCKED);
>  	sx_assert(&allproc_lock, SX_XLOCKED);
>  
> -	p2_held = 0;
>  	p1 = td->td_proc;
>  
>  	trypid = fork_findpid(flags);
> @@ -708,6 +704,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)) {
>  		/*
> @@ -720,25 +721,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.
>  	 */
> @@ -751,20 +739,43 @@ 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 (procpid != NULL)
> +		*procpid = p2->p_pid;
> +	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);
> +	} else {
> +		/*
> +		 * Return child proc pointer to parent.
> +		 */
> +		*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);
>  }
>  
>  int
>  fork1(struct thread *td, int flags, int pages, struct proc **procp,
> -    struct procdesc_req *pdr)
> +    int *procpid, struct procdesc_req *pdr)
>  {
>  	struct proc *p1, *newproc;
>  	struct thread *td2;
> @@ -775,6 +786,11 @@ fork1(struct thread *td, int flags, int pages, struct proc **procp,
>  	static int curfail;
>  	static struct timeval lastfail;
>  
> +	if ((flags & RFSTOPPED) != 0)
> +		MPASS(procp != NULL && procpid == NULL);
> +	else
> +		MPASS(procp == NULL);
> +
>  	/* Check for the undefined or unimplemented flags. */
>  	if ((flags & ~(RFFLAGS | RFTSIGFLAGS(RFTSIGMASK))) != 0)
>  		return (EINVAL);
> @@ -810,7 +826,10 @@ fork1(struct thread *td, int flags, int pages, struct proc **procp,
>  	 * certain parts of a process from itself.
>  	 */
>  	if ((flags & RFPROC) == 0) {
> -		*procp = NULL;
> +		if (procp != NULL)
> +			*procp = NULL;
> +		if (procpid != NULL)
> +			*procpid = 0;
>  		return (fork_norfproc(td, flags));
>  	}
>  
> @@ -938,17 +957,8 @@ fork1(struct thread *td, int flags, int pages, struct proc **procp,
>  		    lim_cur(td, RLIMIT_NPROC));
>  	}
>  	if (ok) {
> -		do_fork(td, flags, newproc, td2, vm2, pdflags);
> -
> -		/*
> -		 * Return child proc pointer to parent.
> -		 */
> -		*procp = newproc;
> -		if (flags & RFPROCDESC) {
> -			procdesc_finit(newproc->p_procdesc, fp_procdesc);
> -			fdrop(fp_procdesc, td);
> -		}
> -		racct_proc_fork_done(newproc);
> +		do_fork(td, flags, newproc, td2, vm2, procp, procpid, pdflags,
> +		    fp_procdesc);
>  		return (0);
>  	}
>  
> diff --git a/sys/kern/kern_kthread.c b/sys/kern/kern_kthread.c
> index 0673f68..67f6cc2 100644
> --- a/sys/kern/kern_kthread.c
> +++ b/sys/kern/kern_kthread.c
> @@ -89,7 +89,7 @@ kproc_create(void (*func)(void *), void *arg,
>  		panic("kproc_create called too soon");
>  
>  	error = fork1(&thread0, RFMEM | RFFDG | RFPROC | RFSTOPPED | flags,
> -	    pages, &p2, NULL);
> +	    pages, &p2, NULL, NULL);
>  	if (error)
>  		return error;
>  
> diff --git a/sys/sys/proc.h b/sys/sys/proc.h
> index 60efdcd..b072e10 100644
> --- a/sys/sys/proc.h
> +++ b/sys/sys/proc.h
> @@ -931,7 +931,7 @@ int	enterpgrp(struct proc *p, pid_t pgid, struct pgrp *pgrp,
>  int	enterthispgrp(struct proc *p, struct pgrp *pgrp);
>  void	faultin(struct proc *p);
>  void	fixjobc(struct proc *p, struct pgrp *pgrp, int entering);
> -int	fork1(struct thread *, int, int, struct proc **,
> +int	fork1(struct thread *, int, int, struct proc **, int *,
>  	    struct procdesc_req *);
>  void	fork_exit(void (*)(void *, struct trapframe *), void *,
>  	    struct trapframe *);
> -- 
> 2.7.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
 }

-- 
Mateusz Guzik <mjguzik gmail.com>



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20160201052809.GA7127>