cap: fix format-string vulnerability
[akaros.git] / kern / drivers / dev / capability.c
index 1b5b252..f489e6f 100644 (file)
@@ -7,8 +7,6 @@
  * in the LICENSE file.
  */
 
-#include <vfs.h>
-#include <kfs.h>
 #include <slab.h>
 #include <kmalloc.h>
 #include <kref.h>
 #include <cpio.h>
 #include <pmap.h>
 #include <smp.h>
-#include <ip.h>
+#include <net/ip.h>
 
-#include <vfs.h>
-#include <kfs.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 <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,
 };
 
@@ -44,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_t qlock_t qlock;
-       Caphash *first;
+       qlock_t qlock;
+       struct Caphash *first;
        int nhash;
 } capalloc;
 
@@ -64,18 +54,24 @@ enum {
 
 /* caphash must be last */
 struct dirtab capdir[] = {
-    ".",       {Qdir, 0, QTDIR}, 0, DMDIR | 0500, "capuse", {Quse}, 0, 0222,
-    "caphash", {Qhash},          0, 0200,
+       {".",       {Qdir, 0, QTDIR}, 0, DMDIR | 0500},
+       {"capuse",  {Quse},           0, 0222,},
+       {"caphash", {Qhash},          0, 0200,},
 };
 int ncapdir = ARRAY_SIZE(capdir);
 
+static void capinit(void)
+{
+       qlock_init(&capalloc.qlock);
+}
+
 static struct chan *capattach(char *spec)
 {
-       return devattach(L'¤', spec);
+       return devattach("capability", spec);
 }
 
 static struct walkqid *capwalk(struct chan *c, struct chan *nc, char **name,
-                       int nname)
+                               unsigned int nname)
 {
        return devwalk(c, nc, name, nname, capdir, ncapdir, devgen);
 }
@@ -85,10 +81,10 @@ static void capremove(struct chan *c)
        if (iseve() && c->qid.path == Qhash)
                ncapdir = ARRAY_SIZE(capdir) - 1;
        else
-               error(Eperm);
+               error(EPERM, "Permission denied");
 }
 
-static int32_t capstat(struct 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);
 }
@@ -99,9 +95,9 @@ static int32_t capstat(struct chan *c, uint8_t *db, int32_t n)
 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;
@@ -110,7 +106,8 @@ static struct chan *capopen(struct 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;
        }
 
@@ -120,25 +117,21 @@ static struct chan *capopen(struct chan *c, int omode)
        return c;
 }
 
-/*
-static char*
-hashstr(uint8_t *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);
+       qlock(&capalloc.qlock);
 
        /* find the matching capability */
        for (l = &capalloc.first; *l != NULL;) {
@@ -152,7 +145,7 @@ static Caphash *remcap(uint8_t *hash)
                capalloc.nhash--;
                *l = t->next;
        }
-       qunlock(&(&capalloc.QLock)->qlock);
+       qunlock(&capalloc.qlock);
 
        return t;
 }
@@ -160,13 +153,13 @@ 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 = kzmalloc(sizeof(*p), 0);
        memmove(p->hash, hash, Hashlen);
        p->next = NULL;
 
-       qlock(&(&capalloc.QLock)->qlock);
+       qlock(&capalloc.qlock);
 
        /* trim extras */
        while (capalloc.nhash >= Maxhash) {
@@ -184,50 +177,55 @@ static void addcap(uint8_t *hash)
        *l = p;
        capalloc.nhash++;
 
-       qunlock(&(&capalloc.QLock)->qlock);
+       qunlock(&capalloc.qlock);
 }
 
 static void capclose(struct chan *c)
 {
 }
 
-static int32_t capread(struct 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(struct 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];
-       struct 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 */
+               /* copy key to avoid a fault in hmac_xx and so we can enforce
+                * null termination. */
                cp = NULL;
                if (waserror()) {
                        kfree(cp);
+                       kfree(hashstr);
                        nexterror();
                }
                cp = kzmalloc(n + 1, 0);
@@ -237,17 +235,26 @@ static int32_t capwrite(struct chan *c, void *va, int32_t n, int64_t m)
                from = cp;
                key = strrchr(cp, '@');
                if (key == NULL)
-                       error(Eshort);
+                       error(EIO, "short read: Quse");
                *key++ = 0;
 
-               hmac_sha1((uint8_t *)from, strlen(from), (uint8_t *)key, strlen(key),
-                         hash, NULL);
+               ret = hmac(VB2_HASH_SHA256, key, strlen(key),
+                          from, strlen(from), hash, Hashlen);
+               if (ret)
+                       error(EINVAL, "HMAC failed");
 
-               p = remcap(hash);
-               if (p == NULL) {
-                       snprintf(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, '@');
@@ -255,13 +262,18 @@ static int32_t capwrite(struct chan *c, void *va, int32_t n, int64_t m)
                        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;
+               // 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);
@@ -269,28 +281,29 @@ static int32_t capwrite(struct chan *c, void *va, int32_t n, int64_t m)
                break;
 
        default:
-               error(Eperm);
+               error(EPERM, "permission denied: capwrite");
                break;
        }
 
        return n;
 }
 
-struct 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,
+};