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