blob: 662508670e4330d3680daac0c48928f7af261fec [file] [log] [blame]
/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#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.
*/
struct Caphash {
struct Caphash *next;
char hash[Hashlen + 1];
};
struct {
qlock_t qlock;
struct Caphash *first;
int nhash;
} capalloc;
enum {
Qdir,
Qhash,
Quse,
};
/* caphash must be last */
struct dirtab capdir[] = {
{".", {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("capability", spec);
}
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(struct chan *c)
{
if (iseve() && c->qid.path == Qhash)
ncapdir = ARRAY_SIZE(capdir) - 1;
else
error(EPERM, "Permission denied");
}
static size_t capstat(struct chan *c, uint8_t *db, size_t n)
{
return devstat(c, db, n, capdir, ncapdir, devgen);
}
/*
* if the stream doesn't exist, create it
*/
static struct chan *capopen(struct chan *c, int 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) {
case Qhash:
if (!iseve())
error(EPERM,
"Permission denied: only eve can open Qhash");
break;
}
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
return c;
}
static size_t __hashstr(char *buf, uint8_t *hash, size_t bytes_to_split)
{
int i;
for (i = 0; i < bytes_to_split; i++)
snprintf(buf + 2 * i, 3, "%02x", hash[i]);
return bytes_to_split;
}
static struct Caphash *remcap(uint8_t *hash)
{
struct Caphash *t, **l;
qlock(&capalloc.qlock);
/* find the matching capability */
for (l = &capalloc.first; *l != NULL;) {
t = *l;
if (memcmp(hash, t->hash, Hashlen) == 0)
break;
l = &t->next;
}
t = *l;
if (t != NULL) {
capalloc.nhash--;
*l = t->next;
}
qunlock(&capalloc.qlock);
return t;
}
/* add a capability, throwing out any old ones */
static void addcap(uint8_t *hash)
{
struct Caphash *p, *t, **l;
p = kzmalloc(sizeof(*p), 0);
memmove(p->hash, hash, Hashlen);
p->next = NULL;
qlock(&capalloc.qlock);
/* trim extras */
while (capalloc.nhash >= Maxhash) {
t = capalloc.first;
if (t == NULL)
panic("addcap");
capalloc.first = t->next;
kfree(t);
capalloc.nhash--;
}
/* add new one */
for (l = &capalloc.first; *l != NULL; l = &(*l)->next)
;
*l = p;
capalloc.nhash++;
qunlock(&capalloc.qlock);
}
static void capclose(struct chan *c)
{
}
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, "Permission denied: can't read capability files");
break;
}
return n;
}
static size_t capwrite(struct chan *c, void *va, size_t n, off64_t m)
{
struct Caphash *p;
char *cp;
uint8_t hash[Hashlen + 1] = {0};
char *hashstr = NULL;
char *key, *from, *to;
char err[256];
int ret;
ERRSTACK(1);
switch ((uint32_t)c->qid.path) {
case Qhash:
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 = NULL;
if (waserror()) {
kfree(cp);
kfree(hashstr);
nexterror();
}
cp = kzmalloc(n + 1, 0);
memmove(cp, va, n);
cp[n] = 0;
from = cp;
key = strrchr(cp, '@');
if (key == NULL)
error(EIO, "short read: Quse");
*key++ = 0;
ret = hmac(VB2_HASH_SHA256, key, strlen(key),
from, strlen(from), hash, Hashlen);
if (ret)
error(EINVAL, "HMAC failed");
// 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) {
snprintf(err, sizeof(err), "invalid capability %s@%s",
from, key);
error(EINVAL, err);
}
kfree(hashstr);
hashstr = NULL;
/* if a from user is supplied, make sure it matches */
to = strchr(from, '@');
if (to == NULL) {
to = from;
} else {
*to++ = 0;
if (strcmp(from, current->user.name) != 0)
error(EINVAL, "capability must match user");
}
/* set user id */
// 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, "permission denied: capwrite");
break;
}
return n;
}
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,
};