Files
codezero/src/generic/capability.c
2009-11-04 17:19:18 +02:00

769 lines
18 KiB
C

/*
* Capability checking for all system calls
*
* Copyright (C) 2009 Bahadir Balban
*/
#include <l4/generic/resource.h>
#include <l4/generic/capability.h>
#include <l4/generic/container.h>
#include <l4/generic/cap-types.h>
#include <l4/generic/tcb.h>
#include <l4/api/capability.h>
#include <l4/api/thread.h>
#include <l4/api/errno.h>
#include <l4/lib/printk.h>
#include <l4/api/thread.h>
#include <l4/api/exregs.h>
#include <l4/api/ipc.h>
#include INC_GLUE(message.h)
void capability_init(struct capability *cap)
{
cap->capid = id_new(&kernel_resources.capability_ids);
link_init(&cap->list);
}
/*
* Boot-time function to create capability without
* capability checking
*/
struct capability *boot_capability_create(void)
{
struct capability *cap = boot_alloc_capability();
capability_init(cap);
return cap;
}
struct capability *capability_create(void)
{
struct capability *cap = alloc_capability();
capability_init(cap);
return cap;
}
#if defined(CONFIG_CAPABILITIES)
int capability_consume(struct capability *cap, int quantity)
{
if (cap->size < cap->used + quantity)
return -ENOCAP;
cap->used += quantity;
return 0;
}
int capability_free(struct capability *cap, int quantity)
{
BUG_ON((cap->used -= quantity) < 0);
return 0;
}
#else
int capability_consume(struct capability *cap, int quantity)
{
return 0;
}
int capability_free(struct capability *cap, int quantity)
{
return 0;
}
#endif
struct capability *cap_list_find_by_rtype(struct cap_list *cap_list,
unsigned int rtype)
{
struct capability *cap;
list_foreach_struct(cap, &cap_list->caps, list)
if (cap_rtype(cap) == rtype)
return cap;
return 0;
}
/*
* Find a capability from a list by its resource type
* Search all capability lists that task is allowed.
*
* FIXME:
* Tasks should not always search for a capability randomly. Consider
* mutexes, if a mutex is freed, it needs to be accounted to private
* pool first if that is not full, because freeing it into shared
* pool may lose the mutex right to another task. In other words,
* when you're freeing a mutex, we should know which capability pool
* to free it to.
*
* In conclusion freeing of pool-type capabilities need to be done
* in order of privacy.
*/
struct capability *capability_find_by_rtype(struct ktcb *task,
unsigned int rtype)
{
struct capability *cap;
/* Search task's own list */
list_foreach_struct(cap, &task->cap_list.caps, list)
if (cap_rtype(cap) == rtype)
return cap;
/* Search space list */
list_foreach_struct(cap, &task->space->cap_list.caps, list)
if (cap_rtype(cap) == rtype)
return cap;
/* Search container list */
list_foreach_struct(cap, &task->container->cap_list.caps, list)
if (cap_rtype(cap) == rtype)
return cap;
return 0;
}
typedef struct capability *(*cap_match_func_t) \
(struct capability *cap, void *match_args);
struct capability *cap_find(struct ktcb *task, cap_match_func_t cap_match_func,
void *match_args, unsigned int cap_type)
{
struct capability *cap, *found;
/* Search task's own list */
list_foreach_struct(cap, &task->cap_list.caps, list)
if (cap_type(cap) == cap_type &&
((found = cap_match_func(cap, match_args))))
return found;
/* Search space list */
list_foreach_struct(cap, &task->space->cap_list.caps, list)
if (cap_type(cap) == cap_type &&
((found = cap_match_func(cap, match_args))))
return found;
/* Search container list */
list_foreach_struct(cap, &task->container->cap_list.caps, list)
if (cap_type(cap) == cap_type &&
((found = cap_match_func(cap, match_args))))
return found;
return 0;
}
struct sys_mutex_args {
unsigned long address;
unsigned int op;
};
/*
* Check broadly the ability to do mutex ops. Check it by
* the thread, space or container, (i.e. the group that can
* do this operation broadly)
*
* Note, that we check mutex_address elsewhere as a quick,
* per-task virt_to_phys translation that would not get
* easily/quickly satisfied by a memory capability checking.
*
* While this is not %100 right from a capability checking
* point-of-view, it is a shortcut that works and makes sense.
*
* For sake of completion, the right way to do it would be to
* add MUTEX_LOCKABLE, MUTEX_UNLOCKABLE attributes to both
* virtual and physical memory caps of a task, search those
* to validate the address. But we would have to translate
* from the page tables either ways.
*/
struct capability *
cap_match_mutex(struct capability *cap, void *args)
{
/* Unconditionally expect these flags */
unsigned int perms = CAP_UMUTEX_LOCK | CAP_UMUTEX_UNLOCK;
if ((cap->access & perms) != perms)
return 0;
/* Now check the usual restype/resid pair */
switch (cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
if (current->tid != cap->resid)
return 0;
break;
case CAP_RTYPE_SPACE:
if (current->space->spid != cap->resid)
return 0;
break;
case CAP_RTYPE_CONTAINER:
if (current->container->cid != cap->resid)
return 0;
break;
default:
BUG(); /* Unknown cap type is a bug */
}
return cap;
}
struct sys_capctrl_args {
unsigned int req;
unsigned int flags;
struct ktcb *task;
};
struct capability *
cap_match_capctrl(struct capability *cap, void *args_ptr)
{
struct sys_capctrl_args *args = args_ptr;
unsigned int req = args->req;
struct ktcb *target = args->task;
/* Check operation privileges */
if (req == CAP_CONTROL_NCAPS ||
req == CAP_CONTROL_READ)
if (!(cap->access & CAP_CAP_READ))
return 0;
if (req == CAP_CONTROL_SHARE)
if (!(cap->access & CAP_CAP_SHARE))
return 0;
if (req == CAP_CONTROL_GRANT)
if (!(cap->access & CAP_CAP_GRANT))
return 0;
if (req == CAP_CONTROL_MODIFY)
if (!(cap->access & CAP_CAP_MODIFY))
return 0;
/* Now check the usual restype/resid pair */
switch (cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
if (target->tid != cap->resid)
return 0;
break;
case CAP_RTYPE_SPACE:
if (target->space->spid != cap->resid)
return 0;
break;
case CAP_RTYPE_CONTAINER:
if (target->container->cid != cap->resid)
return 0;
break;
default:
BUG(); /* Unknown cap type is a bug */
}
return cap;
}
struct sys_ipc_args {
struct ktcb *task;
unsigned int ipc_type;
unsigned int flags;
};
/*
* In an ipc, we could look for access bits, resource type and target id
*/
struct capability *
cap_match_ipc(struct capability *cap, void *args_ptr)
{
struct sys_ipc_args *args = args_ptr;
struct ktcb *target = args->task;
/* Check operation privileges */
if (args->flags & IPC_FLAGS_SHORT)
if (!(cap->access & CAP_IPC_SHORT))
return 0;
if (args->flags & IPC_FLAGS_FULL)
if (!(cap->access & CAP_IPC_FULL))
return 0;
if (args->flags & IPC_FLAGS_EXTENDED)
if (!(cap->access & CAP_IPC_EXTENDED))
return 0;
/* Assume we have both send and receive unconditionally */
if (!((cap->access & CAP_IPC_SEND) &&
(cap->access & CAP_IPC_RECV)))
return 0;
/*
* We have a target thread, check if capability match
* any resource fields in target
*/
switch (cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
if (target->tid != cap->resid)
return 0;
break;
case CAP_RTYPE_SPACE:
if (target->space->spid != cap->resid)
return 0;
break;
case CAP_RTYPE_CONTAINER:
if (target->container->cid != cap->resid)
return 0;
break;
default:
BUG(); /* Unknown cap type is a bug */
}
return cap;
}
struct sys_exregs_args {
struct exregs_data *exregs;
struct ktcb *task;
};
/*
* CAP_TYPE_EXREGS already matched upon entry
*/
struct capability *
cap_match_exregs(struct capability *cap, void *args_ptr)
{
struct sys_exregs_args *args = args_ptr;
struct exregs_data *exregs = args->exregs;
struct ktcb *target = args->task;
/* Check operation privileges */
if (exregs->valid_vect & EXREGS_VALID_REGULAR_REGS)
if (!(cap->access & CAP_EXREGS_RW_REGS))
return 0;
if (exregs->valid_vect & EXREGS_VALID_SP)
if (!(cap->access & CAP_EXREGS_RW_SP))
return 0;
if (exregs->valid_vect & EXREGS_VALID_PC)
if (!(cap->access & CAP_EXREGS_RW_PC))
return 0;
if (args->exregs->valid_vect & EXREGS_SET_UTCB)
if (!(cap->access & CAP_EXREGS_RW_UTCB))
return 0;
if (args->exregs->valid_vect & EXREGS_SET_PAGER)
if (!(cap->access & CAP_EXREGS_RW_PAGER))
return 0;
/*
* We have a target thread, check if capability
* match any resource fields in target.
*/
switch (cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
if (target->tid != cap->resid)
return 0;
break;
case CAP_RTYPE_SPACE:
if (target->space->spid != cap->resid)
return 0;
break;
case CAP_RTYPE_CONTAINER:
if (target->container->cid != cap->resid)
return 0;
break;
default:
BUG(); /* Unknown cap type is a bug */
}
return cap;
}
/*
* FIXME: Issues on capabilities:
*
* As new pagers, thread groups,
* thread ids, spaces are created, we need to
* give them thread_control capabilities dynamically,
* based on those ids!!! How do we get to do that, so that
* in userspace it looks not so difficult ???
*
* What do you match here?
*
* THREAD_CREATE:
* - TC_SAME_SPACE
* - spid -> Does thread have cap to create in that space?
* - cid -> Does thread have cap to create in that container?
* - tgid -> Does thread have cap to create in that thread group?
* - pagerid -> Does thread have cap to create in that group of paged threads?
* - TC_NEW_SPACE or TC_COPY_SPACE
* - Check cid, tgid, pagerid,
* - TC_SHARE_GROUP
* - Check tgid
* - TC_AS_PAGER
* - pagerid -> Does thread have cap to create in that group of paged threads?
* - TC_SHARE_PAGER
* - pagerid -> Does thread have cap to create in that group of paged threads?
* New group -> New set of caps, thread_control, exregs, ipc, ... all of them!
* New pager -> New set of caps for that pager.
* New thread -> New set of caps for that thread!
* New space -> New set of caps for that space! So many capabilities!
*/
struct sys_tctrl_args {
struct ktcb *task;
unsigned int flags;
struct task_ids *ids;
};
/*
* CAP_TYPE_TCTRL matched upon entry
*/
struct capability *cap_match_thread(struct capability *cap,
void *args_ptr)
{
struct sys_tctrl_args *args = args_ptr;
struct ktcb *target = args->task;
unsigned int action_flags = args->flags & THREAD_ACTION_MASK;
/* Check operation privileges */
switch (action_flags) {
case THREAD_CREATE:
if (!(cap->access & CAP_TCTRL_CREATE))
return 0;
break;
case THREAD_DESTROY:
if (!(cap->access & CAP_TCTRL_DESTROY))
return 0;
break;
case THREAD_SUSPEND:
if (!(cap->access & CAP_TCTRL_SUSPEND))
return 0;
break;
case THREAD_RUN:
if (!(cap->access & CAP_TCTRL_RUN))
return 0;
break;
case THREAD_RECYCLE:
if (!(cap->access & CAP_TCTRL_RECYCLE))
return 0;
break;
case THREAD_WAIT:
if (!(cap->access & CAP_TCTRL_WAIT))
return 0;
break;
default:
/* We refuse to accept anything else */
return 0;
}
/* If no target and create, or vice versa, it really is a bug */
BUG_ON(!target && action_flags != THREAD_CREATE);
BUG_ON(target && action_flags == THREAD_CREATE);
if (action_flags == THREAD_CREATE) {
/*
* TODO: Add cid to task_ids arg.
*
* Its a thread create and we have no knowledge of
* thread id, space id, or any other id.
*
* We _assume_ target is the largest group,
* e.g. same container as current. We check
* for `container' as target in capability
*/
if (cap_rtype(cap) != CAP_RTYPE_CONTAINER)
return 0;
if (cap->resid != current->container->cid)
return 0;
/* Resource type and id match, success */
return cap;
}
/*
* We have a target thread, check if capability match
* any resource fields in target
*/
switch (cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
if (target->tid != cap->resid)
return 0;
break;
case CAP_RTYPE_SPACE:
if (target->space->spid != cap->resid)
return 0;
break;
case CAP_RTYPE_CONTAINER:
if (target->container->cid != cap->resid)
return 0;
break;
default:
BUG(); /* Unknown cap type is a bug */
}
return cap;
}
struct sys_map_args {
struct ktcb *task;
unsigned long phys;
unsigned long virt;
unsigned long npages;
unsigned int flags;
};
/*
* CAP_TYPE_MAP already matched upon entry
*/
struct capability *cap_match_mem(struct capability *cap,
void *args_ptr)
{
struct sys_map_args *args = args_ptr;
struct ktcb *target = args->task;
unsigned long pfn;
unsigned int perms;
/* Set base according to what type of mem type we're matching */
if (cap_type(cap) == CAP_TYPE_MAP_PHYSMEM)
pfn = __pfn(args->phys);
else
pfn = __pfn(args->virt);
/* Check range */
if (cap->start > pfn || cap->end < pfn + args->npages)
return 0;
/* Check permissions */
switch (args->flags) {
case MAP_USR_RW_FLAGS:
perms = CAP_MAP_READ | CAP_MAP_WRITE | CAP_MAP_CACHED;
if ((cap->access & perms) != perms)
return 0;
break;
case MAP_USR_RO_FLAGS:
perms = CAP_MAP_READ | CAP_MAP_CACHED;
if ((cap->access & perms) != perms)
return 0;
break;
case MAP_USR_IO_FLAGS:
perms = CAP_MAP_READ | CAP_MAP_WRITE | CAP_MAP_UNCACHED;
if ((cap->access & perms) != perms)
return 0;
break;
default:
/* Anything else is an invalid/unrecognised argument */
return 0;
}
/*
* We have a target thread, check if capability match
* any resource fields in target
*/
switch (cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
if (target->tid != cap->resid)
return 0;
break;
case CAP_RTYPE_SPACE:
if (target->space->spid != cap->resid)
return 0;
break;
case CAP_RTYPE_CONTAINER:
if (target->container->cid != cap->resid)
return 0;
break;
default:
BUG(); /* Unknown cap type is a bug */
}
return cap;
}
#if defined(CONFIG_CAPABILITIES)
int cap_mutex_check(unsigned long mutex_address, int mutex_op)
{
struct sys_mutex_args args = {
.address = mutex_address,
.op = mutex_op,
};
if (!(cap_find(current, cap_match_mutex,
&args, CAP_TYPE_UMUTEX)))
return -ENOCAP;
return 0;
}
int cap_cap_check(struct ktcb *task, unsigned int req, unsigned int flags)
{
struct sys_capctrl_args args = {
.req = req,
.flags = flags,
.task = task,
};
if (!(cap_find(current, cap_match_capctrl,
&args, CAP_TYPE_CAP)))
return -ENOCAP;
return 0;
}
int cap_map_check(struct ktcb *target, unsigned long phys, unsigned long virt,
unsigned long npages, unsigned int flags)
{
struct capability *physmem, *virtmem;
struct sys_map_args args = {
.task = target,
.phys = phys,
.virt = virt,
.npages = npages,
.flags = flags,
};
if (!(physmem = cap_find(current, cap_match_mem,
&args, CAP_TYPE_MAP_PHYSMEM)))
return -ENOCAP;
if (!(virtmem = cap_find(current, cap_match_mem,
&args, CAP_TYPE_MAP_VIRTMEM)))
return -ENOCAP;
return 0;
}
/*
* Limitation: We currently only check from sender's
* perspective. This is because sender always targets a
* real thread. Does sender have the right to do this ipc?
*/
int cap_ipc_check(l4id_t to, l4id_t from,
unsigned int flags, unsigned int ipc_type)
{
struct ktcb *target;
struct sys_ipc_args args;
/* Receivers can get away from us (for now) */
if (ipc_type != IPC_SEND && ipc_type != IPC_SENDRECV)
return 0;
/*
* We're the sender, meaning we have
* a real target
*/
if (!(target = tcb_find(to)))
return -ESRCH;
/* Set up other args */
args.flags = flags;
args.ipc_type = ipc_type;
args.task = target;
if (!(cap_find(current, cap_match_ipc,
&args, CAP_TYPE_IPC)))
return -ENOCAP;
return 0;
}
int cap_exregs_check(struct ktcb *task, struct exregs_data *exregs)
{
struct sys_exregs_args args = {
.exregs = exregs,
.task = task,
};
/* We always search for current's caps */
if (!(cap_find(current, cap_match_exregs,
&args, CAP_TYPE_EXREGS)))
return -ENOCAP;
return 0;
}
int cap_thread_check(struct ktcb *task,
unsigned int flags,
struct task_ids *ids)
{
struct sys_tctrl_args args = {
.task = task,
.flags = flags,
.ids = ids,
};
if (!(cap_find(current, cap_match_thread,
&args, CAP_TYPE_TCTRL)))
return -ENOCAP;
return 0;
}
#else /* Meaning !CONFIG_CAPABILITIES */
int cap_mutex_check(unsigned long mutex_address, int mutex_op)
{
return 0;
}
int cap_cap_check(struct ktcb *task, unsigned int req, unsigned int flags)
{
return 0;
}
int cap_ipc_check(l4id_t to, l4id_t from,
unsigned int flags, unsigned int ipc_type)
{
return 0;
}
int cap_map_check(struct ktcb *task, unsigned long phys, unsigned long virt,
unsigned long npages, unsigned int flags)
{
return 0;
}
int cap_exregs_check(struct ktcb *task, struct exregs_data *exregs)
{
return 0;
}
int cap_thread_check(struct ktcb *task,
unsigned int flags,
struct task_ids *ids)
{
return 0;
}
#endif /* End of !CONFIG_CAPABILITIES */
/*
* FIXME:
*
* Capability resource ids
*
* Currently the kernel cinfo has no resource id field. This is
* because resources are dynamically assigned an id at run-time.
*
* However, this prevents the user to specify capabilities on
* particular resources since resources aren't id-able at
* configuration time. There's also no straightforward way to
* determine which id was meant at run-time.
*
* As a solution, resources that are id-able and that are targeted
* by capabilities at configuration time should be assigned static
* ids at configuration time. Any other id-able resources that are
* subject to capabilities by run-time sharing/delegation/derivation
* etc. should be assigned their ids dynamically.
*
* Example:
*
* A capability to ipc to a particular container.
*
* When this capability is defined at configuration time, it is only
* meaningful if the container id is also supplied statically.
*
* If a pager later on generates a capability to ipc to a thread
* that it created at run-time, and grant it to another container's
* pager, (say a container thread would like to talk to another
* container's thread) it should assign the dynamically assigned
* target thread id as the capability resource id and hand it over.
*/
int capability_set_resource_id(struct capability *cap)
{
/* Identifiable resources */
switch(cap_rtype(cap)) {
case CAP_RTYPE_THREAD:
case CAP_RTYPE_TGROUP:
case CAP_RTYPE_SPACE:
case CAP_RTYPE_CONTAINER:
break;
}
return 0;
}