cap: fix format-string vulnerability
[akaros.git] / kern / drivers / dev / capability.c
index 53a59af..f489e6f 100644 (file)
@@ -7,79 +7,84 @@
  * in the LICENSE file.
  */
 
-#include       "u.h"
-#include       "../port/lib.h"
-#include       "mem.h"
-#include       "dat.h"
-#include       "fns.h"
-#include       "../port/error.h"
-
-#include       <libsec.h>
-
-enum
-{
-       Hashlen=        SHA1dlen,
-       Maxhash=        256,
+#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 = VB2_MAX_DIGEST_SIZE * 2,
+       Maxhash = 256,
 };
 
 /*
  *  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 {
+       struct Caphash *next;
+       char hash[Hashlen + 1];
 };
 
-struct
-{
-       QLock QLock;
-       Caphash *first;
-       int     nhash;
+struct {
+       qlock_t qlock;
+       struct Caphash *first;
+       int nhash;
 } capalloc;
 
-enum
-{
+enum {
        Qdir,
        Qhash,
        Quse,
 };
 
 /* 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;
+       if (iseve() && c->qid.path == Qhash)
+               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);
 }
@@ -87,22 +92,22 @@ 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 (c->qid.type & QTDIR) {
+               if (openmode(omode) != O_READ)
+                       error(EISDIR, "Can only read a directory");
+               c->mode = openmode(omode);
                c->flag |= COPEN;
                c->offset = 0;
                return c;
        }
 
-       switch((uint32_t)c->qid.path){
+       switch ((uint32_t)c->qid.path) {
        case Qhash:
-               if(!iseve())
-                       error(Eperm);
+               if (!iseve())
+                       error(EPERM,
+                             "Permission denied: only eve can open Qhash");
                break;
        }
 
@@ -112,173 +117,182 @@ 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;
 
-       for(i = 0; i < Hashlen; i++)
-               sprint(buf+2*i, "%2.2x", hash[i]);
-       buf[2*Hashlen] = 0;
-       return buf;
+       for (i = 0; i < bytes_to_split; i++)
+               snprintf(buf + 2 * i, 3, "%02x", hash[i]);
+
+       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)
+               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;
 }
 
 /* add a capability, throwing out any old ones */
-static void
-addcap(uint8_t *hash)
+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){
+       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){
+       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){
+       switch ((uint32_t)c->qid.path) {
        case Qhash:
-               if(!iseve())
-                       error(Eperm);
-               if(n < Hashlen)
-                       error(Eshort);
+               if (!iseve())
+                       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;
-               if(waserror()){
-                       free(cp);
+               /* 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 = 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",
+struct dev capdevtab __devtab = {
+       .name = "capability",
 
        .reset = devreset,
-       .init = devinit,
+       .init = capinit,
        .shutdown = devshutdown,
        .attach = capattach,
        .walk = capwalk,
@@ -291,5 +305,5 @@ Dev capdevtab = {
        .write = capwrite,
        .bwrite = devbwrite,
        .remove = capremove,
-       .wstat = devwstat
+       .wstat = devwstat,
 };