Synopsis: Procfs can give any users a rootshell
NetBSD versions: NetBSD 1.4.1 and before, NetBSD-current to 20000126
Thanks to: Jason Thorpe, Charles Hannum
Reported in NetBSD Security Advisory: SA2000-001

*** sys/kern/kern_subr.c.orig	Wed Mar 24 06:51:25 1999
--- sys/kern/kern_subr.c	Fri Jan 28 22:48:05 2000
***************
*** 392,397 ****
--- 392,463 ----
  }
  
  /*
+  * Exec hook code.
+  */
+ 
+ struct exechook_desc {
+ 	LIST_ENTRY(exechook_desc) ehk_list;
+ 	void	(*ehk_fn) __P((struct proc *, void *));
+ 	void	*ehk_arg;
+ };
+ 
+ LIST_HEAD(, exechook_desc) exechook_list;
+ 
+ void *
+ exechook_establish(fn, arg)
+ 	void (*fn) __P((struct proc *, void *));
+ 	void *arg;
+ {
+ 	struct exechook_desc *edp;
+ 
+ 	edp = (struct exechook_desc *)
+ 	    malloc(sizeof(*edp), M_DEVBUF, M_NOWAIT);
+ 	if (edp == NULL)
+ 		return NULL;
+ 
+ 	edp->ehk_fn = fn;
+ 	edp->ehk_arg = arg;
+ 	LIST_INSERT_HEAD(&exechook_list, edp, ehk_list);
+ 
+ 	return (edp);
+ }
+ 
+ void
+ exechook_disestablish(vhook)
+ 	void *vhook;
+ {
+ #ifdef DIAGNOSTIC
+ 	struct exechook_desc *edp;
+ 
+ 	for (edp = exechook_list.lh_first; edp != NULL;
+ 	    edp = edp->ehk_list.le_next)
+                 if (edp == vhook)
+ 			break;
+ 	if (edp == NULL)
+ 		panic("exechook_disestablish: hook not established");
+ #endif
+ 
+ 	LIST_REMOVE((struct exechook_desc *)vhook, ehk_list);
+ 	free(vhook, M_DEVBUF);
+ }
+ 
+ /*
+  * Run exec hooks.
+  */
+ void
+ doexechooks(p)
+ 	struct proc *p;
+ {
+ 	struct exechook_desc *edp;
+ 
+ 	for (edp = LIST_FIRST(&exechook_list); 
+ 	     edp != NULL; 
+ 	     edp = LIST_NEXT(edp, ehk_list)) {
+ 		(*edp->ehk_fn)(p, edp->ehk_arg);
+ 	}
+ }
+ 
+ /*
   * Determine the root device and, if instructed to, the root file system.
   */
  
*** sys/kern/kern_exec.c.orig	Fri Jan 28 21:50:37 2000
--- sys/kern/kern_exec.c	Fri Jan 28 22:48:05 2000
***************
*** 467,472 ****
--- 467,474 ----
  	p->p_cred->p_svuid = p->p_ucred->cr_uid;
  	p->p_cred->p_svgid = p->p_ucred->cr_gid;
  
+ 	doexechooks(p);
+ 
  	uvm_km_free_wakeup(exec_map, (vaddr_t) argp, NCARGS);
  
  	FREE(nid.ni_cnd.cn_pnbuf, M_NAMEI);
*** sys/miscfs/procfs/procfs_vfsops.c.orig	Sat Feb 27 00:44:46 1999
--- sys/miscfs/procfs/procfs_vfsops.c	Fri Jan 28 22:48:05 2000
***************
*** 57,62 ****
--- 57,63 ----
  #include <sys/mount.h>
  #include <sys/signalvar.h>
  #include <sys/vnode.h>
+ #include <sys/malloc.h>
  #include <miscfs/procfs/procfs.h>
  #include <vm/vm.h>			/* for PAGE_SIZE */
  
