So this gets rid of unveil's PLEDGE_STAT.
Instead we use UNVEIL_INSPECT which is set by the stat and access opeerations
that are needed for realpath() type traversals that effectively call stat/access
for each component of a pathname before doing a final operation on the end.
The intended semantic of UNVEIL_INSPECT (which is only used in the kernel)
is to allow inspection of vnodes that are traversed on the way to an
unveil'ed component - just like what PLEDGE_STAT did.
This also removes the use of PLEDGE_STATLIE in unveil - theo and I had
discussed that this was probably fine in lubljana, but I never did it
then. I'll remove STATLIE later if we decide that's the way we
are going.
Passes regress - realpath still works, etc. etc.
ok?
Index: kern/kern_pledge.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_pledge.c,v
retrieving revision 1.239
diff -u -p -u -p -r1.239 kern_pledge.c
--- kern/kern_pledge.c 2 Aug 2018 15:34:07 -0000 1.239
+++ kern/kern_pledge.c 5 Aug 2018 17:45:52 -0000
@@ -608,14 +608,14 @@ pledge_namei(struct proc *p, struct name
switch (p->p_pledge_syscall) {
case SYS_access:
/* tzset() needs this. */
- if ((ni->ni_pledge == (PLEDGE_RPATH | PLEDGE_STAT)) &&
+ if (ni->ni_pledge == PLEDGE_RPATH &&
strcmp(path, "/etc/localtime") == 0) {
ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
}
/* when avoiding YP mode, getpw* functions touch this */
- if (ni->ni_pledge == (PLEDGE_RPATH | PLEDGE_STAT) &&
+ if (ni->ni_pledge == PLEDGE_RPATH &&
strcmp(path, "/var/run/ypbind.lock") == 0) {
if (p->p_p->ps_pledge & PLEDGE_GETPW) {
ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
@@ -713,7 +713,7 @@ pledge_namei(struct proc *p, struct name
break;
case SYS_readlink:
/* Allow /etc/malloc.conf for malloc(3). */
- if ((ni->ni_pledge == (PLEDGE_RPATH | PLEDGE_STAT)) &&
+ if ((ni->ni_pledge == PLEDGE_RPATH) &&
strcmp(path, "/etc/malloc.conf") == 0) {
ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
return (0);
@@ -721,7 +721,7 @@ pledge_namei(struct proc *p, struct name
break;
case SYS_stat:
/* DNS needs /etc/resolv.conf. */
- if ((ni->ni_pledge == (PLEDGE_RPATH | PLEDGE_STAT)) &&
+ if ((ni->ni_pledge == PLEDGE_RPATH) &&
(p->p_p->ps_pledge & PLEDGE_DNS) &&
strcmp(path, "/etc/resolv.conf") == 0) {
ni->ni_cnd.cn_flags |= BYPASSUNVEIL;
@@ -732,9 +732,9 @@ pledge_namei(struct proc *p, struct name
/*
* Ensure each flag of ni_pledge has counterpart allowing it in
- * ps_pledge. discard PLEDGE_STAT as it is unveil(2) stuff.
+ * ps_pledge.
*/
- if ((ni->ni_pledge & ~PLEDGE_STAT) & ~p->p_p->ps_pledge)
+ if (ni->ni_pledge & ~p->p_p->ps_pledge)
return (pledge_fail(p, EPERM, (ni->ni_pledge &
~p->p_p->ps_pledge)));
/* continue, and check unveil if present */
Index: kern/kern_unveil.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_unveil.c,v
retrieving revision 1.11
diff -u -p -u -p -r1.11 kern_unveil.c
--- kern/kern_unveil.c 5 Aug 2018 14:23:57 -0000 1.11
+++ kern/kern_unveil.c 5 Aug 2018 17:45:52 -0000
@@ -379,13 +379,20 @@ unveil_add_vnode(struct process *pr, str
return (uv);
}
-void
+int
unveil_add_traversed_vnodes(struct proc *p, struct nameidata *ndp)
{
/*
- * add the traversed vnodes with 0 flags if they
- * are not already present.
+ * Add the traversed vnodes with the UNVEIL_INSPECT flag
+ * if they are not already present to allow traversal
+ * operations such as access and stat. This lets
+ * TOCTOU fans that call access on all components of
+ * an unveil'ed path before the final operation
+ * work.
*/
+ int ret = 0;
+ struct unveil *uv;
+
if (ndp->ni_tvpsize) {
size_t i;
@@ -394,10 +401,15 @@ unveil_add_traversed_vnodes(struct proc
if (unveil_lookup(vp, p) == NULL) {
vref(vp);
vp->v_uvcount++;
- unveil_add_vnode(p->p_p, vp);
+ uv = unveil_add_vnode(p->p_p, vp);
+ if (uv != NULL)
+ uv->uv_flags = UNVEIL_INSPECT;
+ else
+ ret = E2BIG;
}
}
}
+ return ret;
}
int
@@ -524,7 +536,7 @@ unveil_add(struct proc *p, struct nameid
done:
if (ret == 0)
- unveil_add_traversed_vnodes(p, ndp);
+ ret = unveil_add_traversed_vnodes(p, ndp);
unveil_free_traversed_vnodes(ndp);
pool_put(&namei_pool, ndp->ni_cnd.cn_pnbuf);
return ret;
@@ -538,26 +550,11 @@ int
unveil_flagmatch(struct nameidata *ni, u_char flags)
{
if (flags == 0) {
- /* XXX Fix this, you can do it better */
- if (ni->ni_pledge & PLEDGE_STAT) {
-#ifdef DEBUG_UNVEIL
- printf("allowing stat/accesss for 0 flags");
-#endif
- SET(ni->ni_pledge, PLEDGE_STATLIE);
- return 1;
- }
#ifdef DEBUG_UNVEIL
printf("All operations forbidden for 0 flags\n");
#endif
return 0;
}
- if (ni->ni_pledge & PLEDGE_STAT) {
-#ifdef DEBUG_UNVEIL
- printf("Allowing stat for nonzero flags\n");
-#endif
- CLR(ni->ni_pledge, PLEDGE_STATLIE);
- return 1;
- }
if (ni->ni_unveil & UNVEIL_READ) {
if ((flags & UNVEIL_READ) == 0) {
#ifdef DEBUG_UNVEIL
@@ -590,6 +587,11 @@ unveil_flagmatch(struct nameidata *ni, u
return 0;
}
}
+ if (ni->ni_unveil & UNVEIL_INSPECT) {
+#ifdef DEBUG_UNVEIL
+ printf("any unveil allows UNVEIL_INSPECT\n");
+#endif
+ }
return 1;
}
@@ -607,7 +609,7 @@ unveil_check_component(struct proc *p, s
(uv = unveil_lookup(dp, p)) != NULL) {
/* if directory flags match, it's a match */
if (unveil_flagmatch(ni, uv->uv_flags)) {
- if (uv->uv_flags) {
+ if (uv->uv_flags & UNVEIL_RWXC) {
ni->ni_unveil_match = uv;
#ifdef DEBUG_UNVEIL
printf("unveil: %s(%d): component
directory match"
Index: kern/vfs_syscalls.c
===================================================================
RCS file: /cvs/src/sys/kern/vfs_syscalls.c,v
retrieving revision 1.301
diff -u -p -u -p -r1.301 vfs_syscalls.c
--- kern/vfs_syscalls.c 5 Aug 2018 14:23:57 -0000 1.301
+++ kern/vfs_syscalls.c 5 Aug 2018 17:45:52 -0000
@@ -1809,8 +1809,8 @@ dofaccessat(struct proc *p, int fd, cons
}
NDINITAT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, fd, path, p);
- nd.ni_pledge = PLEDGE_RPATH | PLEDGE_STAT;
- nd.ni_unveil = 0; /* XXX fix this when we fix PLEDGE_STAT */
+ nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_unveil = UNVEIL_INSPECT;
if ((error = namei(&nd)) != 0)
goto out;
vp = nd.ni_vp;
@@ -1880,8 +1880,8 @@ dofstatat(struct proc *p, int fd, const
follow = (flag & AT_SYMLINK_NOFOLLOW) ? NOFOLLOW : FOLLOW;
NDINITAT(&nd, LOOKUP, follow | LOCKLEAF, UIO_USERSPACE, fd, path, p);
- nd.ni_pledge = PLEDGE_RPATH | PLEDGE_STAT;
- nd.ni_unveil = 0;
+ nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_unveil = UNVEIL_INSPECT;
if ((error = namei(&nd)) != 0)
return (error);
error = vn_stat(nd.ni_vp, &sb, p);
@@ -1989,8 +1989,8 @@ doreadlinkat(struct proc *p, int fd, con
struct nameidata nd;
NDINITAT(&nd, LOOKUP, NOFOLLOW | LOCKLEAF, UIO_USERSPACE, fd, path, p);
- nd.ni_pledge = PLEDGE_RPATH | PLEDGE_STAT;
- nd.ni_unveil = 0;
+ nd.ni_pledge = PLEDGE_RPATH;
+ nd.ni_unveil = UNVEIL_INSPECT;
if ((error = namei(&nd)) != 0)
return (error);
vp = nd.ni_vp;
Index: sys/namei.h
===================================================================
RCS file: /cvs/src/sys/sys/namei.h,v
retrieving revision 1.36
diff -u -p -u -p -r1.36 namei.h
--- sys/namei.h 5 Aug 2018 14:23:57 -0000 1.36
+++ sys/namei.h 5 Aug 2018 17:45:52 -0000
@@ -257,5 +257,7 @@ struct nchstats {
#define UNVEIL_WRITE 0x02
#define UNVEIL_CREATE 0x04
#define UNVEIL_EXEC 0x08
+#define UNVEIL_RWXC 0x0F
+#define UNVEIL_INSPECT 0x80
#endif /* !_SYS_NAMEI_H_ */
Index: sys/pledge.h
===================================================================
RCS file: /cvs/src/sys/sys/pledge.h,v
retrieving revision 1.37
diff -u -p -u -p -r1.37 pledge.h
--- sys/pledge.h 13 Jul 2018 09:25:23 -0000 1.37
+++ sys/pledge.h 5 Aug 2018 17:45:52 -0000
@@ -68,9 +68,8 @@
* to track program behaviours which have been observed.
*/
#define PLEDGE_USERSET 0x0fffffffffffffffULL
-#define PLEDGE_STAT 0x2000000000000000ULL /* XXX this is a stat */
-#define PLEDGE_STATLIE 0x4000000000000000ULL
-#define PLEDGE_YPACTIVE 0x8000000000000000ULL /* YP use detected and
allowed */
+#define PLEDGE_STATLIE 0x2000000000000000ULL
+#define PLEDGE_YPACTIVE 0x4000000000000000ULL /* YP use detected and
allowed */
#ifdef PLEDGENAMES
static struct {