diff options
Diffstat (limited to 'kernel/fork.c')
| -rw-r--r-- | kernel/fork.c | 87 | 
1 files changed, 64 insertions, 23 deletions
| diff --git a/kernel/fork.c b/kernel/fork.c index 051f090d40c..26a7a6707fa 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -66,6 +66,7 @@  #include <linux/user-return-notifier.h>  #include <linux/oom.h>  #include <linux/khugepaged.h> +#include <linux/signalfd.h>  #include <asm/pgtable.h>  #include <asm/pgalloc.h> @@ -647,6 +648,58 @@ struct mm_struct *get_task_mm(struct task_struct *task)  }  EXPORT_SYMBOL_GPL(get_task_mm); +struct mm_struct *mm_access(struct task_struct *task, unsigned int mode) +{ +	struct mm_struct *mm; +	int err; + +	err =  mutex_lock_killable(&task->signal->cred_guard_mutex); +	if (err) +		return ERR_PTR(err); + +	mm = get_task_mm(task); +	if (mm && mm != current->mm && +			!ptrace_may_access(task, mode)) { +		mmput(mm); +		mm = ERR_PTR(-EACCES); +	} +	mutex_unlock(&task->signal->cred_guard_mutex); + +	return mm; +} + +static void complete_vfork_done(struct task_struct *tsk) +{ +	struct completion *vfork; + +	task_lock(tsk); +	vfork = tsk->vfork_done; +	if (likely(vfork)) { +		tsk->vfork_done = NULL; +		complete(vfork); +	} +	task_unlock(tsk); +} + +static int wait_for_vfork_done(struct task_struct *child, +				struct completion *vfork) +{ +	int killed; + +	freezer_do_not_count(); +	killed = wait_for_completion_killable(vfork); +	freezer_count(); + +	if (killed) { +		task_lock(child); +		child->vfork_done = NULL; +		task_unlock(child); +	} + +	put_task_struct(child); +	return killed; +} +  /* Please note the differences between mmput and mm_release.   * mmput is called whenever we stop holding onto a mm_struct,   * error success whatever. @@ -662,8 +715,6 @@ EXPORT_SYMBOL_GPL(get_task_mm);   */  void mm_release(struct task_struct *tsk, struct mm_struct *mm)  { -	struct completion *vfork_done = tsk->vfork_done; -  	/* Get rid of any futexes when releasing the mm */  #ifdef CONFIG_FUTEX  	if (unlikely(tsk->robust_list)) { @@ -683,17 +734,15 @@ void mm_release(struct task_struct *tsk, struct mm_struct *mm)  	/* Get rid of any cached register state */  	deactivate_mm(tsk, mm); -	/* notify parent sleeping on vfork() */ -	if (vfork_done) { -		tsk->vfork_done = NULL; -		complete(vfork_done); -	} +	if (tsk->vfork_done) +		complete_vfork_done(tsk);  	/*  	 * If we're exiting normally, clear a user-space tid field if  	 * requested.  We leave this alone when dying by signal, to leave  	 * the value intact in a core dump, and to save the unnecessary -	 * trouble otherwise.  Userland only wants this done for a sys_exit. +	 * trouble, say, a killed vfork parent shouldn't touch this mm. +	 * Userland only wants this done for a sys_exit.  	 */  	if (tsk->clear_child_tid) {  		if (!(tsk->flags & PF_SIGNALED) && @@ -890,7 +939,7 @@ static int copy_io(unsigned long clone_flags, struct task_struct *tsk)  			return -ENOMEM;  		new_ioc->ioprio = ioc->ioprio; -		put_io_context(new_ioc, NULL); +		put_io_context(new_ioc);  	}  #endif  	return 0; @@ -915,8 +964,10 @@ static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)  void __cleanup_sighand(struct sighand_struct *sighand)  { -	if (atomic_dec_and_test(&sighand->count)) +	if (atomic_dec_and_test(&sighand->count)) { +		signalfd_cleanup(sighand);  		kmem_cache_free(sighand_cachep, sighand); +	}  } @@ -995,7 +1046,6 @@ static void copy_flags(unsigned long clone_flags, struct task_struct *p)  	new_flags &= ~(PF_SUPERPRIV | PF_WQ_WORKER);  	new_flags |= PF_FORKNOEXEC; -	new_flags |= PF_STARTING;  	p->flags = new_flags;  } @@ -1525,16 +1575,9 @@ long do_fork(unsigned long clone_flags,  		if (clone_flags & CLONE_VFORK) {  			p->vfork_done = &vfork;  			init_completion(&vfork); +			get_task_struct(p);  		} -		/* -		 * We set PF_STARTING at creation in case tracing wants to -		 * use this to distinguish a fully live task from one that -		 * hasn't finished SIGSTOP raising yet.  Now we clear it -		 * and set the child going. -		 */ -		p->flags &= ~PF_STARTING; -  		wake_up_new_task(p);  		/* forking complete and child started to run, tell ptracer */ @@ -1542,10 +1585,8 @@ long do_fork(unsigned long clone_flags,  			ptrace_event(trace, nr);  		if (clone_flags & CLONE_VFORK) { -			freezer_do_not_count(); -			wait_for_completion(&vfork); -			freezer_count(); -			ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); +			if (!wait_for_vfork_done(p, &vfork)) +				ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);  		}  	} else {  		nr = PTR_ERR(p); | 
