summaryrefslogtreecommitdiffstats
path: root/sys/fs
diff options
context:
space:
mode:
authordes <des@FreeBSD.org>2001-10-01 04:22:20 +0000
committerdes <des@FreeBSD.org>2001-10-01 04:22:20 +0000
commite1b05f5687fbe0556cfab55517800169bc5da426 (patch)
tree62268de8821a9da6754d96e01ebfdc5653ef7674 /sys/fs
parentd6dca2e412b6f853bdfed2ddcf74bd5d060e8d67 (diff)
downloadFreeBSD-src-e1b05f5687fbe0556cfab55517800169bc5da426.zip
FreeBSD-src-e1b05f5687fbe0556cfab55517800169bc5da426.tar.gz
YA pseudofs megacommit, part 1:
- Add a third callback to the pfs_node structure. This one simply returns non-zero if the specified requesting process is allowed to access the specified node for the specified target process. This is used in addition to the usual permission checks, e.g. when certain files don't make sense for certain (system) processes. - Make sure that pfs_lookup() and pfs_readdir() don't yap about files which aren't pfs_visible(). Also check pfs_visible() before performing reads and writes, to prevent the kind of races reported in SA-00:77 and SA-01:55 (fork a child, open /proc/child/ctl, have that child fork a setuid binary, and assume control of it). - Add some more trace points.
Diffstat (limited to 'sys/fs')
-rw-r--r--sys/fs/pseudofs/pseudofs.h38
-rw-r--r--sys/fs/pseudofs/pseudofs_vncache.c96
-rw-r--r--sys/fs/pseudofs/pseudofs_vnops.c126
3 files changed, 181 insertions, 79 deletions
diff --git a/sys/fs/pseudofs/pseudofs.h b/sys/fs/pseudofs/pseudofs.h
index 8bff258..e925d92 100644
--- a/sys/fs/pseudofs/pseudofs.h
+++ b/sys/fs/pseudofs/pseudofs.h
@@ -89,6 +89,15 @@ typedef int (*pfs_attr_t)(PFS_ATTR_ARGS);
struct pfs_bitmap; /* opaque */
/*
+ * Visibility callback
+ */
+#define PFS_VIS_ARGS \
+ struct thread *td, struct proc *p, struct pfs_node *pn
+#define PFS_VIS_PROTO(name) \
+ int name(PFS_VIS_ARGS);
+typedef int (*pfs_vis_t)(PFS_VIS_ARGS);
+
+/*
* pfs_info: describes a pseudofs instance
*/
struct pfs_info {
@@ -114,6 +123,7 @@ struct pfs_node {
#define pn_func u1._pn_func
#define pn_nodes u1._pn_nodes
pfs_attr_t pn_attr;
+ pfs_vis_t pn_vis;
void *pn_data;
int pn_flags;
/* members below this line aren't initialized */
@@ -121,24 +131,24 @@ struct pfs_node {
u_int32_t pn_fileno;
};
-#define PFS_NODE(name, type, fill, attr, data, flags) \
- { (name), (type), { (fill) }, (attr), (data), (flags) }
-#define PFS_DIR(name, nodes, attr, data, flags) \
- PFS_NODE(name, pfstype_dir, nodes, attr, data, flags)
+#define PFS_NODE(name, type, fill, attr, vis, data, flags) \
+ { (name), (type), { (fill) }, (attr), (vis), (data), (flags) }
+#define PFS_DIR(name, nodes, attr, vis, data, flags) \
+ PFS_NODE(name, pfstype_dir, nodes, attr, vis, data, flags)
#define PFS_ROOT(nodes) \
- PFS_NODE("/", pfstype_root, nodes, NULL, NULL, 0)
+ PFS_NODE("/", pfstype_root, nodes, NULL, NULL, NULL, 0)
#define PFS_THIS \
- PFS_NODE(".", pfstype_this, NULL, NULL, NULL, 0)
+ PFS_NODE(".", pfstype_this, NULL, NULL, NULL, NULL, 0)
#define PFS_PARENT \
- PFS_NODE("..", pfstype_parent, NULL, NULL, NULL, 0)
-#define PFS_FILE(name, func, attr, data, flags) \
- PFS_NODE(name, pfstype_file, func, attr, data, flags)
-#define PFS_SYMLINK(name, func, attr, data, flags) \
- PFS_NODE(name, pfstype_symlink, func, attr, data, flags)
-#define PFS_PROCDIR(nodes, attr, data, flags) \
- PFS_NODE("", pfstype_procdir, nodes, attr, data, flags)
+ PFS_NODE("..", pfstype_parent, NULL, NULL, NULL, NULL, 0)
+#define PFS_FILE(name, func, attr, vis, data, flags) \
+ PFS_NODE(name, pfstype_file, func, attr, vis, data, flags)
+#define PFS_SYMLINK(name, func, attr, vis, data, flags) \
+ PFS_NODE(name, pfstype_symlink, func, attr, vis, data, flags)
+#define PFS_PROCDIR(nodes, attr, vis, data, flags) \
+ PFS_NODE("", pfstype_procdir, nodes, attr, vis, data, flags)
#define PFS_LASTNODE \
- PFS_NODE("", pfstype_none, NULL, NULL, NULL, 0)
+ PFS_NODE("", pfstype_none, NULL, NULL, NULL, NULL, 0)
/*
* VFS interface
diff --git a/sys/fs/pseudofs/pseudofs_vncache.c b/sys/fs/pseudofs/pseudofs_vncache.c
index 3ff33d7..191f85c 100644
--- a/sys/fs/pseudofs/pseudofs_vncache.c
+++ b/sys/fs/pseudofs/pseudofs_vncache.c
@@ -35,6 +35,7 @@
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/mutex.h>
+#include <sys/proc.h>
#include <sys/sbuf.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>
@@ -45,21 +46,30 @@
static MALLOC_DEFINE(M_PFSVNCACHE, "pfs_vncache", "pseudofs vnode cache");
static struct mtx pfs_vncache_mutex;
-
-struct pfs_vnode {
- struct vnode *pv_vnode;
- struct pfs_vnode *pv_next;
-} *pfs_vncache;
+struct pfs_vdata *pfs_vncache;
+static void pfs_exit(struct proc *p);
SYSCTL_NODE(_vfs_pfs, OID_AUTO, vncache, CTLFLAG_RW, 0,
"pseudofs vnode cache");
+static int pfs_vncache_entries;
+SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, entries, CTLFLAG_RD,
+ &pfs_vncache_entries, 0,
+ "number of entries in the vnode cache");
+
+static int pfs_vncache_maxentries;
+SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, maxentries, CTLFLAG_RD,
+ &pfs_vncache_maxentries, 0,
+ "highest number of entries in the vnode cache");
+
static int pfs_vncache_hits;
-SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, hits, CTLFLAG_RD, &pfs_vncache_hits, 0,
+SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, hits, CTLFLAG_RD,
+ &pfs_vncache_hits, 0,
"number of cache hits since initialization");
static int pfs_vncache_misses;
-SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, misses, CTLFLAG_RD, &pfs_vncache_misses, 0,
+SYSCTL_INT(_vfs_pfs_vncache, OID_AUTO, misses, CTLFLAG_RD,
+ &pfs_vncache_misses, 0,
"number of cache misses since initialization");
extern vop_t **pfs_vnodeop_p;
@@ -70,7 +80,9 @@ extern vop_t **pfs_vnodeop_p;
void
pfs_vncache_load(void)
{
- mtx_init(&pfs_vncache_mutex, "pseudofs_vncache", MTX_DEF);
+ mtx_init(&pfs_vncache_mutex, "pseudofs_vncache", MTX_DEF|MTX_RECURSE);
+ /* XXX at_exit() can fail with ENOMEN */
+ at_exit(pfs_exit);
}
/*
@@ -79,6 +91,7 @@ pfs_vncache_load(void)
void
pfs_vncache_unload(void)
{
+ rm_at_exit(pfs_exit);
mtx_destroy(&pfs_vncache_mutex);
}
@@ -89,18 +102,17 @@ int
pfs_vncache_alloc(struct mount *mp, struct vnode **vpp,
struct pfs_node *pn, pid_t pid)
{
- struct pfs_vnode *pv;
struct pfs_vdata *pvd;
int error;
/* see if the vnode is in the cache */
+ /* XXX linear search... not very efficient */
mtx_lock(&pfs_vncache_mutex);
- for (pv = pfs_vncache; pv; pv = pv->pv_next) {
- pvd = (struct pfs_vdata *)pv->pv_vnode->v_data;
+ for (pvd = pfs_vncache; pvd; pvd = pvd->pvd_next) {
if (pvd->pvd_pn == pn && pvd->pvd_pid == pid) {
- if (vget(pv->pv_vnode, 0, curthread) == 0) {
+ if (vget(pvd->pvd_vnode, 0, curthread) == 0) {
++pfs_vncache_hits;
- *vpp = pv->pv_vnode;
+ *vpp = pvd->pvd_vnode;
mtx_unlock(&pfs_vncache_mutex);
return (0);
}
@@ -112,8 +124,9 @@ pfs_vncache_alloc(struct mount *mp, struct vnode **vpp,
++pfs_vncache_misses;
/* nope, get a new one */
- MALLOC(pv, struct pfs_vnode *, sizeof *pv, M_PFSVNCACHE, M_WAITOK);
MALLOC(pvd, struct pfs_vdata *, sizeof *pvd, M_PFSVNCACHE, M_WAITOK);
+ if (++pfs_vncache_entries > pfs_vncache_maxentries)
+ pfs_vncache_maxentries = pfs_vncache_entries;
error = getnewvnode(VT_PSEUDOFS, mp, pfs_vnodeop_p, vpp);
if (error)
return (error);
@@ -126,6 +139,7 @@ pfs_vncache_alloc(struct mount *mp, struct vnode **vpp,
#if 0
printf("root vnode allocated\n");
#endif
+ /* fall through */
case pfstype_dir:
case pfstype_this:
case pfstype_parent:
@@ -143,10 +157,13 @@ pfs_vncache_alloc(struct mount *mp, struct vnode **vpp,
default:
panic("%s has unexpected type: %d", pn->pn_name, pn->pn_type);
}
- pv->pv_vnode = *vpp;
+ pvd->pvd_vnode = *vpp;
mtx_lock(&pfs_vncache_mutex);
- pv->pv_next = pfs_vncache;
- pfs_vncache = pv;
+ pvd->pvd_prev = NULL;
+ pvd->pvd_next = pfs_vncache;
+ if (pvd->pvd_next)
+ pvd->pvd_next->pvd_prev = pvd;
+ pfs_vncache = pvd;
mtx_unlock(&pfs_vncache_mutex);
return (0);
}
@@ -157,23 +174,46 @@ pfs_vncache_alloc(struct mount *mp, struct vnode **vpp,
int
pfs_vncache_free(struct vnode *vp)
{
- struct pfs_vnode *prev, *pv;
struct pfs_vdata *pvd;
mtx_lock(&pfs_vncache_mutex);
- for (prev = NULL, pv = pfs_vncache; pv; prev = pv, pv = pv->pv_next)
- if (pv->pv_vnode == vp)
- break;
- KASSERT(pv != NULL, ("pfs_vncache_free(): not in cache\n"));
- if (prev)
- prev->pv_next = pv->pv_next;
+ pvd = (struct pfs_vdata *)vp->v_data;
+ KASSERT(pvd != NULL, ("pfs_vncache_free(): no vnode data\n"));
+ if (pvd->pvd_next)
+ pvd->pvd_next->pvd_prev = pvd->pvd_prev;
+ if (pvd->pvd_prev)
+ pvd->pvd_prev->pvd_next = pvd->pvd_next;
else
- pfs_vncache = pv->pv_next;
+ pfs_vncache = pvd->pvd_next;
mtx_unlock(&pfs_vncache_mutex);
-
- pvd = (struct pfs_vdata *)vp->v_data;
+
+ --pfs_vncache_entries;
FREE(pvd, M_PFSVNCACHE);
vp->v_data = NULL;
- FREE(pv, M_PFSVNCACHE);
return (0);
}
+
+/*
+ * Free all vnodes associated with a defunct process
+ */
+static void
+pfs_exit(struct proc *p)
+{
+ struct pfs_vdata *pvd, *prev;
+
+ mtx_lock(&pfs_vncache_mutex);
+ /*
+ * The double loop is necessary because vgone() indirectly
+ * calls pfs_vncache_free() which frees pvd, so we have to
+ * backtrace one step every time we free a vnode.
+ */
+ /* XXX linear search... not very efficient */
+ for (pvd = pfs_vncache; pvd != NULL; pvd = pvd->pvd_next) {
+ while (pvd != NULL && pvd->pvd_pid == p->p_pid) {
+ prev = pvd->pvd_prev;
+ vgone(pvd->pvd_vnode);
+ pvd = prev ? prev->pvd_next : pfs_vncache;
+ }
+ }
+ mtx_unlock(&pfs_vncache_mutex);
+}
diff --git a/sys/fs/pseudofs/pseudofs_vnops.c b/sys/fs/pseudofs/pseudofs_vnops.c
index c582612..305ed21 100644
--- a/sys/fs/pseudofs/pseudofs_vnops.c
+++ b/sys/fs/pseudofs/pseudofs_vnops.c
@@ -67,6 +67,31 @@
#endif
/*
+ * Returns non-zero if given file is visible to given process
+ */
+static int
+pfs_visible(struct thread *td, struct pfs_node *pn, pid_t pid)
+{
+ struct proc *proc;
+ int r;
+
+ PFS_TRACE(("%s (pid: %d, req: %d)",
+ pn->pn_name, pid, td->td_proc->p_pid));
+ if (pid == NO_PID)
+ PFS_RETURN (1);
+
+ r = 1;
+ if ((proc = pfind(pid)) == NULL)
+ PFS_RETURN (0);
+ /* XXX should lock td->td_proc? */
+ if (p_cansee(td->td_proc, proc) != 0 ||
+ (pn->pn_vis != NULL && !(pn->pn_vis)(td, proc, pn)))
+ r = 0;
+ PROC_UNLOCK(proc);
+ PFS_RETURN (r);
+}
+
+/*
* Verify permissions
*/
static int
@@ -82,10 +107,10 @@ pfs_access(struct vop_access_args *va)
error = VOP_GETATTR(vn, &vattr, va->a_cred, va->a_td);
if (error)
- return (error);
+ PFS_RETURN (error);
error = vaccess(vn->v_type, vattr.va_mode, vattr.va_uid,
vattr.va_gid, va->a_mode, va->a_cred, NULL);
- return (error);
+ PFS_RETURN (error);
}
/*
@@ -146,7 +171,7 @@ pfs_getattr(struct vop_getattr_args *va)
vap->va_uid = proc->p_ucred->cr_ruid;
vap->va_gid = proc->p_ucred->cr_rgid;
if (pn->pn_attr != NULL)
- error = (pn->pn_attr)(curthread, proc, pn, vap);
+ error = (pn->pn_attr)(va->a_td, proc, pn, vap);
PROC_UNLOCK(proc);
} else {
vap->va_uid = 0;
@@ -168,7 +193,6 @@ pfs_lookup(struct vop_lookup_args *va)
struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
struct pfs_node *pd = pvd->pvd_pn;
struct pfs_node *pn, *pdn = NULL;
- struct proc *proc;
pid_t pid = pvd->pvd_pid;
char *pname;
int error, i, namelen;
@@ -190,12 +214,9 @@ pfs_lookup(struct vop_lookup_args *va)
if (cnp->cn_namelen >= PFS_NAMELEN)
PFS_RETURN (ENOENT);
- /* check that owner process exists */
- if (pvd->pvd_pid != NO_PID) {
- if ((proc = pfind(pvd->pvd_pid)) == NULL)
- PFS_RETURN (ENOENT);
- PROC_UNLOCK(proc);
- }
+ /* check that parent directory is visisble... */
+ if (!pfs_visible(curthread, pd, pvd->pvd_pid))
+ PFS_RETURN (ENOENT);
/* self */
namelen = cnp->cn_namelen;
@@ -204,7 +225,7 @@ pfs_lookup(struct vop_lookup_args *va)
pn = pd;
*vpp = vn;
VREF(vn);
- goto got_vnode;
+ PFS_RETURN (0);
}
/* parent */
@@ -223,10 +244,8 @@ pfs_lookup(struct vop_lookup_args *va)
*/
if (pd->pn_type == pfstype_procdir)
pid = NO_PID;
- error = pfs_vncache_alloc(vn->v_mount, vpp, pd->pn_parent, pid);
- if (error)
- PFS_RETURN (error);
- goto got_vnode;
+ pn = pd->pn_parent;
+ goto got_pnode;
}
/* named node */
@@ -249,12 +268,13 @@ pfs_lookup(struct vop_lookup_args *va)
PFS_RETURN (ENOENT);
got_pnode:
- if (!pn->pn_parent)
+ if (pn != pd->pn_parent && !pn->pn_parent)
pn->pn_parent = pd;
+ if (!pfs_visible(curthread, pn, pvd->pvd_pid))
+ PFS_RETURN (ENOENT);
error = pfs_vncache_alloc(vn->v_mount, vpp, pn, pid);
if (error)
PFS_RETURN (error);
- got_vnode:
if (cnp->cn_flags & MAKEENTRY)
cache_enter(vn, *vpp, cnp);
PFS_RETURN (0);
@@ -270,11 +290,22 @@ pfs_open(struct vop_open_args *va)
struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
struct pfs_node *pn = pvd->pvd_pn;
int mode = va->a_mode;
- struct proc *proc;
- int error;
PFS_TRACE(("%s (mode 0x%x)", pn->pn_name, mode));
+ /*
+ * check if the file is visible to the caller
+ *
+ * XXX Not sure if this is necessary, as the VFS system calls
+ * XXX pfs_lookup() and pfs_access() first, and pfs_lookup()
+ * XXX calls pfs_visible(). There's a race condition here, but
+ * XXX calling pfs_visible() from here doesn't really close it,
+ * XXX and the only consequence of that race is an EIO further
+ * XXX down the line.
+ */
+ if (!pfs_visible(va->a_td, pn, pvd->pvd_pid))
+ PFS_RETURN (ENOENT);
+
/* check if the requested mode is permitted */
if (((mode & FREAD) && !(mode & PFS_RD)) ||
((mode & FWRITE) && !(mode & PFS_WR)))
@@ -284,17 +315,7 @@ pfs_open(struct vop_open_args *va)
if ((mode & O_SHLOCK) || (mode & O_EXLOCK))
PFS_RETURN (EOPNOTSUPP);
- error = 0;
- if (pvd->pvd_pid != NO_PID) {
- if ((proc = pfind(pvd->pvd_pid)) == NULL)
- PFS_RETURN (ENOENT);
- /* XXX should lock va->a_td->td_proc? */
- if (p_cansee(va->a_td->td_proc, proc) != 0)
- error = ENOENT;
- PROC_UNLOCK(proc);
- }
-
- PFS_RETURN (error);
+ PFS_RETURN (0);
}
/*
@@ -320,6 +341,14 @@ pfs_read(struct vop_read_args *va)
if (!(pn->pn_flags & PFS_RD))
PFS_RETURN (EBADF);
+ /*
+ * This is necessary because either process' privileges may
+ * have changed since the open() call.
+ */
+ if (!pfs_visible(curthread, pn, pvd->pvd_pid))
+ PFS_RETURN (EIO);
+
+ /* XXX duplicates bits of pfs_visible() */
if (pvd->pvd_pid != NO_PID) {
if ((proc = pfind(pvd->pvd_pid)) == NULL)
PFS_RETURN (EIO);
@@ -365,11 +394,12 @@ pfs_read(struct vop_read_args *va)
* Iterate through directory entries
*/
static int
-pfs_iterate(struct pfs_info *pi, struct pfs_node **pn, struct proc **p)
+pfs_iterate(struct thread *td, pid_t pid, struct pfs_node **pn, struct proc **p)
{
if ((*pn)->pn_type == pfstype_none)
return (-1);
+ again:
if ((*pn)->pn_type != pfstype_procdir)
++*pn;
@@ -379,12 +409,15 @@ pfs_iterate(struct pfs_info *pi, struct pfs_node **pn, struct proc **p)
else
*p = LIST_NEXT(*p, p_list);
if (*p != NULL)
- return (0);
+ break;
++*pn;
}
if ((*pn)->pn_type == pfstype_none)
return (-1);
+
+ if (!pfs_visible(td, *pn, *p ? (*p)->p_pid : pid))
+ goto again;
return (0);
}
@@ -399,6 +432,7 @@ pfs_readdir(struct vop_readdir_args *va)
struct pfs_info *pi = (struct pfs_info *)vn->v_mount->mnt_data;
struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
struct pfs_node *pd = pvd->pvd_pn;
+ pid_t pid = pvd->pvd_pid;
struct pfs_node *pn;
struct dirent entry;
struct uio *uio;
@@ -412,6 +446,10 @@ pfs_readdir(struct vop_readdir_args *va)
PFS_RETURN (ENOTDIR);
uio = va->a_uio;
+ /* check if the directory is visible to the caller */
+ if (!pfs_visible(curthread, pd, pid))
+ PFS_RETURN (ENOENT);
+
/* only allow reading entire entries */
offset = uio->uio_offset;
resid = uio->uio_resid;
@@ -421,18 +459,18 @@ pfs_readdir(struct vop_readdir_args *va)
/* skip unwanted entries */
sx_slock(&allproc_lock);
for (pn = pd->pn_nodes, p = NULL; offset > 0; offset -= PFS_DELEN)
- if (pfs_iterate(pi, &pn, &p) == -1)
+ if (pfs_iterate(curthread, pid, &pn, &p) == -1)
break;
/* fill in entries */
entry.d_reclen = PFS_DELEN;
- while (pfs_iterate(pi, &pn, &p) != -1 && resid > 0) {
+ while (pfs_iterate(curthread, pid, &pn, &p) != -1 && resid > 0) {
if (!pn->pn_parent)
pn->pn_parent = pd;
if (!pn->pn_fileno)
pfs_fileno_alloc(pi, pn);
- if (pvd->pvd_pid != NO_PID)
- entry.d_fileno = pn->pn_fileno * NO_PID + pvd->pvd_pid;
+ if (pid != NO_PID)
+ entry.d_fileno = pn->pn_fileno * NO_PID + pid;
else
entry.d_fileno = pn->pn_fileno;
/* PFS_DELEN was picked to fit PFS_NAMLEN */
@@ -533,6 +571,12 @@ pfs_readlink(struct vop_readlink_args *va)
static int
pfs_reclaim(struct vop_reclaim_args *va)
{
+ struct vnode *vn = va->a_vp;
+ struct pfs_vdata *pvd = (struct pfs_vdata *)vn->v_data;
+ struct pfs_node *pn = pvd->pvd_pn;
+
+ PFS_TRACE((pn->pn_name));
+
return (pfs_vncache_free(va->a_vp));
}
@@ -576,6 +620,14 @@ pfs_write(struct vop_read_args *va)
if (!(pn->pn_flags & PFS_WR))
PFS_RETURN (EBADF);
+ /*
+ * This is necessary because either process' privileges may
+ * have changed since the open() call.
+ */
+ if (!pfs_visible(curthread, pn, pvd->pvd_pid))
+ PFS_RETURN (EIO);
+
+ /* XXX duplicates bits of pfs_visible() */
if (pvd->pvd_pid != NO_PID) {
if ((proc = pfind(pvd->pvd_pid)) == NULL)
PFS_RETURN (EIO);
@@ -636,7 +688,7 @@ static struct vnodeopv_entry_desc pfs_vnodeop_entries[] = {
{ &vop_setattr_desc, (vop_t *)pfs_setattr },
{ &vop_symlink_desc, (vop_t *)pfs_badop },
{ &vop_write_desc, (vop_t *)pfs_write },
- /* XXX I've probably forgotten a few that need pfs_erofs */
+ /* XXX I've probably forgotten a few that need pfs_badop */
{ NULL, (vop_t *)NULL }
};
OpenPOWER on IntegriCloud