***************
*** 91,96 ****
--- 92,98 ----
  	struct proc *p;
  {
  	size_t size;
+ 	struct procfsmount *pmnt;
  
  	if (UIO_MX & (UIO_MX-1)) {
  		log(LOG_ERR, "procfs: invalid directory entry size");
***************
*** 101,113 ****
  		return (EOPNOTSUPP);
  
  	mp->mnt_flag |= MNT_LOCAL;
! 	mp->mnt_data = 0;
  	vfs_getnewfsid(mp, MOUNT_PROCFS);
  
  	(void) copyinstr(path, mp->mnt_stat.f_mntonname, MNAMELEN, &size);
  	memset(mp->mnt_stat.f_mntonname + size, 0, MNAMELEN - size);
  	memset(mp->mnt_stat.f_mntfromname, 0, MNAMELEN);
  	memcpy(mp->mnt_stat.f_mntfromname, "procfs", sizeof("procfs"));
  	return (0);
  }
  
--- 103,122 ----
  		return (EOPNOTSUPP);
  
  	mp->mnt_flag |= MNT_LOCAL;
! 	pmnt = (struct procfsmount *) malloc(sizeof(struct procfsmount),
! 	    M_UFSMNT, M_WAITOK);   /* XXX need new malloc type */
! 
! 	mp->mnt_data = (qaddr_t)pmnt;
  	vfs_getnewfsid(mp, MOUNT_PROCFS);
  
  	(void) copyinstr(path, mp->mnt_stat.f_mntonname, MNAMELEN, &size);
  	memset(mp->mnt_stat.f_mntonname + size, 0, MNAMELEN - size);
  	memset(mp->mnt_stat.f_mntfromname, 0, MNAMELEN);
  	memcpy(mp->mnt_stat.f_mntfromname, "procfs", sizeof("procfs"));
+ 
+ 	pmnt->pmnt_exechook = exechook_establish(procfs_revoke_vnodes, mp);
+ 	pmnt->pmnt_mp = mp;
+ 
  	return (0);
  }
  
***************
*** 129,134 ****
--- 138,148 ----
  	if ((error = vflush(mp, 0, flags)) != 0)
  		return (error);
  
+ 	exechook_disestablish(VFSTOPROC(mp)->pmnt_exechook);
+ 
+ 	free(mp->mnt_data, M_UFSMNT);
+ 	mp->mnt_data = 0;
+ 
  	return (0);
  }
  
***************
*** 255,260 ****
--- 269,275 ----
  void
  procfs_init()
  {
+ 	procfs_hashinit();
  }
  
  int
*** sys/miscfs/procfs/procfs.h.orig	Wed Mar 24 06:51:27 1999
--- sys/miscfs/procfs/procfs.h	Fri Jan 28 22:48:05 2000
***************
*** 62,68 ****
   * control data for the proc file system.
   */
  struct pfsnode {
! 	struct pfsnode	*pfs_next;	/* next on list */
  	struct vnode	*pfs_vnode;	/* vnode associated with this pfsnode */
  	pfstype		pfs_type;	/* type of procfs node */
  	pid_t		pfs_pid;	/* associated process */
--- 62,68 ----
   * control data for the proc file system.
   */
  struct pfsnode {
! 	LIST_ENTRY(pfsnode) pfs_hash;	/* hash chain */
  	struct vnode	*pfs_vnode;	/* vnode associated with this pfsnode */
  	pfstype		pfs_type;	/* type of procfs node */
  	pid_t		pfs_pid;	/* associated process */
***************
*** 89,94 ****
--- 89,102 ----
  			((type) + 2) : \
  			((((pid)+1) << 4) + ((int) (type))))
  
+ struct procfsmount {
+ 	void *pmnt_exechook;
+ 	struct mount *pmnt_mp;
+ };
+ 
+ #define VFSTOPROC(mp)	((struct procfsmount *)(mp)->mnt_data)
+ #define PROCTOVFS(pp)	((pp)->pmnt_mp)
+ 
  /*
   * Convert between pfsnode vnode
   */
***************
*** 126,131 ****
--- 134,141 ----
      struct uio *));
  
  int procfs_checkioperm __P((struct proc *, struct proc *));
+ void procfs_revoke_vnodes __P((struct proc *, void *));
+ void procfs_hashinit __P((void));
  
  /* functions to check whether or not files should be displayed */
  int procfs_validfile __P((struct proc *));
*** sys/miscfs/procfs/procfs_subr.c.orig	Fri Mar 12 19:45:40 1999
--- sys/miscfs/procfs/procfs_subr.c	Fri Feb 25 23:29:30 2000
***************
*** 51,59 ****
  
  #include <miscfs/procfs/procfs.h>
  
