summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Beck <beck@cvs.openbsd.org>2016-04-28 14:25:09 +0000
committerBob Beck <beck@cvs.openbsd.org>2016-04-28 14:25:09 +0000
commitfce521a545176443545811717213822d0651a03e (patch)
tree7d6d4d18dd6d06dc40a644b1c240649b70006990
parentadc2de426ae23e1ae6dffc2ae80c3e8485d2b857 (diff)
1) Split pledge whitelist path handling out of pledge_namei() and into
pledge_namei_wlpath(). Call the wlpath check only at the end of namei after the namei lookup would otherwise succeed. 2) Add support to namei to keep the path that was looked up, without the symlinks in it, and use that path for whitelist path lookups. This means that paths in pledge whitelists will need to always be the real path to an intended file to whitelist, without symlinks. Any symlinks to the "real" file will then be allowed ok deraadt@ semarie@
-rw-r--r--sys/kern/kern_pledge.c132
-rw-r--r--sys/kern/vfs_lookup.c132
-rw-r--r--sys/sys/namei.h9
-rw-r--r--sys/sys/pledge.h3
4 files changed, 200 insertions, 76 deletions
diff --git a/sys/kern/kern_pledge.c b/sys/kern/kern_pledge.c
index 57bf3539eca..e30e3ee95e5 100644
--- a/sys/kern/kern_pledge.c
+++ b/sys/kern/kern_pledge.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kern_pledge.c,v 1.164 2016/04/25 10:01:23 semarie Exp $ */
+/* $OpenBSD: kern_pledge.c,v 1.165 2016/04/28 14:25:08 beck Exp $ */
/*
* Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
@@ -739,82 +739,94 @@ pledge_namei(struct proc *p, struct nameidata *ni, char *origpath)
if (ni->ni_pledge & ~p->p_p->ps_pledge)
return (pledge_fail(p, EPERM, (ni->ni_pledge & ~p->p_p->ps_pledge)));
+ return (0);
+}
+
+/*
+ * wlpath lookup - only done after namei lookup has succeeded on the last compoent of
+ * a namei lookup, with a possibly non-canonicalized path given in "origpath" from namei.
+ */
+int
+pledge_namei_wlpath(struct proc *p, struct nameidata *ni)
+{
+ struct whitepaths *wl = p->p_p->ps_pledgepaths;
+ char *rdir = NULL, *cwd = NULL, *resolved = NULL;
+ size_t rdirlen, cwdlen, resolvedlen;
+ int i, error, pardir_found;
+
/*
* If a whitelist is set, compare canonical paths. Anything
* not on the whitelist gets ENOENT.
*/
- if (p->p_p->ps_pledgepaths) {
- struct whitepaths *wl = p->p_p->ps_pledgepaths;
- char *rdir = NULL, *cwd = NULL, *resolved = NULL;
- size_t rdirlen, cwdlen, resolvedlen;
- int i, error, pardir_found;
-
- error = resolvpath(p, &rdir, &rdirlen, &cwd, &cwdlen,
- origpath, strlen(origpath)+1, &resolved, &resolvedlen);
-
- free(rdir, M_TEMP, rdirlen);
- free(cwd, M_TEMP, cwdlen);
+ if (ni->ni_p_path == NULL)
+ return(0);
- if (error != 0)
- /* resolved is allocated only if !error */
- return (error);
+ KASSERT(p->p_p->ps_pledgepaths);
- /* print resolved path (viewed as without chroot) */
- DNPRINTF(2, "pledge_namei: resolved=\"%s\" [%lld] strlen=%lld\n",
- resolved, (long long)resolvedlen,
- (long long)strlen(resolved));
+ // XXX change later or more help from namei?
+ error = resolvpath(p, &rdir, &rdirlen, &cwd, &cwdlen,
+ ni->ni_p_path, ni->ni_p_length+1, &resolved, &resolvedlen);
- error = ENOENT;
- pardir_found = 0;
- for (i = 0; i < wl->wl_count && wl->wl_paths[i].name && error; i++) {
- int substr = substrcmp(wl->wl_paths[i].name,
- wl->wl_paths[i].len - 1, resolved, resolvedlen - 1);
+ free(rdir, M_TEMP, rdirlen);
+ free(cwd, M_TEMP, cwdlen);
- /* print check between registered wl_path and resolved */
- DNPRINTF(3,
- "pledge: check: \"%s\" (%ld) \"%s\" (%ld) = %d\n",
- wl->wl_paths[i].name, wl->wl_paths[i].len - 1,
- resolved, resolvedlen - 1,
- substr);
-
- /* wl_paths[i].name is a substring of resolved */
- if (substr == 1) {
- u_char term = resolved[wl->wl_paths[i].len - 1];
+ if (error != 0)
+ /* resolved is allocated only if !error */
+ return (error);
- if (term == '\0' || term == '/' ||
- wl->wl_paths[i].name[1] == '\0')
- error = 0;
+ /* print resolved path (viewed as without chroot) */
+ DNPRINTF(2, "pledge_namei: resolved=\"%s\" [%lld] strlen=%lld\n",
+ resolved, (long long)resolvedlen,
+ (long long)strlen(resolved));
+
+ error = ENOENT;
+ pardir_found = 0;
+ for (i = 0; i < wl->wl_count && wl->wl_paths[i].name && error; i++) {
+ int substr = substrcmp(wl->wl_paths[i].name,
+ wl->wl_paths[i].len - 1, resolved, resolvedlen - 1);
+
+ /* print check between registered wl_path and resolved */
+ DNPRINTF(3,
+ "pledge: check: \"%s\" (%ld) \"%s\" (%ld) = %d\n",
+ wl->wl_paths[i].name, wl->wl_paths[i].len - 1,
+ resolved, resolvedlen - 1,
+ substr);
+
+ /* wl_paths[i].name is a substring of resolved */
+ if (substr == 1) {
+ u_char term = resolved[wl->wl_paths[i].len - 1];
+
+ if (term == '\0' || term == '/' ||
+ wl->wl_paths[i].name[1] == '\0')
+ error = 0;
/* resolved is a substring of wl_paths[i].name */
- } else if (substr == 2) {
- u_char term = wl->wl_paths[i].name[resolvedlen - 1];
+ } else if (substr == 2) {
+ u_char term = wl->wl_paths[i].name[resolvedlen - 1];
- if (resolved[1] == '\0' || term == '/')
- pardir_found = 1;
- }
+ if (resolved[1] == '\0' || term == '/')
+ pardir_found = 1;
+ }
+ }
+ if (pardir_found)
+ switch (p->p_pledge_syscall) {
+ case SYS_stat:
+ case SYS_lstat:
+ case SYS_fstatat:
+ case SYS_fstat:
+ ni->ni_pledge |= PLEDGE_STATLIE;
+ error = 0;
}
- if (pardir_found)
- switch (p->p_pledge_syscall) {
- case SYS_stat:
- case SYS_lstat:
- case SYS_fstatat:
- case SYS_fstat:
- ni->ni_pledge |= PLEDGE_STATLIE;
- error = 0;
- }
#ifdef DEBUG_PLEDGE
- if (error == ENOENT)
- /* print the path that is reported as ENOENT */
- DNPRINTF(1, "pledge: %s(%d): wl_path ENOENT: \"%s\"\n",
- p->p_comm, p->p_pid, resolved);
+ if (error == ENOENT)
+ /* print the path that is reported as ENOENT */
+ DNPRINTF(1, "pledge: %s(%d): wl_path ENOENT: \"%s\"\n",
+ p->p_comm, p->p_pid, resolved);
#endif
- free(resolved, M_TEMP, resolvedlen);
- return (error); /* Don't hint why it failed */
- }
-
- return (0);
+ free(resolved, M_TEMP, resolvedlen);
+ return (error); /* Don't hint why it failed */
}
/*
diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c
index 245fc1ebfe4..fa921d04b22 100644
--- a/sys/kern/vfs_lookup.c
+++ b/sys/kern/vfs_lookup.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: vfs_lookup.c,v 1.61 2016/04/25 20:00:33 tedu Exp $ */
+/* $OpenBSD: vfs_lookup.c,v 1.62 2016/04/28 14:25:08 beck Exp $ */
/* $NetBSD: vfs_lookup.c,v 1.17 1996/02/09 19:00:59 christos Exp $ */
/*
@@ -52,12 +52,49 @@
#include <sys/pledge.h>
#include <sys/file.h>
#include <sys/fcntl.h>
+#include <sys/types.h>
+#include <sys/malloc.h>
#ifdef KTRACE
#include <sys/ktrace.h>
#endif
+void
+push_component(struct nameidata *ndp, char *cp, size_t cplen)
+{
+ KASSERT(cplen < MAXPATHLEN);
+ if (ndp->ni_p_size - ndp->ni_p_length <= cplen) {
+ char *tmp = malloc(ndp->ni_p_size + MAXPATHLEN, M_TEMP, M_WAITOK);
+ memcpy(tmp, ndp->ni_p_path, ndp->ni_p_length);
+ ndp->ni_p_next = tmp + (ndp->ni_p_next - ndp->ni_p_path);
+ ndp->ni_p_prev = tmp + (ndp->ni_p_prev - ndp->ni_p_path);
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
+ ndp->ni_p_path = tmp;
+ ndp->ni_p_size += MAXPATHLEN;
+ }
+ memcpy(ndp->ni_p_next, cp, cplen);
+ ndp->ni_p_prev = ndp->ni_p_next;
+ ndp->ni_p_next += cplen;
+ ndp->ni_p_length += cplen;
+ *ndp->ni_p_next = '\0';
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ printf("push: ndp->ni_p_path = %s\n", ndp->ni_p_path);
+#endif
+}
+
+void
+pop_symlink(struct nameidata *ndp)
+{
+ KASSERT(ndp->ni_p_next != ndp->ni_p_prev);
+ ndp->ni_p_length = ndp->ni_p_prev - ndp->ni_p_path;
+ ndp->ni_p_next = ndp->ni_p_prev;
+ *ndp->ni_p_next = '\0';
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ printf("pop: ndp->ni_p_path = %s\n", ndp->ni_p_path);
+#endif
+}
+
/*
* Convert a pathname into a pointer to a vnode.
*
@@ -158,28 +195,51 @@ fail:
*/
if ((ndp->ni_rootdir = fdp->fd_rdir) == NULL)
ndp->ni_rootdir = rootvnode;
-
+
error = pledge_namei(p, ndp, cnp->cn_pnbuf);
if (error)
goto fail;
/*
+ * Decide if we need to call pledge_namei_wlpath after namei lookup, if
+ * so give namei a place to store a path for it to look at.
+ */
+ if (!ISSET(p->p_p->ps_flags, PS_COREDUMP) &&
+ ISSET(p->p_p->ps_flags, PS_PLEDGE) && p->p_p->ps_pledgepaths) {
+ ndp->ni_p_path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK);
+ ndp->ni_p_next = ndp->ni_p_prev = ndp->ni_p_path;
+ ndp->ni_p_size = MAXPATHLEN;
+ ndp->ni_p_length = 0;
+ } else {
+ ndp->ni_p_path = NULL;
+ ndp->ni_p_size = 0;
+ }
+
+ /*
* Check if starting from root directory or current directory.
*/
if (cnp->cn_pnbuf[0] == '/') {
dp = ndp->ni_rootdir;
vref(dp);
+ if (ndp->ni_p_path != NULL) {
+ *ndp->ni_p_path = '/';
+ ndp->ni_p_next++;
+ *ndp->ni_p_next = '\0';
+ ndp->ni_p_length = 1;
+ }
} else if (ndp->ni_dirfd == AT_FDCWD) {
dp = fdp->fd_cdir;
vref(dp);
} else {
struct file *fp = fd_getfile(fdp, ndp->ni_dirfd);
if (fp == NULL) {
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
pool_put(&namei_pool, cnp->cn_pnbuf);
return (EBADF);
}
dp = (struct vnode *)fp->f_data;
if (fp->f_type != DTYPE_VNODE || dp->v_type != VDIR) {
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
pool_put(&namei_pool, cnp->cn_pnbuf);
return (ENOTDIR);
}
@@ -188,12 +248,14 @@ fail:
for (;;) {
if (!dp->v_mount) {
/* Give up if the directory is no longer mounted */
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
pool_put(&namei_pool, cnp->cn_pnbuf);
return (ENOENT);
}
cnp->cn_nameptr = cnp->cn_pnbuf;
ndp->ni_startdir = dp;
if ((error = vfs_lookup(ndp)) != 0) {
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
pool_put(&namei_pool, cnp->cn_pnbuf);
return (error);
}
@@ -201,11 +263,24 @@ fail:
* If not a symbolic link, return search result.
*/
if ((cnp->cn_flags & ISSYMLINK) == 0) {
+ error = pledge_namei_wlpath(p, ndp);
+ if (error) {
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ printf("pledge_namei error %d for path %s\n", error, ndp->ni_p_path);
+#endif
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
+ pool_put(&namei_pool, cnp->cn_pnbuf);
+ if (ndp->ni_vp)
+ vput(ndp->ni_vp);
+ ndp->ni_vp = NULL;
+ return(error);
+ }
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0)
pool_put(&namei_pool, cnp->cn_pnbuf);
else
cnp->cn_flags |= HASBUF;
- return (0);
+ return(0);
}
if ((cnp->cn_flags & LOCKPARENT) && (cnp->cn_flags & ISLASTCN))
VOP_UNLOCK(ndp->ni_dvp, p);
@@ -258,9 +333,17 @@ badlink:
vrele(dp);
dp = ndp->ni_rootdir;
vref(dp);
+ if (ndp->ni_p_path != NULL) {
+ ndp->ni_p_next = ndp->ni_p_prev = ndp->ni_p_path;
+ *ndp->ni_p_path = '/';
+ ndp->ni_p_next++;
+ *ndp->ni_p_next = '\0';
+ ndp->ni_p_length = 1;
+ }
}
}
pool_put(&namei_pool, cnp->cn_pnbuf);
+ free(ndp->ni_p_path, M_TEMP, ndp->ni_p_size);
vrele(ndp->ni_dvp);
vput(ndp->ni_vp);
ndp->ni_vp = NULL;
@@ -392,11 +475,17 @@ dirloop:
}
#ifdef NAMEI_DIAGNOSTIC
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ if (p && p->p_p && p->p_p->ps_pledgepaths)
+#endif
{ char c = *cp;
*cp = '\0';
printf("{%s}: ", cnp->cn_nameptr);
*cp = c; }
#endif
+ if (ndp->ni_p_path != NULL)
+ push_component(ndp, cnp->cn_nameptr, cnp->cn_namelen);
+
ndp->ni_pathlen -= cnp->cn_namelen;
ndp->ni_next = cp;
/*
@@ -478,7 +567,10 @@ dirloop:
panic("leaf should be empty");
#endif
#ifdef NAMEI_DIAGNOSTIC
- printf("not found\n");
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ if (p && p->p_p && p->p_p->ps_pledgepaths)
+#endif
+ printf("not found\n");
#endif
if (error != EJUSTRETURN)
goto bad;
@@ -510,7 +602,10 @@ dirloop:
return (0);
}
#ifdef NAMEI_DIAGNOSTIC
- printf("found\n");
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ if (p && p->p_p && p->p_p->ps_pledgepaths)
+#endif
+ printf("found\n");
#endif
/*
@@ -558,6 +653,8 @@ dirloop:
ndp->ni_pathlen += slashes;
ndp->ni_next -= slashes;
cnp->cn_flags |= ISSYMLINK;
+ if (ndp->ni_p_path != NULL)
+ pop_symlink(ndp);
return (0);
}
@@ -578,6 +675,8 @@ nextname:
if (!(cnp->cn_flags & ISLASTCN)) {
cnp->cn_nameptr = ndp->ni_next;
vrele(ndp->ni_dvp);
+ if (ndp->ni_p_path != NULL)
+ push_component(ndp, "/", 1);
goto dirloop;
}
@@ -658,16 +757,21 @@ vfs_relookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp)
*/
#ifdef NAMEI_DIAGNOSTIC
- /* XXX: Figure out the length of the last component. */
- cp = cnp->cn_nameptr;
- while (*cp && (*cp != '/')) {
- cp++;
+#ifdef NAMEI_DIAGNOSTIC_PLEDGE
+ if (p && p->p_p && p->p_p->ps_pledgepaths)
+#endif
+ {
+ /* XXX: Figure out the length of the last component. */
+ cp = cnp->cn_nameptr;
+ while (*cp && (*cp != '/')) {
+ cp++;
+ }
+ if (cnp->cn_namelen != cp - cnp->cn_nameptr)
+ panic("relookup: bad len");
+ if (*cp != 0)
+ panic("relookup: not last component");
+ printf("{%s}: ", cnp->cn_nameptr);
}
- if (cnp->cn_namelen != cp - cnp->cn_nameptr)
- panic("relookup: bad len");
- if (*cp != 0)
- panic("relookup: not last component");
- printf("{%s}: ", cnp->cn_nameptr);
#endif
/*
diff --git a/sys/sys/namei.h b/sys/sys/namei.h
index 9b4c42e3654..799d9cd90c0 100644
--- a/sys/sys/namei.h
+++ b/sys/sys/namei.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: namei.h,v 1.30 2015/12/06 17:50:21 deraadt Exp $ */
+/* $OpenBSD: namei.h,v 1.31 2016/04/28 14:25:08 beck Exp $ */
/* $NetBSD: namei.h,v 1.11 1996/02/09 18:25:20 christos Exp $ */
/*
@@ -74,6 +74,13 @@ struct nameidata {
size_t ni_pathlen; /* remaining chars in path */
char *ni_next; /* next location in pathname */
u_long ni_loopcnt; /* count of symlinks encountered */
+
+ char *ni_p_path; /* component path for pledge */
+ size_t ni_p_size; /* allocated size of pledge path */
+ size_t ni_p_length; /* length of pledge path */
+ char *ni_p_next; /* start of next component in pledge path */
+ char *ni_p_prev; /* previous component in pledge path */
+
/*
* Lookup parameters: this structure describes the subset of
* information from the nameidata structure that is passed
diff --git a/sys/sys/pledge.h b/sys/sys/pledge.h
index 0193de41d6d..0db6d10ad1c 100644
--- a/sys/sys/pledge.h
+++ b/sys/sys/pledge.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: pledge.h,v 1.27 2016/01/09 06:13:44 semarie Exp $ */
+/* $OpenBSD: pledge.h,v 1.28 2016/04/28 14:25:08 beck Exp $ */
/*
* Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
@@ -112,6 +112,7 @@ int pledge_fail(struct proc *, int, uint64_t);
struct mbuf;
struct nameidata;
int pledge_namei(struct proc *, struct nameidata *, char *);
+int pledge_namei_wlpath(struct proc *, struct nameidata *);
int pledge_sendfd(struct proc *p, struct file *);
int pledge_recvfd(struct proc *p, struct file *);
int pledge_sysctl(struct proc *p, int namelen, int *name, void *new);