On Mon, Jul 30, 2018 at 12:00:59PM -0600, Theo de Raadt wrote:
> + for (i=0; flags[i].pledge != 0; i++)
> + if (ISSET(pledge_flags, flags[i].pledge)) {
> + SET(permissions, flags[i].unveil);
> + CLR(pledge_flags, flags[i].pledge);
> + }
>
> Rather than iterating, can this be done as a direct lookup?
>
> table[PLEDGE_RPATH] = ...
> table[PLEDGE_RPATH | PLEDGE_WPATH] = ..
>
> unveil = table[pledge & range_enforcing_mask];
direct lookup isn't really possible: we would have to map a 64bits long
array in order to be exhaustive (and catch missing bits).
so I tried several alternate things.
(a) the first one was to try to reduce the array size for iterating, and
collapsing bits.
without detailing, it seems the compiler to be smart enough to unroll
the loops and use constants instead of an indirection with an array
(tested clang on i386, but objdump seems to not show me all the asm, so
I am still a bit unsure). having the "missing bits" mecanism in
DIAGNOSTIC could help too to avoid developpment checks in mainline.
but it has the drawback to put too much hope in the fact the compiler
will do the right thing, specially on all platforms (and compiler
version) we support.
(b) another point could be to extend `struct nameidata' to compute
`ni_unveil' from `ni_pledge' at namei() time. this way, we still derive
unveil flags from pledge flags, but the evaluation is done once per
namei() call (instead of once per compoment).
one possible drawback (or restriction) is the conversion is done on the
*initial* value of ni_pledge. for now, only unveil(2) itself add some
flags to ni_pledge (for example PLEDGE_STATLIE), so it shouldn't be a
problem.
(c) last possibility would be to heavy use macros to deduce UNVEIL
semantic from PLEDGE flags.
it could be done this way:
+#ifdef _KERNEL
+
+/* unveil flags definition (for affectation only) */
+#define UNVEIL_READ PLEDGE_RPATH
+#define UNVEIL_WRITE PLEDGE_WPATH
+#define UNVEIL_CREATE PLEDGE_CPATH
+#define UNVEIL_EXEC PLEDGE_EXEC
+
+#define PLEDGE_UVR_MASK (PLEDGE_RPATH|PLEDGE_UNIX)
+#define PLEDGE_UVW_MASK
(PLEDGE_WPATH|PLEDGE_FATTR|PLEDGE_CHOWN|PLEDGE_TTY|PLEDGE_UNIX)
+#define PLEDGE_UVC_MASK (PLEDGE_CPATH|PLEDGE_DPATH)
+#define PLEDGE_UVX_MASK (PLEDGE_EXEC)
+#define PLEDGE_UVz_MASK (PLEDGE_STAT|PLEDGE_STATLIE)
+
+#define PLEDGE_UV_MASK
(PLEDGE_UVR_MASK|PLEDGE_UVW_MASK|PLEDGE_UVC_MASK|PLEDGE_UVX_MASK\
+ |PLEDGE_UVz_MASK)
+
+/* unveil macros (for manipulating flags) */
+#define unveilable_flags(f) (((f) & ~(PLEDGE_UV_MASK)) == 0)
+#define unveil_read_isset(f) ((f) & PLEDGE_UVR_MASK)
+#define unveil_write_isset(f) ((f) & PLEDGE_UVW_MASK)
+#define unveil_create_isset(f) ((f) & PLEDGE_UVC_MASK)
+#define unveil_exec_isset(f) ((f) & PLEDGE_UVX_MASK)
+
+#endif
and later, use only macros for doing checks:
// affectation using UNVEIL_*
case 'r':
*perms |= UNVEIL_READ;
break;
// checking if value is bounded in what unveil expects
if (! unveilable_flags(ni_pledge))
panic("missing bits in pledge->unveil conversion");
// getting unveil semantic from PLEDGE flags
if (unveil_read_isset(ni_pledge))
...
the main drawback I see with this method, is it could be somehow fragile
for developers. for example, if someone doesn't use unveil_write_isset()
macro but prefer to directly use UNVEIL_WRITE for checking "w", he will
be wrong. it seems to me it isn't a clean way to do the thing (even if
it could be efficient).
in resume: it seems to me that doing the conversion in
unveil_flagmatch() is wrong to me. placing it in namei() seems more
consistent (and less resource consuming). it also permits to have simple
interface with UNVEIL constants instead of using macros.
the diff belows implements it (but I also have full diff for others
possibilities).
in particular:
- use separate namespace for UNVEIL and PLEDGE flags
- UNVEIL flags are `int' values (instead of uint64_t)
- add `ni_unveil' member to `struct nameidata'
- complete `ni_unveil' from `ni_pledge' in namei()
--
Sebastien Marie
Index: sys/namei.h
===================================================================
RCS file: /cvs/src/sys/sys/namei.h,v
retrieving revision 1.35
diff -u -p -r1.35 namei.h
--- sys/namei.h 13 Jul 2018 09:25:23 -0000 1.35
+++ sys/namei.h 31 Jul 2018 08:49:59 -0000
@@ -59,6 +59,7 @@ struct nameidata {
struct vnode *ni_startdir; /* starting directory */
struct vnode *ni_rootdir; /* logical root directory */
uint64_t ni_pledge; /* expected pledge for namei */
+ int ni_unveil; /* derived unveil flags from ni_pledge
*/
/*
* Results: returned from/manipulated by lookup
*/
Index: sys/proc.h
===================================================================
RCS file: /cvs/src/sys/sys/proc.h,v
retrieving revision 1.254
diff -u -p -r1.254 proc.h
--- sys/proc.h 28 Jul 2018 18:07:26 -0000 1.254
+++ sys/proc.h 31 Jul 2018 09:02:56 -0000
@@ -130,7 +130,7 @@ struct tusage {
struct unvname {
char *un_name;
size_t un_namesize;
- uint64_t un_flags;
+ int un_flags;
RBT_ENTRY(unvnmae) un_rbt;
};
@@ -424,7 +424,7 @@ struct unveil {
struct vnode *uv_vp;
struct unvname_rbt uv_names;
struct rwlock uv_lock;
- u_int64_t uv_flags;
+ int uv_flags;
};
struct uidinfo {
Index: kern/kern_unveil.c
===================================================================
RCS file: /cvs/src/sys/kern/kern_unveil.c,v
retrieving revision 1.9
diff -u -p -r1.9 kern_unveil.c
--- kern/kern_unveil.c 30 Jul 2018 15:16:27 -0000 1.9
+++ kern/kern_unveil.c 31 Jul 2018 09:30:33 -0000
@@ -40,6 +40,11 @@
#define UNVEIL_MAX_VNODES 128
#define UNVEIL_MAX_NAMES 128
+#define UNVEIL_READ 0x01
+#define UNVEIL_WRITE 0x02
+#define UNVEIL_EXEC 0x04
+#define UNVEIL_CREATE 0x08
+
static inline int
unvname_compare(const struct unvname *n1, const struct unvname *n2)
{
@@ -50,7 +55,7 @@ unvname_compare(const struct unvname *n1
}
struct unvname *
-unvname_new(const char *name, size_t size, uint64_t flags)
+unvname_new(const char *name, size_t size, int flags)
{
struct unvname *ret = malloc(sizeof(struct unvname), M_PROC, M_WAITOK);
ret->un_name = malloc(size, M_PROC, M_WAITOK);
@@ -118,7 +123,7 @@ unveil_delete_names(struct unveil *uv)
}
void
-unveil_add_name(struct unveil *uv, char *name, uint64_t flags)
+unveil_add_name(struct unveil *uv, char *name, int flags)
{
struct unvname *unvn;
@@ -310,7 +315,7 @@ unveil_lookup(struct vnode *vp, struct p
}
int
-unveil_parsepermissions(const char *permissions, uint64_t *perms)
+unveil_parsepermissions(const char *permissions, int *perms)
{
size_t i = 0;
char c;
@@ -319,16 +324,16 @@ unveil_parsepermissions(const char *perm
while ((c = permissions[i++]) != '\0') {
switch (c) {
case 'r':
- *perms |= PLEDGE_RPATH;
+ *perms |= UNVEIL_READ;
break;
case 'w':
- *perms |= PLEDGE_WPATH;
+ *perms |= UNVEIL_WRITE;
break;
case 'x':
- *perms |= PLEDGE_EXEC;
+ *perms |= UNVEIL_EXEC;
break;
case 'c':
- *perms |= PLEDGE_CPATH;
+ *perms |= UNVEIL_CREATE;
break;
default:
return -1;
@@ -338,7 +343,7 @@ unveil_parsepermissions(const char *perm
}
int
-unveil_setflags(uint64_t *flags, uint64_t nflags)
+unveil_setflags(int *flags, int nflags)
{
#if 0
if (((~(*flags)) & nflags) != 0) {
@@ -403,7 +408,7 @@ unveil_add(struct proc *p, struct nameid
struct unveil *uv;
int directory_add;
int ret = EINVAL;
- u_int64_t flags;
+ int flags;
KASSERT(ISSET(ndp->ni_cnd.cn_flags, HASBUF)); /* must have SAVENAME */
@@ -525,12 +530,43 @@ unveil_add(struct proc *p, struct nameid
return ret;
}
+void
+unveil_complete_nameidata(struct proc *p, struct nameidata *ni)
+{
+ static const struct {
+ int unveil;
+ uint64_t pledge;
+ } flags[] = {
+ { UNVEIL_READ, PLEDGE_RPATH|PLEDGE_UNIX },
+ { UNVEIL_WRITE,
PLEDGE_WPATH|PLEDGE_FATTR|PLEDGE_CHOWN|PLEDGE_TTY|PLEDGE_UNIX },
+ { UNVEIL_EXEC, PLEDGE_EXEC},
+ { UNVEIL_CREATE, PLEDGE_CPATH|PLEDGE_DPATH },
+ };
+ uint64_t ni_pledge = ni->ni_pledge;
+ int i;
+
+ if (p->p_p->ps_uvpaths == NULL)
+ return;
+
+ for (i=0; i < nitems(flags); i++)
+ if (ISSET(ni_pledge, flags[i].pledge))
+ SET(ni->ni_unveil, flags[i].unveil);
+
+#ifdef DIAGNOSTIC
+ for (i=0; i < nitems(flags); i++)
+ CLR(ni_pledge, flags[i].pledge);
+
+ if ((ni_pledge & ~(PLEDGE_UNVEIL|PLEDGE_STAT|PLEDGE_STATLIE)) != 0)
+ panic("missing flag in pledge->unveil conversion: %llu",
ni_pledge);
+#endif
+}
+
/*
* XXX this will probably change.
* XXX collapse down later once debug surely unneded
*/
int
-unveil_flagmatch(struct nameidata *ni, uint64_t flags)
+unveil_flagmatch(struct nameidata *ni, int flags)
{
if (flags == 0) {
if (ni->ni_pledge & PLEDGE_STAT) {
@@ -552,32 +588,32 @@ unveil_flagmatch(struct nameidata *ni, u
CLR(ni->ni_pledge, PLEDGE_STATLIE);
return 1;
}
- if (ni->ni_pledge & PLEDGE_RPATH) {
- if ((flags & PLEDGE_RPATH) == 0) {
+ if (ni->ni_unveil & UNVEIL_READ) {
+ if ((flags & UNVEIL_READ) == 0) {
#ifdef DEBUG_UNVEIL
printf("Pledge wants read but disallowed\n");
#endif
return 0;
}
}
- if (ni->ni_pledge & PLEDGE_WPATH) {
- if ((flags & PLEDGE_WPATH) == 0) {
+ if (ni->ni_unveil & UNVEIL_WRITE) {
+ if ((flags & UNVEIL_WRITE) == 0) {
#ifdef DEBUG_UNVEIL
printf("Pledge wants write but disallowed\n");
#endif
return 0;
}
}
- if (ni->ni_pledge & PLEDGE_EXEC) {
- if ((flags & PLEDGE_EXEC) == 0) {
+ if (ni->ni_unveil & UNVEIL_EXEC) {
+ if ((flags & UNVEIL_EXEC) == 0) {
#ifdef DEBUG_UNVEIL
printf("Pledge wants exec but disallowed\n");
#endif
return 0;
}
}
- if (ni->ni_pledge & PLEDGE_CPATH) {
- if ((flags & PLEDGE_CPATH) == 0) {
+ if (ni->ni_unveil & UNVEIL_CREATE) {
+ if ((flags & UNVEIL_CREATE) == 0) {
#ifdef DEBUG_UNVEIL
printf("Pledge wants cpath but disallowed\n");
#endif
Index: kern/vfs_lookup.c
===================================================================
RCS file: /cvs/src/sys/kern/vfs_lookup.c,v
retrieving revision 1.72
diff -u -p -r1.72 vfs_lookup.c
--- kern/vfs_lookup.c 30 Jul 2018 00:16:59 -0000 1.72
+++ kern/vfs_lookup.c 31 Jul 2018 09:31:47 -0000
@@ -59,6 +59,7 @@
void unveil_check_component(struct proc *p, struct nameidata *ni, struct vnode
*dp );
int unveil_check_final(struct proc *p, struct nameidata *ni);
+void unveil_complete_nameidata(struct proc *p, struct nameidata *ni);
void
ndinitat(struct nameidata *ndp, u_long op, u_long flags,
@@ -177,6 +178,9 @@ fail:
error = pledge_namei(p, ndp, cnp->cn_pnbuf);
if (error)
goto fail;
+
+ /* complete ni_unveil flags from ni_pledge */
+ unveil_complete_nameidata(p, ndp);
/*
* Check if starting from root directory or current directory.