cap: fix format-string vulnerability
[akaros.git] / kern / drivers / dev / capability.c
index a3bafd7..f489e6f 100644 (file)
@@ -7,17 +7,27 @@
  * in the LICENSE file.
  */
 
-#include "../port/error.h"
-#include "../port/lib.h"
-#include "dat.h"
-#include "fns.h"
-#include "mem.h"
-#include "u.h"
-
-#include <libsec.h>
+#include <slab.h>
+#include <kmalloc.h>
+#include <kref.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <error.h>
+#include <cpio.h>
+#include <pmap.h>
+#include <smp.h>
+#include <net/ip.h>
+
+#include <crypto/2crypto.h>
+#include <crypto/2hmac.h>
+#include <crypto/2id.h>
+#include <crypto/2sha.h>
+
+#include <ctype.h>
 
 enum {
-       Hashlen = SHA1dlen,
+       Hashlen = VB2_MAX_DIGEST_SIZE * 2,
        Maxhash = 256,
 };
 
@@ -25,15 +35,14 @@ enum {
  *  if a process knows cap->cap, it can change user
  *  to capabilty->user.
  */
-typedef struct Caphash Caphash;
 struct Caphash {
-       Caphash *next;
-       char hash[Hashlen];
+       struct Caphash *next;
+       char hash[Hashlen + 1];
 };
 
 struct {
-       QLock QLock;
-       Caphash *first;
+       qlock_t qlock;
+       struct Caphash *first;
        int nhash;
 } capalloc;
 
@@ -44,31 +53,38 @@ enum {
 };
 
 /* caphash must be last */
-Dirtab capdir[] = {
-    ".",       {Qdir, 0, QTDIR}, 0, DMDIR | 0500, "capuse", {Quse}, 0, 0222,
-    "caphash", {Qhash},          0, 0200,
+struct dirtab capdir[] = {
+       {".",       {Qdir, 0, QTDIR}, 0, DMDIR | 0500},
+       {"capuse",  {Quse},           0, 0222,},
+       {"caphash", {Qhash},          0, 0200,},
 };
-int ncapdir = nelem(capdir);
+int ncapdir = ARRAY_SIZE(capdir);
+
+static void capinit(void)
+{
+       qlock_init(&capalloc.qlock);
+}
 
-static Chan *capattach(char *spec)
+static struct chan *capattach(char *spec)
 {
-       return devattach(L'¤', spec);
+       return devattach("capability", spec);
 }
 
-static Walkqid *capwalk(Chan *c, Chan *nc, char **name, int nname)
+static struct walkqid *capwalk(struct chan *c, struct chan *nc, char **name,
+                               unsigned int nname)
 {
        return devwalk(c, nc, name, nname, capdir, ncapdir, devgen);
 }
 
-static void capremove(Chan *c)
+static void capremove(struct chan *c)
 {
        if (iseve() && c->qid.path == Qhash)
-               ncapdir = nelem(capdir) - 1;
+               ncapdir = ARRAY_SIZE(capdir) - 1;
        else
-               error(Eperm);
+               error(EPERM, "Permission denied");
 }
 
-static int32_t capstat(Chan *c, uint8_t *db, int32_t n)
+static size_t capstat(struct chan *c, uint8_t *db, size_t n)
 {
        return devstat(c, db, n, capdir, ncapdir, devgen);
 }
@@ -76,12 +92,12 @@ static int32_t capstat(Chan *c, uint8_t *db, int32_t n)
 /*
  *  if the stream doesn't exist, create it
  */
-static Chan *capopen(Chan *c, int omode)
+static struct chan *capopen(struct chan *c, int omode)
 {
        if (c->qid.type & QTDIR) {
-               if (omode != OREAD)
-                       error(Ebadarg);
-               c->mode = omode;
+               if (openmode(omode) != O_READ)
+                       error(EISDIR, "Can only read a directory");
+               c->mode = openmode(omode);
                c->flag |= COPEN;
                c->offset = 0;
                return c;
@@ -90,7 +106,8 @@ static Chan *capopen(Chan *c, int omode)
        switch ((uint32_t)c->qid.path) {
        case Qhash:
                if (!iseve())
-                       error(Eperm);
+                       error(EPERM,
+                             "Permission denied: only eve can open Qhash");
                break;
        }
 
@@ -100,39 +117,35 @@ static Chan *capopen(Chan *c, int omode)
        return c;
 }
 
-/*
-static char*
-hashstr(uchar *hash)
+static size_t __hashstr(char *buf, uint8_t *hash, size_t bytes_to_split)
 {
-    static char buf[2*Hashlen+1];
-    int i;
+       int i;
+
+       for (i = 0; i < bytes_to_split; i++)
+               snprintf(buf + 2 * i, 3, "%02x", hash[i]);
 
-    for(i = 0; i < Hashlen; i++)
-        sprint(buf+2*i, "%2.2x", hash[i]);
-    buf[2*Hashlen] = 0;
-    return buf;
+       return bytes_to_split;
 }
- */
 
-static Caphash *remcap(uint8_t *hash)
+static struct Caphash *remcap(uint8_t *hash)
 {
-       Caphash *t, **l;
+       struct Caphash *t, **l;
 
-       qlock(&capalloc.QLock);
+       qlock(&capalloc.qlock);
 
        /* find the matching capability */
-       for (l = &capalloc.first; *l != nil;) {
+       for (l = &capalloc.first; *l != NULL;) {
                t = *l;
                if (memcmp(hash, t->hash, Hashlen) == 0)
                        break;
                l = &t->next;
        }
        t = *l;
-       if (t != nil) {
+       if (t != NULL) {
                capalloc.nhash--;
                *l = t->next;
        }
-       qunlock(&capalloc.QLock);
+       qunlock(&capalloc.qlock);
 
        return t;
 }
@@ -140,137 +153,157 @@ static Caphash *remcap(uint8_t *hash)
 /* add a capability, throwing out any old ones */
 static void addcap(uint8_t *hash)
 {
-       Caphash *p, *t, **l;
+       struct Caphash *p, *t, **l;
 
-       p = smalloc(sizeof *p);
+       p = kzmalloc(sizeof(*p), 0);
        memmove(p->hash, hash, Hashlen);
-       p->next = nil;
+       p->next = NULL;
 
-       qlock(&capalloc.QLock);
+       qlock(&capalloc.qlock);
 
        /* trim extras */
        while (capalloc.nhash >= Maxhash) {
                t = capalloc.first;
-               if (t == nil)
+               if (t == NULL)
                        panic("addcap");
                capalloc.first = t->next;
-               free(t);
+               kfree(t);
                capalloc.nhash--;
        }
 
        /* add new one */
-       for (l = &capalloc.first; *l != nil; l = &(*l)->next)
+       for (l = &capalloc.first; *l != NULL; l = &(*l)->next)
                ;
        *l = p;
        capalloc.nhash++;
 
-       qunlock(&capalloc.QLock);
+       qunlock(&capalloc.qlock);
 }
 
-static void capclose(Chan *c)
+static void capclose(struct chan *c)
 {
 }
 
-static int32_t capread(Chan *c, void *va, int32_t n, int64_t m)
+static size_t capread(struct chan *c, void *va, size_t n, off64_t m)
 {
        switch ((uint32_t)c->qid.path) {
        case Qdir:
                return devdirread(c, va, n, capdir, ncapdir, devgen);
 
        default:
-               error(Eperm);
+               error(EPERM, "Permission denied: can't read capability files");
                break;
        }
        return n;
 }
 
-static int32_t capwrite(Chan *c, void *va, int32_t n, int64_t m)
+static size_t capwrite(struct chan *c, void *va, size_t n, off64_t m)
 {
-       Caphash *p;
+       struct Caphash *p;
        char *cp;
-       uint8_t hash[Hashlen];
+       uint8_t hash[Hashlen + 1] = {0};
+       char *hashstr = NULL;
        char *key, *from, *to;
-       char err[256];
-       Proc *up = externup();
+       int ret;
+       ERRSTACK(1);
 
        switch ((uint32_t)c->qid.path) {
        case Qhash:
                if (!iseve())
-                       error(Eperm);
-               if (n < Hashlen)
-                       error(Eshort);
+                       error(EPERM, "permission denied: you must be eve");
+               if (n < VB2_SHA256_DIGEST_SIZE * 2)
+                       error(EIO, "Short read: on Qhash");
                memmove(hash, va, Hashlen);
+               for (int i = 0; i < Hashlen; i++)
+                       hash[i] = tolower(hash[i]);
                addcap(hash);
                break;
 
        case Quse:
-               /* copy key to avoid a fault in hmac_xx */
-               cp = nil;
+               /* copy key to avoid a fault in hmac_xx and so we can enforce
+                * null termination. */
+               cp = NULL;
                if (waserror()) {
-                       free(cp);
+                       kfree(cp);
+                       kfree(hashstr);
                        nexterror();
                }
-               cp = smalloc(n + 1);
+               cp = kzmalloc(n + 1, 0);
                memmove(cp, va, n);
                cp[n] = 0;
 
                from = cp;
                key = strrchr(cp, '@');
-               if (key == nil)
-                       error(Eshort);
+               if (key == NULL)
+                       error(EIO, "short read: Quse");
                *key++ = 0;
 
-               hmac_sha1((uint8_t *)from, strlen(from), (uint8_t *)key, strlen(key),
-                         hash, nil);
+               ret = hmac(VB2_HASH_SHA256, key, strlen(key),
+                          from, strlen(from), hash, Hashlen);
+               if (ret)
+                       error(EINVAL, "HMAC failed");
 
-               p = remcap(hash);
-               if (p == nil) {
-                       snprint(err, sizeof err, "invalid capability %s@%s", from, key);
-                       error(err);
-               }
+               // Convert to ASCII text
+               hashstr = (char *)kzmalloc(sizeof(hash), MEM_WAIT);
+               ret = __hashstr(hashstr, hash, VB2_SHA256_DIGEST_SIZE);
+               if (ret != VB2_SHA256_DIGEST_SIZE)
+                       error(EINVAL, "hash is wrong length");
+
+               p = remcap((uint8_t *)hashstr);
+               if (p == NULL)
+                       error(EINVAL, "invalid capability %s@%s", from, key);
+
+               kfree(hashstr);
+               hashstr = NULL;
 
                /* if a from user is supplied, make sure it matches */
                to = strchr(from, '@');
-               if (to == nil) {
+               if (to == NULL) {
                        to = from;
                } else {
                        *to++ = 0;
-                       if (strcmp(from, up->user) != 0)
-                               error("capability must match user");
+                       if (strcmp(from, current->user.name) != 0)
+                               error(EINVAL, "capability must match user");
                }
 
                /* set user id */
-               kstrdup(&up->user, to);
-               up->basepri = PriNormal;
-
-               free(p);
-               free(cp);
+               // In the original user names were NULL-terminated; ensure
+               // that is still the case.
+               if (strlen(to) > sizeof(current->user.name) - 1)
+                       error(EINVAL, "New user name is > %d bytes",
+                             sizeof(current->user.name) - 1);
+               proc_set_username(current, to);
+               //up->basepri = PriNormal;
+
+               kfree(p);
+               kfree(cp);
                poperror();
                break;
 
        default:
-               error(Eperm);
+               error(EPERM, "permission denied: capwrite");
                break;
        }
 
        return n;
 }
 
-Dev capdevtab = {.dc = L'¤',
-                 .name = "cap",
-
-                 .reset = devreset,
-                 .init = devinit,
-                 .shutdown = devshutdown,
-                 .attach = capattach,
-                 .walk = capwalk,
-                 .stat = capstat,
-                 .open = capopen,
-                 .create = devcreate,
-                 .close = capclose,
-                 .read = capread,
-                 .bread = devbread,
-                 .write = capwrite,
-                 .bwrite = devbwrite,
-                 .remove = capremove,
-                 .wstat = devwstat};
+struct dev capdevtab __devtab = {
+       .name = "capability",
+
+       .reset = devreset,
+       .init = capinit,
+       .shutdown = devshutdown,
+       .attach = capattach,
+       .walk = capwalk,
+       .stat = capstat,
+       .open = capopen,
+       .create = devcreate,
+       .close = capclose,
+       .read = capread,
+       .bread = devbread,
+       .write = capwrite,
+       .bwrite = devbwrite,
+       .remove = capremove,
+       .wstat = devwstat,
+};