Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 7 Jul 2015 00:42:01 +0200
From:      Mateusz Guzik <mjguzik@gmail.com>
To:        Konstantin Belousov <kostikbel@gmail.com>
Cc:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   Re: svn commit: r267760 - head/sys/kern
Message-ID:  <20150706224201.GC28898@dft-labs.eu>
In-Reply-To: <20150706090958.GM2080@kib.kiev.ua>
References:  <20140623163523.GK93733@kib.kiev.ua> <20140711024351.GA18214@dft-labs.eu> <20140711095551.GA93733@kib.kiev.ua> <20140711111925.GB18214@dft-labs.eu> <20140713132652.GZ93733@kib.kiev.ua> <20140713213623.GA13241@dft-labs.eu> <20140717005638.GF93733@kib.kiev.ua> <20150706055135.GA28898@dft-labs.eu> <20150706064601.GB28898@dft-labs.eu> <20150706090958.GM2080@kib.kiev.ua>

next in thread | previous in thread | raw e-mail | index | archive | help
On Mon, Jul 06, 2015 at 12:09:58PM +0300, Konstantin Belousov wrote:
> On Mon, Jul 06, 2015 at 08:46:02AM +0200, Mateusz Guzik wrote:
> > diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c
> > index fe8e9ef..c7f579a 100644
> > --- a/sys/fs/devfs/devfs_vnops.c
> > +++ b/sys/fs/devfs/devfs_vnops.c
> > @@ -574,6 +574,8 @@ devfs_close(struct vop_close_args *ap)
> >  	if (vp->v_data == NULL)
> >  		return (0);
> >  
> > +	vp_locked = VOP_ISLOCKED(vp);
> > +
> >  	/*
> >  	 * Hack: a tty device that is a controlling terminal
> >  	 * has a reference from the session structure.
> > @@ -589,6 +591,7 @@ devfs_close(struct vop_close_args *ap)
> >  		if (vp == p->p_session->s_ttyvp) {
> >  			PROC_UNLOCK(p);
> >  			oldvp = NULL;
> > +			VOP_UNLOCK(vp, 0);
> This opens a window where vp can be reclaimed. Then vp->v_rdev
> is invalid and dev_refthread() below accesses random memory.
> 
> Might be, the easiest fix is to call dev_refthread() before the td !=
> NULL block, but leave dsw == NULL check where it is now.
> 

Instead I made the bare minimum to ensure that modified fields which are
accessed here are always protected with proc lock and sess lock, which
eliminates the need for proctree lock in problematic cases. In effect
there are no games with relocking vnodes.

I would commit this patch separately.

Note this establishes a process lock -> vnode interlock order which
seems fishy, but apparently does not result in lors.

diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c
index fe8e9ef..0a2a6bb 100644
--- a/sys/fs/devfs/devfs_vnops.c
+++ b/sys/fs/devfs/devfs_vnops.c
@@ -586,12 +586,12 @@ devfs_close(struct vop_close_args *ap)
 	if (td != NULL) {
 		p = td->td_proc;
 		PROC_LOCK(p);
-		if (vp == p->p_session->s_ttyvp) {
+		if (vp != p->p_session->s_ttyvp) {
 			PROC_UNLOCK(p);
+		} else {
 			oldvp = NULL;
-			sx_xlock(&proctree_lock);
+			SESS_LOCK(p->p_session);
 			if (vp == p->p_session->s_ttyvp) {
-				SESS_LOCK(p->p_session);
 				VI_LOCK(vp);
 				if (count_dev(dev) == 2 &&
 				    (vp->v_iflag & VI_DOOMED) == 0) {
@@ -600,13 +600,12 @@ devfs_close(struct vop_close_args *ap)
 					oldvp = vp;
 				}
 				VI_UNLOCK(vp);
-				SESS_UNLOCK(p->p_session);
 			}
-			sx_xunlock(&proctree_lock);
+			SESS_UNLOCK(p->p_session);
+			PROC_UNLOCK(p);
 			if (oldvp != NULL)
 				vrele(oldvp);
-		} else
-			PROC_UNLOCK(p);
+		}
 	}
 	/*
 	 * We do not want to really close the device if it
diff --git a/sys/kern/tty.c b/sys/kern/tty.c
index d9d0cce..5ef5578 100644
--- a/sys/kern/tty.c
+++ b/sys/kern/tty.c
@@ -1637,8 +1637,13 @@ tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag,
 			return (EPERM);
 		}
 
+		PROC_LOCK(p);
+		SESS_LOCK(p->p_session);
+
 		if (tp->t_session != NULL && tp->t_session == p->p_session) {
 			/* This is already our controlling TTY. */
