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