! static struct pfsnode *pfshead;
! static int pfsvplock;
  
  #define	ISSET(t, f)	((t) & (f))
  
  /*
--- 51,67 ----
  
  #include <miscfs/procfs/procfs.h>
  
! void procfs_hashins __P((struct pfsnode *));
! void procfs_hashrem __P((struct pfsnode *));
! struct vnode *procfs_hashget __P((pid_t, pfstype, struct mount *));
! 
! LIST_HEAD(pfs_hashhead, pfsnode) *pfs_hashtbl;
! u_long	pfs_ihash;	/* size of hash table - 1 */
! #define PFSPIDHASH(pid)	(&pfs_hashtbl[(pid) & pfs_ihash])
  
+ struct lock pfs_hashlock;
+ struct simplelock pfs_hash_slock;
+ 
  #define	ISSET(t, f)	((t) & (f))
  
  /*
***************
*** 91,131 ****
  {
  	struct pfsnode *pfs;
  	struct vnode *vp;
- 	struct pfsnode **pp;
  	int error;
  
! loop:
! 	for (pfs = pfshead; pfs != 0; pfs = pfs->pfs_next) {
! 		vp = PFSTOV(pfs);
! 		if (pfs->pfs_pid == pid &&
! 		    pfs->pfs_type == pfs_type &&
! 		    vp->v_mount == mp) {
! 			if (vget(vp, 0))
! 				goto loop;
! 			*vpp = vp;
  			return (0);
! 		}
! 	}
  
! 	/*
! 	 * otherwise lock the vp list while we call getnewvnode
! 	 * since that can block.
! 	 */ 
! 	if (pfsvplock & PROCFS_LOCKED) {
! 		pfsvplock |= PROCFS_WANT;
! 		sleep((caddr_t) &pfsvplock, PINOD);
! 		goto loop;
  	}
- 	pfsvplock |= PROCFS_LOCKED;
- 
- 	if ((error = getnewvnode(VT_PROCFS, mp, procfs_vnodeop_p, vpp)) != 0)
- 		goto out;
  	vp = *vpp;
  
  	MALLOC(pfs, void *, sizeof(struct pfsnode), M_TEMP, M_WAITOK);
  	vp->v_data = pfs;
  
- 	pfs->pfs_next = 0;
  	pfs->pfs_pid = (pid_t) pid;
  	pfs->pfs_type = pfs_type;
  	pfs->pfs_vnode = vp;
--- 99,121 ----
  {
  	struct pfsnode *pfs;
  	struct vnode *vp;
  	int error;
  
! 	do {
! 		if ((*vpp = procfs_hashget(pid, pfs_type, mp)) != NULL)
  			return (0);
! 	} while (lockmgr(&pfs_hashlock, LK_EXCLUSIVE|LK_SLEEPFAIL, 0));
  
! 	if ((error = getnewvnode(VT_PROCFS, mp, procfs_vnodeop_p, vpp)) != 0) {
! 		*vpp = NULL;
! 		lockmgr(&pfs_hashlock, LK_RELEASE, 0);
! 		return (error);
  	}
  	vp = *vpp;
  
  	MALLOC(pfs, void *, sizeof(struct pfsnode), M_TEMP, M_WAITOK);
  	vp->v_data = pfs;
  
  	pfs->pfs_pid = (pid_t) pid;
  	pfs->pfs_type = pfs_type;
  	pfs->pfs_vnode = vp;
***************
*** 175,192 ****
  		panic("procfs_allocvp");
  	}
  
! 	/* add to procfs vnode list */
! 	for (pp = &pfshead; *pp; pp = &(*pp)->pfs_next)
! 		continue;
! 	*pp = pfs;
! 
! out:
! 	pfsvplock &= ~PROCFS_LOCKED;
! 
! 	if (pfsvplock & PROCFS_WANT) {
! 		pfsvplock &= ~PROCFS_WANT;
! 		wakeup((caddr_t) &pfsvplock);
! 	}
  
  	return (error);
  }
--- 165,172 ----
  		panic("procfs_allocvp");
  	}
  
! 	procfs_hashins(pfs);
! 	lockmgr(&pfs_hashlock, LK_RELEASE, 0);
  
  	return (error);
  }