+			SESS_UNLOCK(p->p_session);
+			PROC_UNLOCK(p);
 			sx_xunlock(&proctree_lock);
 			return (0);
 		}
@@ -1657,6 +1662,8 @@ tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag,
 			 * TTYs of which the session leader has been
 			 * killed or the TTY revoked.
 			 */
+			SESS_UNLOCK(p->p_session);
+			PROC_UNLOCK(p);
 			sx_xunlock(&proctree_lock);
 			return (EPERM);
 		}
@@ -1665,14 +1672,13 @@ tty_generic_ioctl(struct tty *tp, u_long cmd, void *data, int fflag,
 		tp->t_session = p->p_session;
 		tp->t_session->s_ttyp = tp;
 		tp->t_sessioncnt++;
-		sx_xunlock(&proctree_lock);
 
 		/* Assign foreground process group. */
 		tp->t_pgrp = p->p_pgrp;
-		PROC_LOCK(p);
 		p->p_flag |= P_CONTROLT;
+		SESS_UNLOCK(p->p_session);
 		PROC_UNLOCK(p);
-
+		sx_xunlock(&proctree_lock);
 		return (0);
 	}
 	case TIOCSPGRP: {
diff --git a/sys/kern/tty_tty.c b/sys/kern/tty_tty.c
index 582b41a..dca619c 100644
--- a/sys/kern/tty_tty.c
+++ b/sys/kern/tty_tty.c
@@ -65,9 +65,8 @@ ctty_clone(void *arg, struct ucred *cred, char *name, int namelen,
 	if (strcmp(name, "tty"))
 		return;
 	p = curproc;
-	sx_sunlock(&clone_drain_lock);
-	sx_slock(&proctree_lock);
-	sx_slock(&clone_drain_lock);
+	PROC_LOCK(p);
+	SESS_LOCK(p->p_session);
 	dev_lock();
 	if (!(p->p_flag & P_CONTROLT))
 		*dev = ctty;
@@ -81,7 +80,8 @@ ctty_clone(void *arg, struct ucred *cred, char *name, int namelen,
 		*dev = p->p_session->s_ttyvp->v_rdev;
 	dev_refl(*dev);
 	dev_unlock();
-	sx_sunlock(&proctree_lock);
+	SESS_UNLOCK(p->p_session);
+	PROC_UNLOCK(p);
 }
 
 static void
> > diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c
> > index 85cda01..bbdcbf5 100644
> > --- a/sys/kern/kern_descrip.c
> > +++ b/sys/kern/kern_descrip.c
> > @@ -3271,6 +3271,7 @@ kern_proc_filedesc_out(struct proc *p,  struct sbuf *sb, ssize_t maxlen)
> >  	FILEDESC_SUNLOCK(fdp);
> >  	fddrop(fdp);
> >  fail:
> > +	sx_sunlock(&p->p_imagelock);
> IMO it would be less confusing to unlock the p_imagelock at the caller'
> location. At the kern_proc_filedesc_out(), the p_imagelock slocked state
> must be asserted.
> 

Moved.

> Same for ofiledesc().
> 

This is the top level func which also has the relevant pget call.

> > diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c
> > index 9ce6d34..27fd764 100644
> > --- a/sys/kern/kern_exit.c
> > +++ b/sys/kern/kern_exit.c
> > @@ -213,6 +213,7 @@ exit1(struct thread *td, int rv)
> >  	/*
> >  	 * MUST abort all other threads before proceeding past here.
> >  	 */
> > +	sx_xlock(&p->p_imagelock);
> >  	PROC_LOCK(p);
> >  	/*
> >  	 * First check if some other thread or external request got
> > @@ -279,6 +280,7 @@ exit1(struct thread *td, int rv)
> >  	 * decided to wait again after we told them we are exiting.
> >  	 */
> >  	p->p_flag |= P_WEXIT;
> > +	sx_xunlock(&p->p_imagelock);
> I do not understand why p_imagelock is released so early in the exit1().
> 

Since the process catched with p_imagelock must not be exiting, a
lock/unlock sequence paired with setting P_WEXIT gives us a guarantee
that nothing will start inspeting it.

Past P_WEXIT there are next to no guarantees for stability of most
fields of the proc anyway, and soon after proctree lock is taken and we
have to drop imagelock by that time to avoid a lor.

> >  	wakeup(&p->p_stype);
> >  
> >  	/*
> > diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c
> > index 27c6f40..ac6b441 100644
> > --- a/sys/kern/kern_proc.c
> > +++ b/sys/kern/kern_proc.c
> > @@ -230,6 +230,7 @@ proc_init(void *mem, int size, int flags)
> >  	mtx_init(&p->p_statmtx, "pstatl", NULL, MTX_SPIN | MTX_NEW);
> >  	mtx_init(&p->p_itimmtx, "pitiml", NULL, MTX_SPIN | MTX_NEW);
> >  	mtx_init(&p->p_profmtx, "pprofl", NULL, MTX_SPIN | MTX_NEW);
> > +	sx_init_flags(&p->p_imagelock, "process imagelock", SX_NEW);
> >  	cv_init(&p->p_pwait, "ppwait");
> >  	cv_init(&p->p_dbgwait, "dbgwait");
> >  	TAILQ_INIT(&p->p_threads);	     /* all threads in proc */
> > @@ -278,16 +279,20 @@ inferior(struct proc *p)
> >  }
> >  
> >  struct proc *
> > -pfind_locked(pid_t pid)
> > +pfind_locked(pid_t pid, int lockimage)
> >  {
> >  	struct proc *p;
> >  
> >  	sx_assert(&allproc_lock, SX_LOCKED);
> >  	LIST_FOREACH(p, PIDHASH(pid), p_hash) {
> >  		if (p->p_pid == pid) {
> > +			if (lockimage)
> > +				sx_slock(&p->p_imagelock);
> Probably makes sense to explicitely add imagelock name into the
> witness' order_lists[] array.
> 

Added after allprison.

As a cosmetic matter I'm pondering a pair like pimage_lock/unlock or
similar to wrap relevant pget and unlock of p_imagelock.

Note that in this patch I moved exit's imagelock to after
singlethreading is in effect.

diff --git a/sys/fs/nfsclient/nfs_clport.c b/sys/fs/nfsclient/nfs_clport.c
index d3bac30..118a7d9 100644
--- a/sys/fs/nfsclient/nfs_clport.c
+++ b/sys/fs/nfsclient/nfs_clport.c
@@ -1188,7 +1188,7 @@ nfscl_procdoesntexist(u_int8_t *own)
 	tl.cval[2] = *own++;
 	tl.cval[3] = *own++;
 	pid = tl.lval;
-	p = pfind_locked(pid);
+	p = pfind_locked(pid, 0);
 	if (p == NULL)
 		return (1);
 	if (p->p_stats == NULL) {
diff --git a/sys/kern/imgact_elf.c b/sys/kern/imgact_elf.c
index 0675128..2ad09aa 100644
--- a/sys/kern/imgact_elf.c
+++ b/sys/kern/imgact_elf.c
@@ -1887,6 +1887,7 @@ note_procstat_files(void *arg, struct sbuf *sb, size_t *sizep)
 		sb = sbuf_new(NULL, NULL, 128, SBUF_FIXEDLEN);
 		sbuf_set_drain(sb, sbuf_drain_count, &size);
 		sbuf_bcat(sb, &structsize, sizeof(structsize));
+		sx_slock(&p->p_imagelock);
 		PROC_LOCK(p);
 		kern_proc_filedesc_out(p, sb, -1);
 		sbuf_finish(sb);
@@ -1895,6 +1896,7 @@ note_procstat_files(void *arg, struct sbuf *sb, size_t *sizep)
 	} else {
 		structsize = sizeof(struct kinfo_file);
 		sbuf_bcat(sb, &structsize, sizeof(structsize));
+		sx_slock(&p->p_imagelock);
 		PROC_LOCK(p);
 		kern_proc_filedesc_out(p, sb, -1);
 	}
diff --git a/sys/kern/init_main.c b/sys/kern/init_main.c
index 37539c4..9364dcb 100644
--- a/sys/kern/init_main.c
+++ b/sys/kern/init_main.c
@@ -480,6 +480,7 @@ proc0_init(void *dummy __unused)
 	p->p_flag2 = 0;
 	p->p_state = PRS_NORMAL;
 	knlist_init_mtx(&p->p_klist, &p->p_mtx);
+	sx_init_flags(&p->p_imagelock, "process imagelock", SX_NEW);
 	STAILQ_INIT(&p->p_ktr);
 	p->p_nice = NZERO;
 	/* pid_max cannot be greater than PID_MAX */
diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c
index 85cda01..f0f4665 100644
--- a/sys/kern/kern_descrip.c
+++ b/sys/kern/kern_descrip.c
@@ -3292,13 +3292,14 @@ sysctl_kern_proc_filedesc(SYSCTL_HANDLER_ARGS)
 
 	sbuf_new_for_sysctl(&sb, NULL, FILEDESC_SBUF_SIZE, req);
 	sbuf_clear_flags(&sb, SBUF_INCLUDENUL);
-	error = pget((pid_t)name[0], PGET_CANDEBUG | PGET_NOTWEXIT, &p);
+	error = pget((pid_t)name[0], PGET_LOCK, &p);
 	if (error != 0) {
 		sbuf_delete(&sb);
 		return (error);
 	}
 	maxlen = req->oldptr != NULL ? req->oldlen : -1;
 	error = kern_proc_filedesc_out(p, &sb, maxlen);
+	sx_sunlock(&p->p_imagelock);
 	error2 = sbuf_finish(&sb);
 	sbuf_delete(&sb);
 	return (error != 0 ? error : error2);
@@ -3359,7 +3360,7 @@ sysctl_kern_proc_ofiledesc(SYSCTL_HANDLER_ARGS)
 	struct proc *p;
 
 	name = (int *)arg1;
-	error = pget((pid_t)name[0], PGET_CANDEBUG | PGET_NOTWEXIT, &p);
+	error = pget((pid_t)name[0], PGET_LOCK, &p);
 	if (error != 0)
 		return (error);
 	fdp = fdhold(p);
@@ -3391,6 +3392,7 @@ sysctl_kern_proc_ofiledesc(SYSCTL_HANDLER_ARGS)
 	}
 	FILEDESC_SUNLOCK(fdp);
 	fddrop(fdp);
+	sx_sunlock(&p->p_imagelock);
 	free(kif, M_TEMP);
 	free(okif, M_TEMP);
 	return (0);
diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c
index 859b2e3..0d9aac1 100644
--- a/sys/kern/kern_exec.c
+++ b/sys/kern/kern_exec.c
@@ -381,6 +381,7 @@ do_execve(td, args, mac_p)
 	 * that might allow a local user to illicitly obtain elevated
 	 * privileges.
 	 */
+	sx_xlock(&p->p_imagelock);
 	PROC_LOCK(p);
 	KASSERT((p->p_flag & P_INEXEC) == 0,
 	    ("%s(): process already has P_INEXEC flag", __func__));
@@ -907,6 +908,7 @@ exec_fail:
 	SDT_PROBE(proc, kernel, , exec__failure, error, 0, 0, 0, 0);
 
 done2:
+	sx_xunlock(&p->p_imagelock);
 #ifdef MAC
 	mac_execve_exit(imgp);
 	mac_execve_interpreter_exit(interpvplabel);
diff --git a/sys/kern/kern_exit.c b/sys/kern/kern_exit.c
index 9ce6d34..127629d 100644
--- a/sys/kern/kern_exit.c
+++ b/sys/kern/kern_exit.c
@@ -290,6 +290,15 @@ exit1(struct thread *td, int rv)
 
 	p->p_xstat = rv;	/* Let event handler change exit status */
 	PROC_UNLOCK(p);
+
+	/*
+	 * The lock/unlock pair gives us a guarantee nobody pins the process
+	 * with p_imagelock. While the process is still reachable, consumers
+	 * are supposed to check for P_WEXIT flag set earlier.
+	 */
+	sx_xlock(&p->p_imagelock);
+	sx_xunlock(&p->p_imagelock);
+
 	/* Drain the limit callout while we don't have the proc locked */
 	callout_drain(&p->p_limco);
 
diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c
index 27c6f40..ac6b441 100644
--- a/sys/kern/kern_proc.c
+++ b/sys/kern/kern_proc.c
@@ -230,6 +230,7 @@ proc_init(void *mem, int size, int flags)
 	mtx_init(&p->p_statmtx, "pstatl", NULL, MTX_SPIN | MTX_NEW);
 	mtx_init(&p->p_itimmtx, "pitiml", NULL, MTX_SPIN | MTX_NEW);
 	mtx_init(&p->p_profmtx, "pprofl", NULL, MTX_SPIN | MTX_NEW);
+	sx_init_flags(&p->p_imagelock, "process imagelock", SX_NEW);
 	cv_init(&p->p_pwait, "ppwait");
 	cv_init(&p->p_dbgwait, "dbgwait");
 	TAILQ_INIT(&p->p_threads);	     /* all threads in proc */
@@ -278,16 +279,20 @@ inferior(struct proc *p)
 }
 
 struct proc *
-pfind_locked(pid_t pid)
+pfind_locked(pid_t pid, int lockimage)
 {
 	struct proc *p;
 
 	sx_assert(&allproc_lock, SX_LOCKED);
 	LIST_FOREACH(p, PIDHASH(pid), p_hash) {
 		if (p->p_pid == pid) {
+			if (lockimage)
+				sx_slock(&p->p_imagelock);
 			PROC_LOCK(p);
 			if (p->p_state == PRS_NEW) {
 				PROC_UNLOCK(p);
+				if (lockimage)
+					sx_sunlock(&p->p_imagelock);
 				p = NULL;
 			}
 			break;
@@ -308,7 +313,7 @@ pfind(pid_t pid)
 	struct proc *p;
 
 	sx_slock(&allproc_lock);
-	p = pfind_locked(pid);
+	p = pfind_locked(pid, 0);
 	sx_sunlock(&allproc_lock);
 	return (p);
 }
@@ -364,11 +369,17 @@ int
 pget(pid_t pid, int flags, struct proc **pp)
 {
 	struct proc *p;
-	int error;
+	int error, lockimage;
+
+	lockimage = ((flags & PGET_IMAGELOCK) != 0);
+	if (lockimage) {
+		MPASS((flags & PGET_NOTWEXIT) != 0);
+		MPASS((flags & PGET_HOLD) == 0);
+	}
 
 	sx_slock(&allproc_lock);
 	if (pid <= PID_MAX) {
-		p = pfind_locked(pid);
+		p = pfind_locked(pid, lockimage);
 		if (p == NULL && (flags & PGET_NOTWEXIT) == 0)
 			p = zpfind_locked(pid);
 	} else if ((flags & PGET_NOTID) == 0) {
@@ -413,6 +424,8 @@ pget(pid_t pid, int flags, struct proc **pp)
 	return (0);
 errout:
 	PROC_UNLOCK(p);
+	if (lockimage)
+		sx_sunlock(&p->p_imagelock);
 	return (error);
 }
 
diff --git a/sys/kern/subr_witness.c b/sys/kern/subr_witness.c
index 1280807..a423c80 100644
--- a/sys/kern/subr_witness.c
+++ b/sys/kern/subr_witness.c
@@ -475,6 +475,7 @@ static struct witness_order_list_entry order_lists[] = {
 	{ "proctree", &lock_class_sx },
 	{ "allproc", &lock_class_sx },
 	{ "allprison", &lock_class_sx },
+	{ "process imagelock", &lock_class_sx },
 	{ NULL, NULL },
 	/*
 	 * Various mutexes
diff --git a/sys/sys/proc.h b/sys/sys/proc.h
index e6c83b4..5df817a 100644
--- a/sys/sys/proc.h
+++ b/sys/sys/proc.h
@@ -522,6 +522,7 @@ struct proc {
 					       (if I am reaper). */
 	LIST_ENTRY(proc) p_reapsibling;	/* (e) List of siblings - descendants of
 					       the same reaper. */
+	struct sx	p_imagelock;	/* Lock blocking exec/exit. */
 	struct mtx	p_mtx;		/* (n) Lock for this struct. */
 	struct mtx	p_statmtx;	/* Lock for the stats */
 	struct mtx	p_itimmtx;	/* Lock for the virt/prof timers */
@@ -886,7 +887,7 @@ extern struct proc *initproc, *pageproc; /* Process slots for init, pager. */
 extern struct uma_zone *proc_zone;
 
 struct	proc *pfind(pid_t);		/* Find process by id. */
-struct	proc *pfind_locked(pid_t pid);
+struct	proc *pfind_locked(pid_t pid, int lockimage);
 struct	pgrp *pgfind(pid_t);		/* Find process group by id. */
 struct	proc *zpfind(pid_t);		/* Find zombie process by id. */
 
@@ -900,8 +901,10 @@ struct	proc *zpfind(pid_t);		/* Find zombie process by id. */
 #define	PGET_NOTWEXIT	0x00010	/* Check that the process is not in P_WEXIT. */
 #define	PGET_NOTINEXEC	0x00020	/* Check that the process is not in P_INEXEC. */
 #define	PGET_NOTID	0x00040	/* Do not assume tid if pid > PID_MAX. */
+#define	PGET_IMAGELOCK	0x00080 /* Prevent execs and exits. */
 
 #define	PGET_WANTREAD	(PGET_HOLD | PGET_CANDEBUG | PGET_NOTWEXIT)
+#define	PGET_LOCK	(PGET_CANDEBUG | PGET_NOTWEXIT | PGET_IMAGELOCK)
 
 int	pget(pid_t pid, int flags, struct proc **pp);
 
-- 
Mateusz Guzik <mjguzik gmail.com>



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