mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-01-30 23:02:18 +00:00
2a709f28fa
* 0day explit mitigation * Memory corruption prevention * Privilege escalation prevention * Buffer over flow prevention * File System corruption defense * Thread escape prevention This may very well be the most intensive inclusion to BrooklynR. This will not be part of an x86 suite nor it will be released as tool kit. The security core toolkit will remain part of kernel base.
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;
|
|
}
|