mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-01-30 23:02:18 +00:00
1783 lines
48 KiB
C
1783 lines
48 KiB
C
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/types.h>
|
|
#include <linux/sysctl.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/gracl.h>
|
|
#include <linux/gralloc.h>
|
|
#include <linux/security.h>
|
|
#include <linux/grinternal.h>
|
|
#include <linux/pid_namespace.h>
|
|
#include <linux/stop_machine.h>
|
|
#include <linux/fdtable.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/posix-timers.h>
|
|
#include "../fs/mount.h"
|
|
|
|
#include <asm/uaccess.h>
|
|
#include <asm/errno.h>
|
|
#include <asm/mman.h>
|
|
|
|
extern struct gr_policy_state *polstate;
|
|
|
|
#define FOR_EACH_ROLE_START(role) \
|
|
role = polstate->role_list; \
|
|
while (role) {
|
|
|
|
#define FOR_EACH_ROLE_END(role) \
|
|
role = role->prev; \
|
|
}
|
|
|
|
struct path gr_real_root;
|
|
|
|
extern struct gr_alloc_state *current_alloc_state;
|
|
|
|
u16 acl_sp_role_value;
|
|
|
|
static DEFINE_MUTEX(gr_dev_mutex);
|
|
|
|
extern int chkpw(struct gr_arg *entry, unsigned char *salt, unsigned char *sum);
|
|
extern void gr_clear_learn_entries(void);
|
|
|
|
struct gr_arg *gr_usermode __read_only;
|
|
unsigned char *gr_system_salt __read_only;
|
|
unsigned char *gr_system_sum __read_only;
|
|
|
|
static unsigned int gr_auth_attempts = 0;
|
|
static unsigned long gr_auth_expires = 0UL;
|
|
|
|
struct acl_object_label *fakefs_obj_rw;
|
|
struct acl_object_label *fakefs_obj_rwx;
|
|
|
|
extern int gr_init_uidset(void);
|
|
extern void gr_free_uidset(void);
|
|
extern int gr_find_and_remove_uid(uid_t uid);
|
|
|
|
extern struct acl_subject_label *__gr_get_subject_for_task(const struct gr_policy_state *state, struct task_struct *task, const char *filename, int fallback);
|
|
extern void __gr_apply_subject_to_task(const struct gr_policy_state *state, struct task_struct *task, struct acl_subject_label *subj);
|
|
extern int gr_streq(const char *a, const char *b, const unsigned int lena, const unsigned int lenb);
|
|
extern void __insert_inodev_entry(const struct gr_policy_state *state, struct inodev_entry *entry);
|
|
extern struct acl_role_label *__lookup_acl_role_label(const struct gr_policy_state *state, const struct task_struct *task, const uid_t uid, const gid_t gid);
|
|
extern void insert_acl_obj_label(struct acl_object_label *obj, struct acl_subject_label *subj);
|
|
extern void insert_acl_subj_label(struct acl_subject_label *obj, struct acl_role_label *role);
|
|
extern struct name_entry * __lookup_name_entry(const struct gr_policy_state *state, const char *name);
|
|
extern char *gr_to_filename_rbac(const struct dentry *dentry, const struct vfsmount *mnt);
|
|
extern struct acl_subject_label *lookup_acl_subj_label(const u64 ino, const dev_t dev, const struct acl_role_label *role);
|
|
extern struct acl_subject_label *lookup_acl_subj_label_deleted(const u64 ino, const dev_t dev, const struct acl_role_label *role);
|
|
extern void assign_special_role(const char *rolename);
|
|
extern struct acl_subject_label *chk_subj_label(const struct dentry *l_dentry, const struct vfsmount *l_mnt, const struct acl_role_label *role);
|
|
extern int gr_rbac_disable(void *unused);
|
|
extern void gr_enable_rbac_system(void);
|
|
|
|
static int copy_acl_object_label_normal(struct acl_object_label *obj, const struct acl_object_label *userp)
|
|
{
|
|
if (copy_from_user(obj, userp, sizeof(struct acl_object_label)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_acl_ip_label_normal(struct acl_ip_label *ip, const struct acl_ip_label *userp)
|
|
{
|
|
if (copy_from_user(ip, userp, sizeof(struct acl_ip_label)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_acl_subject_label_normal(struct acl_subject_label *subj, const struct acl_subject_label *userp)
|
|
{
|
|
if (copy_from_user(subj, userp, sizeof(struct acl_subject_label)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_acl_role_label_normal(struct acl_role_label *role, const struct acl_role_label *userp)
|
|
{
|
|
if (copy_from_user(role, userp, sizeof(struct acl_role_label)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_role_allowed_ip_normal(struct role_allowed_ip *roleip, const struct role_allowed_ip *userp)
|
|
{
|
|
if (copy_from_user(roleip, userp, sizeof(struct role_allowed_ip)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_sprole_pw_normal(struct sprole_pw *pw, unsigned long idx, const struct sprole_pw *userp)
|
|
{
|
|
if (copy_from_user(pw, userp + idx, sizeof(struct sprole_pw)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_gr_hash_struct_normal(struct gr_hash_struct *hash, const struct gr_hash_struct *userp)
|
|
{
|
|
if (copy_from_user(hash, userp, sizeof(struct gr_hash_struct)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_role_transition_normal(struct role_transition *trans, const struct role_transition *userp)
|
|
{
|
|
if (copy_from_user(trans, userp, sizeof(struct role_transition)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int copy_pointer_from_array_normal(void *ptr, unsigned long idx, const void *userp)
|
|
{
|
|
if (copy_from_user(ptr, userp + (idx * sizeof(void *)), sizeof(void *)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_gr_arg_wrapper_normal(const char __user *buf, struct gr_arg_wrapper *uwrap)
|
|
{
|
|
if (copy_from_user(uwrap, buf, sizeof (struct gr_arg_wrapper)))
|
|
return -EFAULT;
|
|
|
|
if ((uwrap->version != GRSECURITY_VERSION) ||
|
|
(uwrap->size != sizeof(struct gr_arg)))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int copy_gr_arg_normal(const struct gr_arg __user *buf, struct gr_arg *arg)
|
|
{
|
|
if (copy_from_user(arg, buf, sizeof (struct gr_arg)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static size_t get_gr_arg_wrapper_size_normal(void)
|
|
{
|
|
return sizeof(struct gr_arg_wrapper);
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
extern int copy_gr_arg_wrapper_compat(const char *buf, struct gr_arg_wrapper *uwrap);
|
|
extern int copy_gr_arg_compat(const struct gr_arg __user *buf, struct gr_arg *arg);
|
|
extern int copy_acl_object_label_compat(struct acl_object_label *obj, const struct acl_object_label *userp);
|
|
extern int copy_acl_subject_label_compat(struct acl_subject_label *subj, const struct acl_subject_label *userp);
|
|
extern int copy_acl_role_label_compat(struct acl_role_label *role, const struct acl_role_label *userp);
|
|
extern int copy_role_allowed_ip_compat(struct role_allowed_ip *roleip, const struct role_allowed_ip *userp);
|
|
extern int copy_role_transition_compat(struct role_transition *trans, const struct role_transition *userp);
|
|
extern int copy_gr_hash_struct_compat(struct gr_hash_struct *hash, const struct gr_hash_struct *userp);
|
|
extern int copy_pointer_from_array_compat(void *ptr, unsigned long idx, const void *userp);
|
|
extern int copy_acl_ip_label_compat(struct acl_ip_label *ip, const struct acl_ip_label *userp);
|
|
extern int copy_sprole_pw_compat(struct sprole_pw *pw, unsigned long idx, const struct sprole_pw *userp);
|
|
extern size_t get_gr_arg_wrapper_size_compat(void);
|
|
|
|
int (* copy_gr_arg_wrapper)(const char *buf, struct gr_arg_wrapper *uwrap) __read_only;
|
|
int (* copy_gr_arg)(const struct gr_arg *buf, struct gr_arg *arg) __read_only;
|
|
int (* copy_acl_object_label)(struct acl_object_label *obj, const struct acl_object_label *userp) __read_only;
|
|
int (* copy_acl_subject_label)(struct acl_subject_label *subj, const struct acl_subject_label *userp) __read_only;
|
|
int (* copy_acl_role_label)(struct acl_role_label *role, const struct acl_role_label *userp) __read_only;
|
|
int (* copy_acl_ip_label)(struct acl_ip_label *ip, const struct acl_ip_label *userp) __read_only;
|
|
int (* copy_pointer_from_array)(void *ptr, unsigned long idx, const void *userp) __read_only;
|
|
int (* copy_sprole_pw)(struct sprole_pw *pw, unsigned long idx, const struct sprole_pw *userp) __read_only;
|
|
int (* copy_gr_hash_struct)(struct gr_hash_struct *hash, const struct gr_hash_struct *userp) __read_only;
|
|
int (* copy_role_transition)(struct role_transition *trans, const struct role_transition *userp) __read_only;
|
|
int (* copy_role_allowed_ip)(struct role_allowed_ip *roleip, const struct role_allowed_ip *userp) __read_only;
|
|
size_t (* get_gr_arg_wrapper_size)(void) __read_only;
|
|
|
|
#else
|
|
#define copy_gr_arg_wrapper copy_gr_arg_wrapper_normal
|
|
#define copy_gr_arg copy_gr_arg_normal
|
|
#define copy_gr_hash_struct copy_gr_hash_struct_normal
|
|
#define copy_acl_object_label copy_acl_object_label_normal
|
|
#define copy_acl_subject_label copy_acl_subject_label_normal
|
|
#define copy_acl_role_label copy_acl_role_label_normal
|
|
#define copy_acl_ip_label copy_acl_ip_label_normal
|
|
#define copy_pointer_from_array copy_pointer_from_array_normal
|
|
#define copy_sprole_pw copy_sprole_pw_normal
|
|
#define copy_role_transition copy_role_transition_normal
|
|
#define copy_role_allowed_ip copy_role_allowed_ip_normal
|
|
#define get_gr_arg_wrapper_size get_gr_arg_wrapper_size_normal
|
|
#endif
|
|
|
|
static struct acl_subject_label *
|
|
lookup_subject_map(const struct acl_subject_label *userp)
|
|
{
|
|
unsigned int index = gr_shash(userp, polstate->subj_map_set.s_size);
|
|
struct subject_map *match;
|
|
|
|
match = polstate->subj_map_set.s_hash[index];
|
|
|
|
while (match && match->user != userp)
|
|
match = match->next;
|
|
|
|
if (match != NULL)
|
|
return match->kernel;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
insert_subj_map_entry(struct subject_map *subjmap)
|
|
{
|
|
unsigned int index = gr_shash(subjmap->user, polstate->subj_map_set.s_size);
|
|
struct subject_map **curr;
|
|
|
|
subjmap->prev = NULL;
|
|
|
|
curr = &polstate->subj_map_set.s_hash[index];
|
|
if (*curr != NULL)
|
|
(*curr)->prev = subjmap;
|
|
|
|
subjmap->next = *curr;
|
|
*curr = subjmap;
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
__insert_acl_role_label(struct acl_role_label *role, uid_t uidgid)
|
|
{
|
|
unsigned int index =
|
|
gr_rhash(uidgid, role->roletype & (GR_ROLE_USER | GR_ROLE_GROUP), polstate->acl_role_set.r_size);
|
|
struct acl_role_label **curr;
|
|
struct acl_role_label *tmp, *tmp2;
|
|
|
|
curr = &polstate->acl_role_set.r_hash[index];
|
|
|
|
/* simple case, slot is empty, just set it to our role */
|
|
if (*curr == NULL) {
|
|
*curr = role;
|
|
} else {
|
|
/* example:
|
|
1 -> 2 -> 3 (adding 2 -> 3 to here)
|
|
2 -> 3
|
|
*/
|
|
/* first check to see if we can already be reached via this slot */
|
|
tmp = *curr;
|
|
while (tmp && tmp != role)
|
|
tmp = tmp->next;
|
|
if (tmp == role) {
|
|
/* we don't need to add ourselves to this slot's chain */
|
|
return;
|
|
}
|
|
/* we need to add ourselves to this chain, two cases */
|
|
if (role->next == NULL) {
|
|
/* simple case, append the current chain to our role */
|
|
role->next = *curr;
|
|
*curr = role;
|
|
} else {
|
|
/* 1 -> 2 -> 3 -> 4
|
|
2 -> 3 -> 4
|
|
3 -> 4 (adding 1 -> 2 -> 3 -> 4 to here)
|
|
*/
|
|
/* trickier case: walk our role's chain until we find
|
|
the role for the start of the current slot's chain */
|
|
tmp = role;
|
|
tmp2 = *curr;
|
|
while (tmp->next && tmp->next != tmp2)
|
|
tmp = tmp->next;
|
|
if (tmp->next == tmp2) {
|
|
/* from example above, we found 3, so just
|
|
replace this slot's chain with ours */
|
|
*curr = role;
|
|
} else {
|
|
/* we didn't find a subset of our role's chain
|
|
in the current slot's chain, so append their
|
|
chain to ours, and set us as the first role in
|
|
the slot's chain
|
|
|
|
we could fold this case with the case above,
|
|
but making it explicit for clarity
|
|
*/
|
|
tmp->next = tmp2;
|
|
*curr = role;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
insert_acl_role_label(struct acl_role_label *role)
|
|
{
|
|
int i;
|
|
|
|
if (polstate->role_list == NULL) {
|
|
polstate->role_list = role;
|
|
role->prev = NULL;
|
|
} else {
|
|
role->prev = polstate->role_list;
|
|
polstate->role_list = role;
|
|
}
|
|
|
|
/* used for hash chains */
|
|
role->next = NULL;
|
|
|
|
if (role->roletype & GR_ROLE_DOMAIN) {
|
|
for (i = 0; i < role->domain_child_num; i++)
|
|
__insert_acl_role_label(role, role->domain_children[i]);
|
|
} else
|
|
__insert_acl_role_label(role, role->uidgid);
|
|
}
|
|
|
|
static int
|
|
insert_name_entry(char *name, const u64 inode, const dev_t device, __u8 deleted)
|
|
{
|
|
struct name_entry **curr, *nentry;
|
|
struct inodev_entry *ientry;
|
|
unsigned int len = strlen(name);
|
|
unsigned int key = full_name_hash(NULL, (const unsigned char *)name, len);
|
|
unsigned int index = key % polstate->name_set.n_size;
|
|
|
|
curr = &polstate->name_set.n_hash[index];
|
|
|
|
while (*curr && ((*curr)->key != key || !gr_streq((*curr)->name, name, (*curr)->len, len)))
|
|
curr = &((*curr)->next);
|
|
|
|
if (*curr != NULL)
|
|
return 1;
|
|
|
|
nentry = acl_alloc(sizeof (struct name_entry));
|
|
if (nentry == NULL)
|
|
return 0;
|
|
ientry = acl_alloc(sizeof (struct inodev_entry));
|
|
if (ientry == NULL)
|
|
return 0;
|
|
ientry->nentry = nentry;
|
|
|
|
nentry->key = key;
|
|
nentry->name = name;
|
|
nentry->inode = inode;
|
|
nentry->device = device;
|
|
nentry->len = len;
|
|
nentry->deleted = deleted;
|
|
|
|
nentry->prev = NULL;
|
|
curr = &polstate->name_set.n_hash[index];
|
|
if (*curr != NULL)
|
|
(*curr)->prev = nentry;
|
|
nentry->next = *curr;
|
|
*curr = nentry;
|
|
|
|
/* insert us into the table searchable by inode/dev */
|
|
__insert_inodev_entry(polstate, ientry);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* allocating chained hash tables, so optimal size is where lambda ~ 1 */
|
|
|
|
static void *
|
|
create_table(__u32 * len, int elementsize)
|
|
{
|
|
unsigned int table_sizes[] = {
|
|
7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381,
|
|
32749, 65521, 131071, 262139, 524287, 1048573, 2097143,
|
|
4194301, 8388593, 16777213, 33554393, 67108859
|
|
};
|
|
void *newtable = NULL;
|
|
unsigned int pwr = 0;
|
|
|
|
while ((pwr < ((sizeof (table_sizes) / sizeof (table_sizes[0])) - 1)) &&
|
|
table_sizes[pwr] <= *len)
|
|
pwr++;
|
|
|
|
if (table_sizes[pwr] <= *len || (table_sizes[pwr] > ULONG_MAX / elementsize))
|
|
return newtable;
|
|
|
|
if ((table_sizes[pwr] * elementsize) <= PAGE_SIZE)
|
|
newtable =
|
|
kmalloc(table_sizes[pwr] * elementsize, GFP_KERNEL);
|
|
else
|
|
newtable = vmalloc(table_sizes[pwr] * elementsize);
|
|
|
|
*len = table_sizes[pwr];
|
|
|
|
return newtable;
|
|
}
|
|
|
|
static int
|
|
init_variables(const struct gr_arg *arg, bool reload)
|
|
{
|
|
struct task_struct *reaper = init_pid_ns.child_reaper;
|
|
unsigned int stacksize;
|
|
|
|
polstate->subj_map_set.s_size = arg->role_db.num_subjects;
|
|
polstate->acl_role_set.r_size = arg->role_db.num_roles + arg->role_db.num_domain_children;
|
|
polstate->name_set.n_size = arg->role_db.num_objects;
|
|
polstate->inodev_set.i_size = arg->role_db.num_objects;
|
|
|
|
if (!polstate->subj_map_set.s_size || !polstate->acl_role_set.r_size ||
|
|
!polstate->name_set.n_size || !polstate->inodev_set.i_size)
|
|
return 1;
|
|
|
|
if (!reload) {
|
|
if (!gr_init_uidset())
|
|
return 1;
|
|
}
|
|
|
|
/* set up the stack that holds allocation info */
|
|
|
|
stacksize = arg->role_db.num_pointers + 5;
|
|
|
|
if (!acl_alloc_stack_init(stacksize))
|
|
return 1;
|
|
|
|
if (!reload) {
|
|
/* grab reference for the real root dentry and vfsmount */
|
|
get_fs_root(reaper->fs, &gr_real_root);
|
|
|
|
#ifdef CONFIG_GRKERNSEC_RBAC_DEBUG
|
|
printk(KERN_ALERT "Obtained real root device=%d, inode=%lu\n", gr_get_dev_from_dentry(gr_real_root.dentry), gr_get_ino_from_dentry(gr_real_root.dentry));
|
|
#endif
|
|
|
|
fakefs_obj_rw = kzalloc(sizeof(struct acl_object_label), GFP_KERNEL);
|
|
if (fakefs_obj_rw == NULL)
|
|
return 1;
|
|
fakefs_obj_rw->mode = GR_FIND | GR_READ | GR_WRITE;
|
|
|
|
fakefs_obj_rwx = kzalloc(sizeof(struct acl_object_label), GFP_KERNEL);
|
|
if (fakefs_obj_rwx == NULL)
|
|
return 1;
|
|
fakefs_obj_rwx->mode = GR_FIND | GR_READ | GR_WRITE | GR_EXEC;
|
|
}
|
|
|
|
polstate->subj_map_set.s_hash =
|
|
(struct subject_map **) create_table(&polstate->subj_map_set.s_size, sizeof(void *));
|
|
polstate->acl_role_set.r_hash =
|
|
(struct acl_role_label **) create_table(&polstate->acl_role_set.r_size, sizeof(void *));
|
|
polstate->name_set.n_hash = (struct name_entry **) create_table(&polstate->name_set.n_size, sizeof(void *));
|
|
polstate->inodev_set.i_hash =
|
|
(struct inodev_entry **) create_table(&polstate->inodev_set.i_size, sizeof(void *));
|
|
|
|
if (!polstate->subj_map_set.s_hash || !polstate->acl_role_set.r_hash ||
|
|
!polstate->name_set.n_hash || !polstate->inodev_set.i_hash)
|
|
return 1;
|
|
|
|
memset(polstate->subj_map_set.s_hash, 0,
|
|
sizeof(struct subject_map *) * polstate->subj_map_set.s_size);
|
|
memset(polstate->acl_role_set.r_hash, 0,
|
|
sizeof (struct acl_role_label *) * polstate->acl_role_set.r_size);
|
|
memset(polstate->name_set.n_hash, 0,
|
|
sizeof (struct name_entry *) * polstate->name_set.n_size);
|
|
memset(polstate->inodev_set.i_hash, 0,
|
|
sizeof (struct inodev_entry *) * polstate->inodev_set.i_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* free information not needed after startup
|
|
currently contains user->kernel pointer mappings for subjects
|
|
*/
|
|
|
|
static void
|
|
free_init_variables(void)
|
|
{
|
|
__u32 i;
|
|
|
|
if (polstate->subj_map_set.s_hash) {
|
|
for (i = 0; i < polstate->subj_map_set.s_size; i++) {
|
|
if (polstate->subj_map_set.s_hash[i]) {
|
|
kfree(polstate->subj_map_set.s_hash[i]);
|
|
polstate->subj_map_set.s_hash[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if ((polstate->subj_map_set.s_size * sizeof (struct subject_map *)) <=
|
|
PAGE_SIZE)
|
|
kfree(polstate->subj_map_set.s_hash);
|
|
else
|
|
vfree(polstate->subj_map_set.s_hash);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
free_variables(bool reload)
|
|
{
|
|
struct acl_subject_label *s;
|
|
struct acl_role_label *r;
|
|
struct task_struct *task, *task2;
|
|
unsigned int x;
|
|
|
|
if (!reload) {
|
|
gr_clear_learn_entries();
|
|
|
|
read_lock(&tasklist_lock);
|
|
do_each_thread(task2, task) {
|
|
task->acl_sp_role = 0;
|
|
task->acl_role_id = 0;
|
|
task->inherited = 0;
|
|
task->acl = NULL;
|
|
task->role = NULL;
|
|
} while_each_thread(task2, task);
|
|
read_unlock(&tasklist_lock);
|
|
|
|
kfree(fakefs_obj_rw);
|
|
fakefs_obj_rw = NULL;
|
|
kfree(fakefs_obj_rwx);
|
|
fakefs_obj_rwx = NULL;
|
|
|
|
/* release the reference to the real root dentry and vfsmount */
|
|
path_put(&gr_real_root);
|
|
memset(&gr_real_root, 0, sizeof(gr_real_root));
|
|
}
|
|
|
|
/* free all object hash tables */
|
|
|
|
FOR_EACH_ROLE_START(r)
|
|
if (r->subj_hash == NULL)
|
|
goto next_role;
|
|
FOR_EACH_SUBJECT_START(r, s, x)
|
|
if (s->obj_hash == NULL)
|
|
break;
|
|
if ((s->obj_hash_size * sizeof (struct acl_object_label *)) <= PAGE_SIZE)
|
|
kfree(s->obj_hash);
|
|
else
|
|
vfree(s->obj_hash);
|
|
FOR_EACH_SUBJECT_END(s, x)
|
|
FOR_EACH_NESTED_SUBJECT_START(r, s)
|
|
if (s->obj_hash == NULL)
|
|
break;
|
|
if ((s->obj_hash_size * sizeof (struct acl_object_label *)) <= PAGE_SIZE)
|
|
kfree(s->obj_hash);
|
|
else
|
|
vfree(s->obj_hash);
|
|
FOR_EACH_NESTED_SUBJECT_END(s)
|
|
if ((r->subj_hash_size * sizeof (struct acl_subject_label *)) <= PAGE_SIZE)
|
|
kfree(r->subj_hash);
|
|
else
|
|
vfree(r->subj_hash);
|
|
r->subj_hash = NULL;
|
|
next_role:
|
|
FOR_EACH_ROLE_END(r)
|
|
|
|
acl_free_all();
|
|
|
|
if (polstate->acl_role_set.r_hash) {
|
|
if ((polstate->acl_role_set.r_size * sizeof (struct acl_role_label *)) <=
|
|
PAGE_SIZE)
|
|
kfree(polstate->acl_role_set.r_hash);
|
|
else
|
|
vfree(polstate->acl_role_set.r_hash);
|
|
}
|
|
if (polstate->name_set.n_hash) {
|
|
if ((polstate->name_set.n_size * sizeof (struct name_entry *)) <=
|
|
PAGE_SIZE)
|
|
kfree(polstate->name_set.n_hash);
|
|
else
|
|
vfree(polstate->name_set.n_hash);
|
|
}
|
|
|
|
if (polstate->inodev_set.i_hash) {
|
|
if ((polstate->inodev_set.i_size * sizeof (struct inodev_entry *)) <=
|
|
PAGE_SIZE)
|
|
kfree(polstate->inodev_set.i_hash);
|
|
else
|
|
vfree(polstate->inodev_set.i_hash);
|
|
}
|
|
|
|
if (!reload)
|
|
gr_free_uidset();
|
|
|
|
memset(&polstate->name_set, 0, sizeof (struct name_db));
|
|
memset(&polstate->inodev_set, 0, sizeof (struct inodev_db));
|
|
memset(&polstate->acl_role_set, 0, sizeof (struct acl_role_db));
|
|
memset(&polstate->subj_map_set, 0, sizeof (struct acl_subj_map_db));
|
|
|
|
polstate->default_role = NULL;
|
|
polstate->kernel_role = NULL;
|
|
polstate->role_list = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
static struct acl_subject_label *
|
|
do_copy_user_subj(struct acl_subject_label *userp, struct acl_role_label *role, int *already_copied);
|
|
|
|
static int alloc_and_copy_string(char **name, unsigned int maxlen)
|
|
{
|
|
unsigned int len = strnlen_user(*name, maxlen);
|
|
char *tmp;
|
|
|
|
if (!len || len >= maxlen)
|
|
return -EINVAL;
|
|
|
|
if ((tmp = (char *) acl_alloc(len)) == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(tmp, *name, len))
|
|
return -EFAULT;
|
|
|
|
tmp[len-1] = '\0';
|
|
*name = tmp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
copy_user_glob(struct acl_object_label *obj)
|
|
{
|
|
struct acl_object_label *g_tmp, **guser;
|
|
int error;
|
|
|
|
if (obj->globbed == NULL)
|
|
return 0;
|
|
|
|
guser = &obj->globbed;
|
|
while (*guser) {
|
|
g_tmp = (struct acl_object_label *)
|
|
acl_alloc(sizeof (struct acl_object_label));
|
|
if (g_tmp == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_acl_object_label(g_tmp, *guser))
|
|
return -EFAULT;
|
|
|
|
error = alloc_and_copy_string(&g_tmp->filename, PATH_MAX);
|
|
if (error)
|
|
return error;
|
|
|
|
*guser = g_tmp;
|
|
guser = &(g_tmp->next);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
copy_user_objs(struct acl_object_label *userp, struct acl_subject_label *subj,
|
|
struct acl_role_label *role)
|
|
{
|
|
struct acl_object_label *o_tmp;
|
|
int ret;
|
|
|
|
while (userp) {
|
|
if ((o_tmp = (struct acl_object_label *)
|
|
acl_alloc(sizeof (struct acl_object_label))) == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_acl_object_label(o_tmp, userp))
|
|
return -EFAULT;
|
|
|
|
userp = o_tmp->prev;
|
|
|
|
ret = alloc_and_copy_string(&o_tmp->filename, PATH_MAX);
|
|
if (ret)
|
|
return ret;
|
|
|
|
insert_acl_obj_label(o_tmp, subj);
|
|
if (!insert_name_entry(o_tmp->filename, o_tmp->inode,
|
|
o_tmp->device, (o_tmp->mode & GR_DELETED) ? 1 : 0))
|
|
return -ENOMEM;
|
|
|
|
ret = copy_user_glob(o_tmp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (o_tmp->nested) {
|
|
int already_copied;
|
|
|
|
o_tmp->nested = do_copy_user_subj(o_tmp->nested, role, &already_copied);
|
|
if (IS_ERR(o_tmp->nested))
|
|
return PTR_ERR(o_tmp->nested);
|
|
|
|
/* insert into nested subject list if we haven't copied this one yet
|
|
to prevent duplicate entries */
|
|
if (!already_copied) {
|
|
o_tmp->nested->next = role->hash->first;
|
|
role->hash->first = o_tmp->nested;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __u32
|
|
count_user_subjs(struct acl_subject_label *userp)
|
|
{
|
|
struct acl_subject_label s_tmp;
|
|
__u32 num = 0;
|
|
|
|
while (userp) {
|
|
if (copy_acl_subject_label(&s_tmp, userp))
|
|
break;
|
|
|
|
userp = s_tmp.prev;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static int
|
|
copy_user_allowedips(struct acl_role_label *rolep)
|
|
{
|
|
struct role_allowed_ip *ruserip, *rtmp = NULL, *rlast;
|
|
|
|
ruserip = rolep->allowed_ips;
|
|
|
|
while (ruserip) {
|
|
rlast = rtmp;
|
|
|
|
if ((rtmp = (struct role_allowed_ip *)
|
|
acl_alloc(sizeof (struct role_allowed_ip))) == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_role_allowed_ip(rtmp, ruserip))
|
|
return -EFAULT;
|
|
|
|
ruserip = rtmp->prev;
|
|
|
|
if (!rlast) {
|
|
rtmp->prev = NULL;
|
|
rolep->allowed_ips = rtmp;
|
|
} else {
|
|
rlast->next = rtmp;
|
|
rtmp->prev = rlast;
|
|
}
|
|
|
|
if (!ruserip)
|
|
rtmp->next = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
copy_user_transitions(struct acl_role_label *rolep)
|
|
{
|
|
struct role_transition *rusertp, *rtmp = NULL, *rlast;
|
|
int error;
|
|
|
|
rusertp = rolep->transitions;
|
|
|
|
while (rusertp) {
|
|
rlast = rtmp;
|
|
|
|
if ((rtmp = (struct role_transition *)
|
|
acl_alloc(sizeof (struct role_transition))) == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_role_transition(rtmp, rusertp))
|
|
return -EFAULT;
|
|
|
|
rusertp = rtmp->prev;
|
|
|
|
error = alloc_and_copy_string(&rtmp->rolename, GR_SPROLE_LEN);
|
|
if (error)
|
|
return error;
|
|
|
|
if (!rlast) {
|
|
rtmp->prev = NULL;
|
|
rolep->transitions = rtmp;
|
|
} else {
|
|
rlast->next = rtmp;
|
|
rtmp->prev = rlast;
|
|
}
|
|
|
|
if (!rusertp)
|
|
rtmp->next = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __u32 count_user_objs(const struct acl_object_label __user *userp)
|
|
{
|
|
struct acl_object_label o_tmp;
|
|
__u32 num = 0;
|
|
|
|
while (userp) {
|
|
if (copy_acl_object_label(&o_tmp, userp))
|
|
break;
|
|
|
|
userp = o_tmp.prev;
|
|
num++;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static struct acl_subject_label *
|
|
do_copy_user_subj(struct acl_subject_label *userp, struct acl_role_label *role, int *already_copied)
|
|
{
|
|
struct acl_subject_label *s_tmp = NULL, *s_tmp2;
|
|
__u32 num_objs;
|
|
struct acl_ip_label **i_tmp, *i_utmp2;
|
|
struct gr_hash_struct ghash;
|
|
struct subject_map *subjmap;
|
|
unsigned int i_num;
|
|
int err;
|
|
|
|
if (already_copied != NULL)
|
|
*already_copied = 0;
|
|
|
|
s_tmp = lookup_subject_map(userp);
|
|
|
|
/* we've already copied this subject into the kernel, just return
|
|
the reference to it, and don't copy it over again
|
|
*/
|
|
if (s_tmp) {
|
|
if (already_copied != NULL)
|
|
*already_copied = 1;
|
|
return(s_tmp);
|
|
}
|
|
|
|
if ((s_tmp = (struct acl_subject_label *)
|
|
acl_alloc(sizeof (struct acl_subject_label))) == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
subjmap = (struct subject_map *)kmalloc(sizeof (struct subject_map), GFP_KERNEL);
|
|
if (subjmap == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
subjmap->user = userp;
|
|
subjmap->kernel = s_tmp;
|
|
insert_subj_map_entry(subjmap);
|
|
|
|
if (copy_acl_subject_label(s_tmp, userp))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
err = alloc_and_copy_string(&s_tmp->filename, PATH_MAX);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
if (!strcmp(s_tmp->filename, "/"))
|
|
role->root_label = s_tmp;
|
|
|
|
if (copy_gr_hash_struct(&ghash, s_tmp->hash))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
/* copy user and group transition tables */
|
|
|
|
if (s_tmp->user_trans_num) {
|
|
uid_t *uidlist;
|
|
|
|
uidlist = (uid_t *)acl_alloc_num(s_tmp->user_trans_num, sizeof(uid_t));
|
|
if (uidlist == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
if (copy_from_user(uidlist, s_tmp->user_transitions, s_tmp->user_trans_num * sizeof(uid_t)))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
s_tmp->user_transitions = uidlist;
|
|
}
|
|
|
|
if (s_tmp->group_trans_num) {
|
|
gid_t *gidlist;
|
|
|
|
gidlist = (gid_t *)acl_alloc_num(s_tmp->group_trans_num, sizeof(gid_t));
|
|
if (gidlist == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
if (copy_from_user(gidlist, s_tmp->group_transitions, s_tmp->group_trans_num * sizeof(gid_t)))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
s_tmp->group_transitions = gidlist;
|
|
}
|
|
|
|
/* set up object hash table */
|
|
num_objs = count_user_objs(ghash.first);
|
|
|
|
s_tmp->obj_hash_size = num_objs;
|
|
s_tmp->obj_hash =
|
|
(struct acl_object_label **)
|
|
create_table(&(s_tmp->obj_hash_size), sizeof(void *));
|
|
|
|
if (!s_tmp->obj_hash)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
memset(s_tmp->obj_hash, 0,
|
|
s_tmp->obj_hash_size *
|
|
sizeof (struct acl_object_label *));
|
|
|
|
/* add in objects */
|
|
err = copy_user_objs(ghash.first, s_tmp, role);
|
|
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
/* set pointer for parent subject */
|
|
if (s_tmp->parent_subject) {
|
|
s_tmp2 = do_copy_user_subj(s_tmp->parent_subject, role, NULL);
|
|
|
|
if (IS_ERR(s_tmp2))
|
|
return s_tmp2;
|
|
|
|
s_tmp->parent_subject = s_tmp2;
|
|
}
|
|
|
|
/* add in ip acls */
|
|
|
|
if (!s_tmp->ip_num) {
|
|
s_tmp->ips = NULL;
|
|
goto insert;
|
|
}
|
|
|
|
i_tmp =
|
|
(struct acl_ip_label **) acl_alloc_num(s_tmp->ip_num,
|
|
sizeof (struct acl_ip_label *));
|
|
|
|
if (!i_tmp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
for (i_num = 0; i_num < s_tmp->ip_num; i_num++) {
|
|
*(i_tmp + i_num) =
|
|
(struct acl_ip_label *)
|
|
acl_alloc(sizeof (struct acl_ip_label));
|
|
if (!*(i_tmp + i_num))
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (copy_pointer_from_array(&i_utmp2, i_num, s_tmp->ips))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
if (copy_acl_ip_label(*(i_tmp + i_num), i_utmp2))
|
|
return ERR_PTR(-EFAULT);
|
|
|
|
if ((*(i_tmp + i_num))->iface == NULL)
|
|
continue;
|
|
|
|
err = alloc_and_copy_string(&(*(i_tmp + i_num))->iface, IFNAMSIZ);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
s_tmp->ips = i_tmp;
|
|
|
|
insert:
|
|
if (!insert_name_entry(s_tmp->filename, s_tmp->inode,
|
|
s_tmp->device, (s_tmp->mode & GR_DELETED) ? 1 : 0))
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
return s_tmp;
|
|
}
|
|
|
|
static int
|
|
copy_user_subjs(struct acl_subject_label *userp, struct acl_role_label *role)
|
|
{
|
|
struct acl_subject_label s_pre;
|
|
struct acl_subject_label * ret;
|
|
int err;
|
|
|
|
while (userp) {
|
|
if (copy_acl_subject_label(&s_pre, userp))
|
|
return -EFAULT;
|
|
|
|
ret = do_copy_user_subj(userp, role, NULL);
|
|
|
|
err = PTR_ERR(ret);
|
|
if (IS_ERR(ret))
|
|
return err;
|
|
|
|
insert_acl_subj_label(ret, role);
|
|
|
|
userp = s_pre.prev;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
copy_user_acl(struct gr_arg *arg)
|
|
{
|
|
struct acl_role_label *r_tmp = NULL, **r_utmp, *r_utmp2;
|
|
struct acl_subject_label *subj_list;
|
|
struct sprole_pw *sptmp;
|
|
struct gr_hash_struct *ghash;
|
|
uid_t *domainlist;
|
|
unsigned int r_num;
|
|
int err = 0;
|
|
__u16 i;
|
|
__u32 num_subjs;
|
|
|
|
/* we need a default and kernel role */
|
|
if (arg->role_db.num_roles < 2)
|
|
return -EINVAL;
|
|
|
|
/* copy special role authentication info from userspace */
|
|
|
|
polstate->num_sprole_pws = arg->num_sprole_pws;
|
|
polstate->acl_special_roles = (struct sprole_pw **) acl_alloc_num(polstate->num_sprole_pws, sizeof(struct sprole_pw *));
|
|
|
|
if (!polstate->acl_special_roles && polstate->num_sprole_pws)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < polstate->num_sprole_pws; i++) {
|
|
sptmp = (struct sprole_pw *) acl_alloc(sizeof(struct sprole_pw));
|
|
if (!sptmp)
|
|
return -ENOMEM;
|
|
if (copy_sprole_pw(sptmp, i, arg->sprole_pws))
|
|
return -EFAULT;
|
|
|
|
err = alloc_and_copy_string((char **)&sptmp->rolename, GR_SPROLE_LEN);
|
|
if (err)
|
|
return err;
|
|
|
|
#ifdef CONFIG_GRKERNSEC_RBAC_DEBUG
|
|
printk(KERN_ALERT "Copying special role %s\n", sptmp->rolename);
|
|
#endif
|
|
|
|
polstate->acl_special_roles[i] = sptmp;
|
|
}
|
|
|
|
r_utmp = (struct acl_role_label **) arg->role_db.r_table;
|
|
|
|
for (r_num = 0; r_num < arg->role_db.num_roles; r_num++) {
|
|
r_tmp = acl_alloc(sizeof (struct acl_role_label));
|
|
|
|
if (!r_tmp)
|
|
return -ENOMEM;
|
|
|
|
if (copy_pointer_from_array(&r_utmp2, r_num, r_utmp))
|
|
return -EFAULT;
|
|
|
|
if (copy_acl_role_label(r_tmp, r_utmp2))
|
|
return -EFAULT;
|
|
|
|
err = alloc_and_copy_string(&r_tmp->rolename, GR_SPROLE_LEN);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!strcmp(r_tmp->rolename, "default")
|
|
&& (r_tmp->roletype & GR_ROLE_DEFAULT)) {
|
|
polstate->default_role = r_tmp;
|
|
} else if (!strcmp(r_tmp->rolename, ":::kernel:::")) {
|
|
polstate->kernel_role = r_tmp;
|
|
}
|
|
|
|
if ((ghash = (struct gr_hash_struct *) acl_alloc(sizeof(struct gr_hash_struct))) == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_gr_hash_struct(ghash, r_tmp->hash))
|
|
return -EFAULT;
|
|
|
|
r_tmp->hash = ghash;
|
|
|
|
num_subjs = count_user_subjs(r_tmp->hash->first);
|
|
|
|
r_tmp->subj_hash_size = num_subjs;
|
|
r_tmp->subj_hash =
|
|
(struct acl_subject_label **)
|
|
create_table(&(r_tmp->subj_hash_size), sizeof(void *));
|
|
|
|
if (!r_tmp->subj_hash)
|
|
return -ENOMEM;
|
|
|
|
err = copy_user_allowedips(r_tmp);
|
|
if (err)
|
|
return err;
|
|
|
|
/* copy domain info */
|
|
if (r_tmp->domain_children != NULL) {
|
|
domainlist = acl_alloc_num(r_tmp->domain_child_num, sizeof(uid_t));
|
|
if (domainlist == NULL)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(domainlist, r_tmp->domain_children, r_tmp->domain_child_num * sizeof(uid_t)))
|
|
return -EFAULT;
|
|
|
|
r_tmp->domain_children = domainlist;
|
|
}
|
|
|
|
err = copy_user_transitions(r_tmp);
|
|
if (err)
|
|
return err;
|
|
|
|
memset(r_tmp->subj_hash, 0,
|
|
r_tmp->subj_hash_size *
|
|
sizeof (struct acl_subject_label *));
|
|
|
|
/* acquire the list of subjects, then NULL out
|
|
the list prior to parsing the subjects for this role,
|
|
as during this parsing the list is replaced with a list
|
|
of *nested* subjects for the role
|
|
*/
|
|
subj_list = r_tmp->hash->first;
|
|
|
|
/* set nested subject list to null */
|
|
r_tmp->hash->first = NULL;
|
|
|
|
err = copy_user_subjs(subj_list, r_tmp);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
insert_acl_role_label(r_tmp);
|
|
}
|
|
|
|
if (polstate->default_role == NULL || polstate->kernel_role == NULL)
|
|
return -EINVAL;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int gracl_reload_apply_policies(void *reload)
|
|
{
|
|
struct gr_reload_state *reload_state = (struct gr_reload_state *)reload;
|
|
struct task_struct *task, *task2;
|
|
struct acl_role_label *role, *rtmp;
|
|
struct acl_subject_label *subj;
|
|
const struct cred *cred;
|
|
int role_applied;
|
|
int ret = 0;
|
|
|
|
memcpy(&reload_state->oldpolicy, reload_state->oldpolicy_ptr, sizeof(struct gr_policy_state));
|
|
memcpy(&reload_state->oldalloc, reload_state->oldalloc_ptr, sizeof(struct gr_alloc_state));
|
|
|
|
/* first make sure we'll be able to apply the new policy cleanly */
|
|
do_each_thread(task2, task) {
|
|
if (task->exec_file == NULL)
|
|
continue;
|
|
role_applied = 0;
|
|
if (!reload_state->oldmode && task->role->roletype & GR_ROLE_SPECIAL) {
|
|
/* preserve special roles */
|
|
FOR_EACH_ROLE_START(role)
|
|
if ((role->roletype & GR_ROLE_SPECIAL) && !strcmp(task->role->rolename, role->rolename)) {
|
|
rtmp = task->role;
|
|
task->role = role;
|
|
role_applied = 1;
|
|
break;
|
|
}
|
|
FOR_EACH_ROLE_END(role)
|
|
}
|
|
if (!role_applied) {
|
|
cred = __task_cred(task);
|
|
rtmp = task->role;
|
|
task->role = __lookup_acl_role_label(polstate, task, GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid));
|
|
}
|
|
/* this handles non-nested inherited subjects, nested subjects will still
|
|
be dropped currently */
|
|
subj = __gr_get_subject_for_task(polstate, task, task->acl->filename, 1);
|
|
task->tmpacl = __gr_get_subject_for_task(polstate, task, NULL, 1);
|
|
/* change the role back so that we've made no modifications to the policy */
|
|
task->role = rtmp;
|
|
|
|
if (subj == NULL || task->tmpacl == NULL) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
} while_each_thread(task2, task);
|
|
|
|
/* now actually apply the policy */
|
|
|
|
do_each_thread(task2, task) {
|
|
if (task->exec_file) {
|
|
role_applied = 0;
|
|
if (!reload_state->oldmode && task->role->roletype & GR_ROLE_SPECIAL) {
|
|
/* preserve special roles */
|
|
FOR_EACH_ROLE_START(role)
|
|
if ((role->roletype & GR_ROLE_SPECIAL) && !strcmp(task->role->rolename, role->rolename)) {
|
|
task->role = role;
|
|
role_applied = 1;
|
|
break;
|
|
}
|
|
FOR_EACH_ROLE_END(role)
|
|
}
|
|
if (!role_applied) {
|
|
cred = __task_cred(task);
|
|
task->role = __lookup_acl_role_label(polstate, task, GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid));
|
|
}
|
|
/* this handles non-nested inherited subjects, nested subjects will still
|
|
be dropped currently */
|
|
if (!reload_state->oldmode && task->inherited)
|
|
subj = __gr_get_subject_for_task(polstate, task, task->acl->filename, 1);
|
|
else {
|
|
/* looked up and tagged to the task previously */
|
|
subj = task->tmpacl;
|
|
}
|
|
/* subj will be non-null */
|
|
__gr_apply_subject_to_task(polstate, task, subj);
|
|
if (reload_state->oldmode) {
|
|
task->acl_role_id = 0;
|
|
task->acl_sp_role = 0;
|
|
task->inherited = 0;
|
|
}
|
|
} else {
|
|
// it's a kernel process
|
|
task->role = polstate->kernel_role;
|
|
task->acl = polstate->kernel_role->root_label;
|
|
#ifdef CONFIG_GRKERNSEC_ACL_HIDEKERN
|
|
task->acl->mode &= ~GR_PROCFIND;
|
|
#endif
|
|
}
|
|
} while_each_thread(task2, task);
|
|
|
|
memcpy(reload_state->oldpolicy_ptr, &reload_state->newpolicy, sizeof(struct gr_policy_state));
|
|
memcpy(reload_state->oldalloc_ptr, &reload_state->newalloc, sizeof(struct gr_alloc_state));
|
|
|
|
out:
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int gracl_reload(struct gr_arg *args, unsigned char oldmode)
|
|
{
|
|
struct gr_reload_state new_reload_state = { };
|
|
int err;
|
|
|
|
new_reload_state.oldpolicy_ptr = polstate;
|
|
new_reload_state.oldalloc_ptr = current_alloc_state;
|
|
new_reload_state.oldmode = oldmode;
|
|
|
|
current_alloc_state = &new_reload_state.newalloc;
|
|
polstate = &new_reload_state.newpolicy;
|
|
|
|
/* everything relevant is now saved off, copy in the new policy */
|
|
if (init_variables(args, true)) {
|
|
gr_log_str(GR_DONT_AUDIT_GOOD, GR_INITF_ACL_MSG, GR_VERSION);
|
|
err = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
err = copy_user_acl(args);
|
|
free_init_variables();
|
|
if (err)
|
|
goto error;
|
|
/* the new policy is copied in, with the old policy available via saved_state
|
|
first go through applying roles, making sure to preserve special roles
|
|
then apply new subjects, making sure to preserve inherited and nested subjects,
|
|
though currently only inherited subjects will be preserved
|
|
*/
|
|
err = stop_machine(gracl_reload_apply_policies, &new_reload_state, NULL);
|
|
if (err)
|
|
goto error;
|
|
|
|
/* we've now applied the new policy, so restore the old policy state to free it */
|
|
polstate = &new_reload_state.oldpolicy;
|
|
current_alloc_state = &new_reload_state.oldalloc;
|
|
free_variables(true);
|
|
|
|
/* oldpolicy/oldalloc_ptr point to the new policy/alloc states as they were copied
|
|
to running_polstate/current_alloc_state inside stop_machine
|
|
*/
|
|
err = 0;
|
|
goto out;
|
|
error:
|
|
/* on error of loading the new policy, we'll just keep the previous
|
|
policy set around
|
|
*/
|
|
free_variables(true);
|
|
|
|
/* doesn't affect runtime, but maintains consistent state */
|
|
out:
|
|
polstate = new_reload_state.oldpolicy_ptr;
|
|
current_alloc_state = new_reload_state.oldalloc_ptr;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
gracl_init(struct gr_arg *args)
|
|
{
|
|
int error = 0;
|
|
|
|
memcpy(gr_system_salt, args->salt, GR_SALT_LEN);
|
|
memcpy(gr_system_sum, args->sum, GR_SHA_LEN);
|
|
|
|
if (init_variables(args, false)) {
|
|
gr_log_str(GR_DONT_AUDIT_GOOD, GR_INITF_ACL_MSG, GR_VERSION);
|
|
error = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
error = copy_user_acl(args);
|
|
free_init_variables();
|
|
if (error)
|
|
goto out;
|
|
|
|
error = gr_set_acls(0);
|
|
if (error)
|
|
goto out;
|
|
|
|
gr_enable_rbac_system();
|
|
|
|
return 0;
|
|
|
|
out:
|
|
free_variables(false);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
lookup_special_role_auth(__u16 mode, const char *rolename, unsigned char **salt,
|
|
unsigned char **sum)
|
|
{
|
|
struct acl_role_label *r;
|
|
struct role_allowed_ip *ipp;
|
|
struct role_transition *trans;
|
|
unsigned int i;
|
|
int found = 0;
|
|
u32 curr_ip = current->signal->curr_ip;
|
|
|
|
current->signal->saved_ip = curr_ip;
|
|
|
|
/* check transition table */
|
|
|
|
for (trans = current->role->transitions; trans; trans = trans->next) {
|
|
if (!strcmp(rolename, trans->rolename)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return 0;
|
|
|
|
/* handle special roles that do not require authentication
|
|
and check ip */
|
|
|
|
FOR_EACH_ROLE_START(r)
|
|
if (!strcmp(rolename, r->rolename) &&
|
|
(r->roletype & GR_ROLE_SPECIAL)) {
|
|
found = 0;
|
|
if (r->allowed_ips != NULL) {
|
|
for (ipp = r->allowed_ips; ipp; ipp = ipp->next) {
|
|
if ((ntohl(curr_ip) & ipp->netmask) ==
|
|
(ntohl(ipp->addr) & ipp->netmask))
|
|
found = 1;
|
|
}
|
|
} else
|
|
found = 2;
|
|
if (!found)
|
|
return 0;
|
|
|
|
if (((mode == GR_SPROLE) && (r->roletype & GR_ROLE_NOPW)) ||
|
|
((mode == GR_SPROLEPAM) && (r->roletype & GR_ROLE_PAM))) {
|
|
*salt = NULL;
|
|
*sum = NULL;
|
|
return 1;
|
|
}
|
|
}
|
|
FOR_EACH_ROLE_END(r)
|
|
|
|
for (i = 0; i < polstate->num_sprole_pws; i++) {
|
|
if (!strcmp(rolename, (const char *)polstate->acl_special_roles[i]->rolename)) {
|
|
*salt = polstate->acl_special_roles[i]->salt;
|
|
*sum = polstate->acl_special_roles[i]->sum;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gr_check_secure_terminal(struct task_struct *task)
|
|
{
|
|
struct task_struct *p, *p2, *p3;
|
|
struct files_struct *files;
|
|
struct fdtable *fdt;
|
|
struct file *our_file = NULL, *file;
|
|
struct inode *our_inode = NULL;
|
|
int i;
|
|
|
|
if (task->signal->tty == NULL)
|
|
return 1;
|
|
|
|
files = get_files_struct(task);
|
|
if (files != NULL) {
|
|
rcu_read_lock();
|
|
fdt = files_fdtable(files);
|
|
for (i=0; i < fdt->max_fds; i++) {
|
|
file = fcheck_files(files, i);
|
|
if (file && (our_file == NULL) && (file->private_data == task->signal->tty)) {
|
|
get_file(file);
|
|
our_file = file;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
put_files_struct(files);
|
|
}
|
|
|
|
if (our_file == NULL)
|
|
return 1;
|
|
|
|
our_inode = d_backing_inode(our_file->f_path.dentry);
|
|
|
|
read_lock(&tasklist_lock);
|
|
do_each_thread(p2, p) {
|
|
files = get_files_struct(p);
|
|
if (files == NULL ||
|
|
(p->signal && p->signal->tty == task->signal->tty)) {
|
|
if (files != NULL)
|
|
put_files_struct(files);
|
|
continue;
|
|
}
|
|
rcu_read_lock();
|
|
fdt = files_fdtable(files);
|
|
for (i=0; i < fdt->max_fds; i++) {
|
|
struct inode *inode = NULL;
|
|
file = fcheck_files(files, i);
|
|
if (file)
|
|
inode = d_backing_inode(file->f_path.dentry);
|
|
if (inode && S_ISCHR(inode->i_mode) && inode->i_rdev == our_inode->i_rdev) {
|
|
p3 = task;
|
|
while (task_pid_nr(p3) > 0) {
|
|
if (p3 == p)
|
|
break;
|
|
p3 = p3->real_parent;
|
|
}
|
|
if (p3 == p)
|
|
break;
|
|
gr_log_ttysniff(GR_DONT_AUDIT_GOOD, GR_TTYSNIFF_ACL_MSG, p);
|
|
gr_handle_alertkill(p);
|
|
rcu_read_unlock();
|
|
put_files_struct(files);
|
|
read_unlock(&tasklist_lock);
|
|
fput(our_file);
|
|
return 0;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
put_files_struct(files);
|
|
} while_each_thread(p2, p);
|
|
read_unlock(&tasklist_lock);
|
|
|
|
fput(our_file);
|
|
return 1;
|
|
}
|
|
|
|
ssize_t
|
|
write_grsec_handler(struct file *file, const char __user * buf, size_t count, loff_t *ppos)
|
|
{
|
|
struct gr_arg_wrapper uwrap;
|
|
unsigned char *sprole_salt = NULL;
|
|
unsigned char *sprole_sum = NULL;
|
|
int error = 0;
|
|
int error2 = 0;
|
|
size_t req_count = 0;
|
|
unsigned char oldmode = 0;
|
|
|
|
mutex_lock(&gr_dev_mutex);
|
|
|
|
if (gr_acl_is_enabled() && !(current->acl->mode & GR_KERNELAUTH)) {
|
|
error = -EPERM;
|
|
goto out;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
pax_open_kernel();
|
|
if (in_compat_syscall()) {
|
|
copy_gr_arg_wrapper = ©_gr_arg_wrapper_compat;
|
|
copy_gr_arg = ©_gr_arg_compat;
|
|
copy_acl_object_label = ©_acl_object_label_compat;
|
|
copy_acl_subject_label = ©_acl_subject_label_compat;
|
|
copy_acl_role_label = ©_acl_role_label_compat;
|
|
copy_acl_ip_label = ©_acl_ip_label_compat;
|
|
copy_role_allowed_ip = ©_role_allowed_ip_compat;
|
|
copy_role_transition = ©_role_transition_compat;
|
|
copy_sprole_pw = ©_sprole_pw_compat;
|
|
copy_gr_hash_struct = ©_gr_hash_struct_compat;
|
|
copy_pointer_from_array = ©_pointer_from_array_compat;
|
|
get_gr_arg_wrapper_size = &get_gr_arg_wrapper_size_compat;
|
|
} else {
|
|
copy_gr_arg_wrapper = ©_gr_arg_wrapper_normal;
|
|
copy_gr_arg = ©_gr_arg_normal;
|
|
copy_acl_object_label = ©_acl_object_label_normal;
|
|
copy_acl_subject_label = ©_acl_subject_label_normal;
|
|
copy_acl_role_label = ©_acl_role_label_normal;
|
|
copy_acl_ip_label = ©_acl_ip_label_normal;
|
|
copy_role_allowed_ip = ©_role_allowed_ip_normal;
|
|
copy_role_transition = ©_role_transition_normal;
|
|
copy_sprole_pw = ©_sprole_pw_normal;
|
|
copy_gr_hash_struct = ©_gr_hash_struct_normal;
|
|
copy_pointer_from_array = ©_pointer_from_array_normal;
|
|
get_gr_arg_wrapper_size = &get_gr_arg_wrapper_size_normal;
|
|
}
|
|
pax_close_kernel();
|
|
#endif
|
|
|
|
req_count = get_gr_arg_wrapper_size();
|
|
|
|
if (count != req_count) {
|
|
gr_log_int_int(GR_DONT_AUDIT_GOOD, GR_DEV_ACL_MSG, (int)count, (int)req_count);
|
|
error = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
|
|
if (gr_auth_expires && time_after_eq(get_seconds(), gr_auth_expires)) {
|
|
gr_auth_expires = 0;
|
|
gr_auth_attempts = 0;
|
|
}
|
|
|
|
error = copy_gr_arg_wrapper(buf, &uwrap);
|
|
if (error)
|
|
goto out;
|
|
|
|
error = copy_gr_arg(uwrap.arg, gr_usermode);
|
|
if (error)
|
|
goto out;
|
|
|
|
if (gr_usermode->mode != GR_SPROLE && gr_usermode->mode != GR_SPROLEPAM &&
|
|
gr_auth_attempts >= CONFIG_GRKERNSEC_ACL_MAXTRIES &&
|
|
time_after(gr_auth_expires, get_seconds())) {
|
|
error = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
/* if non-root trying to do anything other than use a special role,
|
|
do not attempt authentication, do not count towards authentication
|
|
locking
|
|
*/
|
|
|
|
if (gr_usermode->mode != GR_SPROLE && gr_usermode->mode != GR_STATUS &&
|
|
gr_usermode->mode != GR_UNSPROLE && gr_usermode->mode != GR_SPROLEPAM &&
|
|
gr_is_global_nonroot(current_uid())) {
|
|
error = -EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/* ensure pw and special role name are null terminated */
|
|
|
|
gr_usermode->pw[GR_PW_LEN - 1] = '\0';
|
|
gr_usermode->sp_role[GR_SPROLE_LEN - 1] = '\0';
|
|
|
|
/* Okay.
|
|
* We have our enough of the argument structure..(we have yet
|
|
* to copy_from_user the tables themselves) . Copy the tables
|
|
* only if we need them, i.e. for loading operations. */
|
|
|
|
switch (gr_usermode->mode) {
|
|
case GR_STATUS:
|
|
if (gr_acl_is_enabled()) {
|
|
error = 1;
|
|
if (!gr_check_secure_terminal(current))
|
|
error = 3;
|
|
} else
|
|
error = 2;
|
|
goto out;
|
|
case GR_SHUTDOWN:
|
|
if (gr_acl_is_enabled() && !(chkpw(gr_usermode, gr_system_salt, gr_system_sum))) {
|
|
stop_machine(gr_rbac_disable, NULL, NULL);
|
|
free_variables(false);
|
|
memset(gr_usermode, 0, sizeof(struct gr_arg));
|
|
memset(gr_system_salt, 0, GR_SALT_LEN);
|
|
memset(gr_system_sum, 0, GR_SHA_LEN);
|
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_SHUTS_ACL_MSG);
|
|
} else if (gr_acl_is_enabled()) {
|
|
gr_log_noargs(GR_DONT_AUDIT, GR_SHUTF_ACL_MSG);
|
|
error = -EPERM;
|
|
} else {
|
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_SHUTI_ACL_MSG);
|
|
error = -EAGAIN;
|
|
}
|
|
break;
|
|
case GR_ENABLE:
|
|
if (!gr_acl_is_enabled() && !(error2 = gracl_init(gr_usermode)))
|
|
gr_log_str(GR_DONT_AUDIT_GOOD, GR_ENABLE_ACL_MSG, GR_VERSION);
|
|
else {
|
|
if (gr_acl_is_enabled())
|
|
error = -EAGAIN;
|
|
else
|
|
error = error2;
|
|
gr_log_str(GR_DONT_AUDIT, GR_ENABLEF_ACL_MSG, GR_VERSION);
|
|
}
|
|
break;
|
|
case GR_OLDRELOAD:
|
|
oldmode = 1;
|
|
case GR_RELOAD:
|
|
if (!gr_acl_is_enabled()) {
|
|
gr_log_str(GR_DONT_AUDIT_GOOD, GR_RELOADI_ACL_MSG, GR_VERSION);
|
|
error = -EAGAIN;
|
|
} else if (!(chkpw(gr_usermode, gr_system_salt, gr_system_sum))) {
|
|
error2 = gracl_reload(gr_usermode, oldmode);
|
|
if (!error2)
|
|
gr_log_str(GR_DONT_AUDIT_GOOD, GR_RELOAD_ACL_MSG, GR_VERSION);
|
|
else {
|
|
gr_log_str(GR_DONT_AUDIT, GR_RELOADF_ACL_MSG, GR_VERSION);
|
|
error = error2;
|
|
}
|
|
} else {
|
|
gr_log_str(GR_DONT_AUDIT, GR_RELOADF_ACL_MSG, GR_VERSION);
|
|
error = -EPERM;
|
|
}
|
|
break;
|
|
case GR_SEGVMOD:
|
|
if (unlikely(!gr_acl_is_enabled())) {
|
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_SEGVMODI_ACL_MSG);
|
|
error = -EAGAIN;
|
|
break;
|
|
}
|
|
|
|
if (!(chkpw(gr_usermode, gr_system_salt, gr_system_sum))) {
|
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_SEGVMODS_ACL_MSG);
|
|
if (gr_usermode->segv_device && gr_usermode->segv_inode) {
|
|
struct acl_subject_label *segvacl;
|
|
segvacl =
|
|
lookup_acl_subj_label(gr_usermode->segv_inode,
|
|
gr_usermode->segv_device,
|
|
current->role);
|
|
if (segvacl) {
|
|
segvacl->crashes = 0;
|
|
segvacl->expires = 0;
|
|
}
|
|
} else
|
|
gr_find_and_remove_uid(gr_usermode->segv_uid);
|
|
} else {
|
|
gr_log_noargs(GR_DONT_AUDIT, GR_SEGVMODF_ACL_MSG);
|
|
error = -EPERM;
|
|
}
|
|
break;
|
|
case GR_SPROLE:
|
|
case GR_SPROLEPAM:
|
|
if (unlikely(!gr_acl_is_enabled())) {
|
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_SPROLEI_ACL_MSG);
|
|
error = -EAGAIN;
|
|
break;
|
|
}
|
|
|
|
if (current->role->expires && time_after_eq(get_seconds(), current->role->expires)) {
|
|
current->role->expires = 0;
|
|
current->role->auth_attempts = 0;
|
|
}
|
|
|
|
if (current->role->auth_attempts >= CONFIG_GRKERNSEC_ACL_MAXTRIES &&
|
|
time_after(current->role->expires, get_seconds())) {
|
|
error = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
if (lookup_special_role_auth
|
|
(gr_usermode->mode, (const char *)gr_usermode->sp_role, &sprole_salt, &sprole_sum)
|
|
&& ((!sprole_salt && !sprole_sum)
|
|
|| !(chkpw(gr_usermode, sprole_salt, sprole_sum)))) {
|
|
char *p = "";
|
|
assign_special_role((const char *)gr_usermode->sp_role);
|
|
read_lock(&tasklist_lock);
|
|
if (current->real_parent)
|
|
p = current->real_parent->role->rolename;
|
|
read_unlock(&tasklist_lock);
|
|
gr_log_str_int(GR_DONT_AUDIT_GOOD, GR_SPROLES_ACL_MSG,
|
|
p, acl_sp_role_value);
|
|
} else {
|
|
gr_log_str(GR_DONT_AUDIT, GR_SPROLEF_ACL_MSG, gr_usermode->sp_role);
|
|
error = -EPERM;
|
|
if(!(current->role->auth_attempts++))
|
|
current->role->expires = get_seconds() + CONFIG_GRKERNSEC_ACL_TIMEOUT;
|
|
|
|
goto out;
|
|
}
|
|
break;
|
|
case GR_UNSPROLE:
|
|
if (unlikely(!gr_acl_is_enabled())) {
|
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_UNSPROLEI_ACL_MSG);
|
|
error = -EAGAIN;
|
|
break;
|
|
}
|
|
|
|
if (current->role->roletype & GR_ROLE_SPECIAL) {
|
|
char *p = "";
|
|
int i = 0;
|
|
|
|
read_lock(&tasklist_lock);
|
|
if (current->real_parent) {
|
|
p = current->real_parent->role->rolename;
|
|
i = current->real_parent->acl_role_id;
|
|
}
|
|
read_unlock(&tasklist_lock);
|
|
|
|
gr_log_str_int(GR_DONT_AUDIT_GOOD, GR_UNSPROLES_ACL_MSG, p, i);
|
|
gr_set_acls(1);
|
|
} else {
|
|
error = -EPERM;
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
gr_log_int(GR_DONT_AUDIT, GR_INVMODE_ACL_MSG, gr_usermode->mode);
|
|
error = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (error != -EPERM)
|
|
goto out;
|
|
|
|
if(!(gr_auth_attempts++))
|
|
gr_auth_expires = get_seconds() + CONFIG_GRKERNSEC_ACL_TIMEOUT;
|
|
|
|
out:
|
|
mutex_unlock(&gr_dev_mutex);
|
|
|
|
if (!error)
|
|
error = req_count;
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
gr_set_acls(const int type)
|
|
{
|
|
struct task_struct *task, *task2;
|
|
struct acl_role_label *role = current->role;
|
|
struct acl_subject_label *subj;
|
|
__u16 acl_role_id = current->acl_role_id;
|
|
const struct cred *cred;
|
|
int ret;
|
|
|
|
rcu_read_lock();
|
|
read_lock(&tasklist_lock);
|
|
read_lock(&grsec_exec_file_lock);
|
|
do_each_thread(task2, task) {
|
|
/* check to see if we're called from the exit handler,
|
|
if so, only replace ACLs that have inherited the admin
|
|
ACL */
|
|
|
|
if (type && (task->role != role ||
|
|
task->acl_role_id != acl_role_id))
|
|
continue;
|
|
|
|
task->acl_role_id = 0;
|
|
task->acl_sp_role = 0;
|
|
task->inherited = 0;
|
|
|
|
if (task->exec_file) {
|
|
cred = __task_cred(task);
|
|
task->role = __lookup_acl_role_label(polstate, task, GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid));
|
|
subj = __gr_get_subject_for_task(polstate, task, NULL, 1);
|
|
if (subj == NULL) {
|
|
ret = -EINVAL;
|
|
read_unlock(&grsec_exec_file_lock);
|
|
read_unlock(&tasklist_lock);
|
|
rcu_read_unlock();
|
|
gr_log_str_int(GR_DONT_AUDIT_GOOD, GR_DEFACL_MSG, task->comm, task_pid_nr(task));
|
|
return ret;
|
|
}
|
|
__gr_apply_subject_to_task(polstate, task, subj);
|
|
} else {
|
|
// it's a kernel process
|
|
task->role = polstate->kernel_role;
|
|
task->acl = polstate->kernel_role->root_label;
|
|
#ifdef CONFIG_GRKERNSEC_ACL_HIDEKERN
|
|
task->acl->mode &= ~GR_PROCFIND;
|
|
#endif
|
|
}
|
|
} while_each_thread(task2, task);
|
|
read_unlock(&grsec_exec_file_lock);
|
|
read_unlock(&tasklist_lock);
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|