***************
*** 195,209 ****
  procfs_freevp(vp)
  	struct vnode *vp;
  {
- 	struct pfsnode **pfspp;
  	struct pfsnode *pfs = VTOPFS(vp);
  
! 	for (pfspp = &pfshead; *pfspp != 0; pfspp = &(*pfspp)->pfs_next) {
! 		if (*pfspp == pfs) {
! 			*pfspp = pfs->pfs_next;
! 			break;
! 		}
! 	}
  
  	FREE(vp->v_data, M_TEMP);
  	vp->v_data = 0;
--- 175,183 ----
  procfs_freevp(vp)
  	struct vnode *vp;
  {
  	struct pfsnode *pfs = VTOPFS(vp);
  
! 	procfs_hashrem(pfs);
  
  	FREE(vp->v_data, M_TEMP);
  	vp->v_data = 0;
***************
*** 332,335 ****
--- 306,394 ----
  			return (nm);
  
  	return (0);
+ }
+ 
+ /*
+  * Initialize pfsnode hash table.
+  */
+ void
+ procfs_hashinit()
+ {
+ 	lockinit(&pfs_hashlock, PINOD, "pfs_hashlock", 0, 0);
+ 	pfs_hashtbl = hashinit(desiredvnodes / 4, M_UFSMNT, M_WAITOK,
+ 	    &pfs_ihash);
+ 	simple_lock_init(&pfs_hash_slock);
+ }
+ 
+ struct vnode *
+ procfs_hashget(pid, type, mp)
+ 	pid_t pid;
+ 	pfstype type;
+ 	struct mount *mp;
+ {
+ 	struct pfsnode *pp;
+ 	struct vnode *vp;
+ 
+ loop:
+ 	simple_lock(&pfs_hash_slock);
+ 	for (pp = PFSPIDHASH(pid)->lh_first; pp; pp = pp->pfs_hash.le_next) {
+ 		vp = PFSTOV(pp);
+ 		if (pid == pp->pfs_pid && pp->pfs_type == type &&
+ 		    vp->v_mount == mp) {
+ 			simple_unlock(&pfs_hash_slock);
+ 			if (vget(vp, 0))
+ 				goto loop;
+ 			return (vp);
+ 		}
+ 	}
+ 	simple_unlock(&pfs_hash_slock);
+ 	return (NULL);
+ }
+ 
+ /*
+  * Insert the pfsnode into the hash table and lock it.
+  */
+ void
+ procfs_hashins(pp)
+ 	struct pfsnode *pp;
+ {
+ 	struct pfs_hashhead *ppp;
+ 
+ 	simple_lock(&pfs_hash_slock);
+ 	ppp = PFSPIDHASH(pp->pfs_pid);
+ 	LIST_INSERT_HEAD(ppp, pp, pfs_hash);
+ 	simple_unlock(&pfs_hash_slock);
+ }
+ 
+ /*
+  * Remove the pfsnode from the hash table.
+  */
+ void
+ procfs_hashrem(pp)
+ 	struct pfsnode *pp;
+ {
+ 	simple_lock(&pfs_hash_slock);
+ 	LIST_REMOVE(pp, pfs_hash);
+ 	simple_unlock(&pfs_hash_slock);
+ }
+ 
+ void
+ procfs_revoke_vnodes(p, arg)
+ 	struct proc *p;
+ 	void *arg;
+ {
+ 	struct pfsnode *pfs, *pnext;
+ 	struct vnode *vp;
+ 	struct mount *mp = (struct mount *)arg;
+ 
+ 	if (!(p->p_flag & P_SUGID))
+ 		return;
+ 
+ 	for (pfs = PFSPIDHASH(p->p_pid)->lh_first; pfs; pfs = pnext) {
+ 		vp = PFSTOV(pfs);
+ 		pnext = pfs->pfs_hash.le_next;
+ 		if (vp->v_usecount > 0 && pfs->pfs_pid == p->p_pid &&
+ 		    vp->v_mount == mp)
+ 			VOP_REVOKE(vp, REVOKEALL);
+ 	}
  }
*** sys/sys/systm.h.orig	Fri Jan 28 21:51:11 2000
--- sys/sys/systm.h	Fri Jan 28 22:48:05 2000
***************
*** 275,280 ****
--- 275,288 ----
  void	mountroothook_destroy __P((void));
  void	domountroothook __P((void));
  
+ /*
+  * Exec hooks. Subsystems may want to do cleanup when a process
+  * execs.
+  */
+ void	*exechook_establish __P((void (*)(struct proc *, void *), void *));
+ void	exechook_disestablish __P((void *));
+ void	doexechooks __P((struct proc *));
+ 
  int	uiomove __P((void *, int, struct uio *));
  
  #ifdef _KERNEL