mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-01-31 07:12:18 +00:00
2774 lines
69 KiB
C
2774 lines
69 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 <linux/prefetch.h>
|
||
|
#if defined(CONFIG_BTRFS_FS) || defined(CONFIG_BTRFS_FS_MODULE)
|
||
|
#include <linux/magic.h>
|
||
|
#include <linux/pagemap.h>
|
||
|
#include "../fs/btrfs/async-thread.h"
|
||
|
#include "../fs/btrfs/ctree.h"
|
||
|
#include "../fs/btrfs/btrfs_inode.h"
|
||
|
#endif
|
||
|
#include "../fs/mount.h"
|
||
|
|
||
|
#include <asm/uaccess.h>
|
||
|
#include <asm/errno.h>
|
||
|
#include <asm/mman.h>
|
||
|
|
||
|
#define FOR_EACH_ROLE_START(role) \
|
||
|
role = running_polstate.role_list; \
|
||
|
while (role) {
|
||
|
|
||
|
#define FOR_EACH_ROLE_END(role) \
|
||
|
role = role->prev; \
|
||
|
}
|
||
|
|
||
|
extern struct path gr_real_root;
|
||
|
|
||
|
static struct gr_policy_state running_polstate;
|
||
|
struct gr_policy_state *polstate = &running_polstate;
|
||
|
extern struct gr_alloc_state *current_alloc_state;
|
||
|
|
||
|
extern char *gr_shared_page[4];
|
||
|
DEFINE_RWLOCK(gr_inode_lock);
|
||
|
|
||
|
static unsigned int gr_status __read_only = GR_STATUS_INIT;
|
||
|
|
||
|
#ifdef CONFIG_NET
|
||
|
extern struct vfsmount *sock_mnt;
|
||
|
#endif
|
||
|
|
||
|
extern struct vfsmount *pipe_mnt;
|
||
|
extern struct vfsmount *shm_mnt;
|
||
|
|
||
|
#ifdef CONFIG_HUGETLBFS
|
||
|
extern struct vfsmount *hugetlbfs_vfsmount[HUGE_MAX_HSTATE];
|
||
|
#endif
|
||
|
|
||
|
extern u16 acl_sp_role_value;
|
||
|
extern struct acl_object_label *fakefs_obj_rw;
|
||
|
extern struct acl_object_label *fakefs_obj_rwx;
|
||
|
|
||
|
int gr_acl_is_enabled(void)
|
||
|
{
|
||
|
return (gr_status & GR_READY);
|
||
|
}
|
||
|
|
||
|
void gr_enable_rbac_system(void)
|
||
|
{
|
||
|
pax_open_kernel();
|
||
|
gr_status |= GR_READY;
|
||
|
pax_close_kernel();
|
||
|
}
|
||
|
|
||
|
int gr_rbac_disable(void *unused)
|
||
|
{
|
||
|
pax_open_kernel();
|
||
|
gr_status &= ~GR_READY;
|
||
|
pax_close_kernel();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static inline dev_t __get_dev(const struct dentry *dentry)
|
||
|
{
|
||
|
struct dentry *ldentry = d_backing_dentry((struct dentry *)dentry);
|
||
|
|
||
|
#if defined(CONFIG_BTRFS_FS) || defined(CONFIG_BTRFS_FS_MODULE)
|
||
|
if (ldentry->d_sb->s_magic == BTRFS_SUPER_MAGIC)
|
||
|
return BTRFS_I(d_inode(ldentry))->root->anon_dev;
|
||
|
else
|
||
|
#endif
|
||
|
return d_inode(ldentry)->i_sb->s_dev;
|
||
|
}
|
||
|
|
||
|
static inline u64 __get_ino(const struct dentry *dentry)
|
||
|
{
|
||
|
struct dentry *ldentry = d_backing_dentry((struct dentry *)dentry);
|
||
|
|
||
|
#if defined(CONFIG_BTRFS_FS) || defined(CONFIG_BTRFS_FS_MODULE)
|
||
|
if (ldentry->d_sb->s_magic == BTRFS_SUPER_MAGIC)
|
||
|
return btrfs_ino(d_inode(dentry));
|
||
|
else
|
||
|
#endif
|
||
|
return d_inode(ldentry)->i_ino;
|
||
|
}
|
||
|
|
||
|
dev_t gr_get_dev_from_dentry(struct dentry *dentry)
|
||
|
{
|
||
|
return __get_dev(dentry);
|
||
|
}
|
||
|
|
||
|
u64 gr_get_ino_from_dentry(struct dentry *dentry)
|
||
|
{
|
||
|
return __get_ino(dentry);
|
||
|
}
|
||
|
|
||
|
static char gr_task_roletype_to_char(struct task_struct *task)
|
||
|
{
|
||
|
switch (task->role->roletype &
|
||
|
(GR_ROLE_DEFAULT | GR_ROLE_USER | GR_ROLE_GROUP |
|
||
|
GR_ROLE_SPECIAL)) {
|
||
|
case GR_ROLE_DEFAULT:
|
||
|
return 'D';
|
||
|
case GR_ROLE_USER:
|
||
|
return 'U';
|
||
|
case GR_ROLE_GROUP:
|
||
|
return 'G';
|
||
|
case GR_ROLE_SPECIAL:
|
||
|
return 'S';
|
||
|
}
|
||
|
|
||
|
return 'X';
|
||
|
}
|
||
|
|
||
|
char gr_roletype_to_char(void)
|
||
|
{
|
||
|
return gr_task_roletype_to_char(current);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_acl_tpe_check(void)
|
||
|
{
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
if (current->role->roletype & GR_ROLE_TPE)
|
||
|
return 1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_handle_rawio(const struct inode *inode)
|
||
|
{
|
||
|
#ifdef CONFIG_GRKERNSEC_CHROOT_CAPS
|
||
|
if (inode && (S_ISBLK(inode->i_mode) || (S_ISCHR(inode->i_mode) && imajor(inode) == RAW_MAJOR)) &&
|
||
|
grsec_enable_chroot_caps && proc_is_chrooted(current) &&
|
||
|
!capable(CAP_SYS_RAWIO))
|
||
|
return 1;
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_streq(const char *a, const char *b, const unsigned int lena, const unsigned int lenb)
|
||
|
{
|
||
|
if (likely(lena != lenb))
|
||
|
return 0;
|
||
|
|
||
|
return !memcmp(a, b, lena);
|
||
|
}
|
||
|
|
||
|
static int prepend(char **buffer, int *buflen, const char *str, int namelen)
|
||
|
{
|
||
|
*buflen -= namelen;
|
||
|
if (*buflen < 0)
|
||
|
return -ENAMETOOLONG;
|
||
|
*buffer -= namelen;
|
||
|
memcpy(*buffer, str, namelen);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int prepend_name(char **buffer, int *buflen, struct qstr *name)
|
||
|
{
|
||
|
return prepend(buffer, buflen, (const char *)name->name, name->len);
|
||
|
}
|
||
|
|
||
|
static int prepend_path(const struct path *path, struct path *root,
|
||
|
char **buffer, int *buflen)
|
||
|
{
|
||
|
struct dentry *dentry = path->dentry;
|
||
|
struct vfsmount *vfsmnt = path->mnt;
|
||
|
struct mount *mnt = real_mount(vfsmnt);
|
||
|
bool slash = false;
|
||
|
int error = 0;
|
||
|
|
||
|
while (dentry != root->dentry || vfsmnt != root->mnt) {
|
||
|
struct dentry * parent;
|
||
|
|
||
|
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
|
||
|
/* Global root? */
|
||
|
if (!mnt_has_parent(mnt)) {
|
||
|
goto out;
|
||
|
}
|
||
|
dentry = mnt->mnt_mountpoint;
|
||
|
mnt = mnt->mnt_parent;
|
||
|
vfsmnt = &mnt->mnt;
|
||
|
continue;
|
||
|
}
|
||
|
parent = dentry->d_parent;
|
||
|
prefetch(parent);
|
||
|
spin_lock(&dentry->d_lock);
|
||
|
error = prepend_name(buffer, buflen, &dentry->d_name);
|
||
|
spin_unlock(&dentry->d_lock);
|
||
|
if (!error)
|
||
|
error = prepend(buffer, buflen, "/", 1);
|
||
|
if (error)
|
||
|
break;
|
||
|
|
||
|
slash = true;
|
||
|
dentry = parent;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (!error && !slash)
|
||
|
error = prepend(buffer, buflen, "/", 1);
|
||
|
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
/* this must be called with mount_lock and rename_lock held */
|
||
|
|
||
|
static char *__our_d_path(const struct path *path, struct path *root,
|
||
|
char *buf, int buflen)
|
||
|
{
|
||
|
char *res = buf + buflen;
|
||
|
int error;
|
||
|
|
||
|
prepend(&res, &buflen, "\0", 1);
|
||
|
error = prepend_path(path, root, &res, &buflen);
|
||
|
if (error)
|
||
|
return ERR_PTR(error);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static char *
|
||
|
gen_full_path(struct path *path, struct path *root, char *buf, int buflen)
|
||
|
{
|
||
|
char *retval;
|
||
|
|
||
|
retval = __our_d_path(path, root, buf, buflen);
|
||
|
if (unlikely(IS_ERR(retval)))
|
||
|
retval = strcpy(buf, "<path too long>");
|
||
|
else if (unlikely(retval[1] == '/' && retval[2] == '\0'))
|
||
|
retval[1] = '\0';
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static char *
|
||
|
__d_real_path(const struct dentry *dentry, const struct vfsmount *vfsmnt,
|
||
|
char *buf, int buflen)
|
||
|
{
|
||
|
struct path path;
|
||
|
char *res;
|
||
|
|
||
|
path.dentry = (struct dentry *)dentry;
|
||
|
path.mnt = (struct vfsmount *)vfsmnt;
|
||
|
|
||
|
/* we can use gr_real_root.dentry, gr_real_root.mnt, because this is only called
|
||
|
by the RBAC system */
|
||
|
res = gen_full_path(&path, &gr_real_root, buf, buflen);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static char *
|
||
|
d_real_path(const struct dentry *dentry, const struct vfsmount *vfsmnt,
|
||
|
char *buf, int buflen)
|
||
|
{
|
||
|
char *res;
|
||
|
struct path path;
|
||
|
struct path root;
|
||
|
struct task_struct *reaper = init_pid_ns.child_reaper;
|
||
|
|
||
|
path.dentry = (struct dentry *)dentry;
|
||
|
path.mnt = (struct vfsmount *)vfsmnt;
|
||
|
|
||
|
/* we can't use gr_real_root.dentry, gr_real_root.mnt, because they belong only to the RBAC system */
|
||
|
get_fs_root(reaper->fs, &root);
|
||
|
|
||
|
read_seqlock_excl(&mount_lock);
|
||
|
write_seqlock(&rename_lock);
|
||
|
res = gen_full_path(&path, &root, buf, buflen);
|
||
|
write_sequnlock(&rename_lock);
|
||
|
read_sequnlock_excl(&mount_lock);
|
||
|
|
||
|
path_put(&root);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
gr_to_filename_rbac(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
char *ret;
|
||
|
read_seqlock_excl(&mount_lock);
|
||
|
write_seqlock(&rename_lock);
|
||
|
ret = __d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[0],smp_processor_id()),
|
||
|
PAGE_SIZE);
|
||
|
write_sequnlock(&rename_lock);
|
||
|
read_sequnlock_excl(&mount_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static char *
|
||
|
gr_to_proc_filename_rbac(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
char *ret;
|
||
|
char *buf;
|
||
|
int buflen;
|
||
|
|
||
|
read_seqlock_excl(&mount_lock);
|
||
|
write_seqlock(&rename_lock);
|
||
|
buf = per_cpu_ptr(gr_shared_page[0], smp_processor_id());
|
||
|
ret = __d_real_path(dentry, mnt, buf, PAGE_SIZE - 6);
|
||
|
buflen = (int)(ret - buf);
|
||
|
if (buflen >= 5)
|
||
|
prepend(&ret, &buflen, "/proc", 5);
|
||
|
else
|
||
|
ret = strcpy(buf, "<path too long>");
|
||
|
write_sequnlock(&rename_lock);
|
||
|
read_sequnlock_excl(&mount_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
gr_to_filename_nolock(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
return __d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[0],smp_processor_id()),
|
||
|
PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
gr_to_filename(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
return d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[0], smp_processor_id()),
|
||
|
PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
gr_to_filename1(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
return d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[1], smp_processor_id()),
|
||
|
PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
gr_to_filename2(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
return d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[2], smp_processor_id()),
|
||
|
PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
gr_to_filename3(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
return d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[3], smp_processor_id()),
|
||
|
PAGE_SIZE);
|
||
|
}
|
||
|
|
||
|
__u32
|
||
|
to_gr_audit(const __u32 reqmode)
|
||
|
{
|
||
|
/* masks off auditable permission flags, then shifts them to create
|
||
|
auditing flags, and adds the special case of append auditing if
|
||
|
we're requesting write */
|
||
|
return (((reqmode & ~GR_AUDITS) << 10) | ((reqmode & GR_WRITE) ? GR_AUDIT_APPEND : 0));
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
unsigned int index = gr_rhash(uid, GR_ROLE_USER, state->acl_role_set.r_size);
|
||
|
struct acl_role_label *match;
|
||
|
struct role_allowed_ip *ipp;
|
||
|
unsigned int x;
|
||
|
u32 curr_ip = task->signal->saved_ip;
|
||
|
|
||
|
match = state->acl_role_set.r_hash[index];
|
||
|
|
||
|
while (match) {
|
||
|
if ((match->roletype & (GR_ROLE_DOMAIN | GR_ROLE_USER)) == (GR_ROLE_DOMAIN | GR_ROLE_USER)) {
|
||
|
for (x = 0; x < match->domain_child_num; x++) {
|
||
|
if (match->domain_children[x] == uid)
|
||
|
goto found;
|
||
|
}
|
||
|
} else if (match->uidgid == uid && match->roletype & GR_ROLE_USER)
|
||
|
break;
|
||
|
match = match->next;
|
||
|
}
|
||
|
found:
|
||
|
if (match == NULL) {
|
||
|
try_group:
|
||
|
index = gr_rhash(gid, GR_ROLE_GROUP, state->acl_role_set.r_size);
|
||
|
match = state->acl_role_set.r_hash[index];
|
||
|
|
||
|
while (match) {
|
||
|
if ((match->roletype & (GR_ROLE_DOMAIN | GR_ROLE_GROUP)) == (GR_ROLE_DOMAIN | GR_ROLE_GROUP)) {
|
||
|
for (x = 0; x < match->domain_child_num; x++) {
|
||
|
if (match->domain_children[x] == gid)
|
||
|
goto found2;
|
||
|
}
|
||
|
} else if (match->uidgid == gid && match->roletype & GR_ROLE_GROUP)
|
||
|
break;
|
||
|
match = match->next;
|
||
|
}
|
||
|
found2:
|
||
|
if (match == NULL)
|
||
|
match = state->default_role;
|
||
|
if (match->allowed_ips == NULL)
|
||
|
return match;
|
||
|
else {
|
||
|
for (ipp = match->allowed_ips; ipp; ipp = ipp->next) {
|
||
|
if (likely
|
||
|
((ntohl(curr_ip) & ipp->netmask) ==
|
||
|
(ntohl(ipp->addr) & ipp->netmask)))
|
||
|
return match;
|
||
|
}
|
||
|
match = state->default_role;
|
||
|
}
|
||
|
} else if (match->allowed_ips == NULL) {
|
||
|
return match;
|
||
|
} else {
|
||
|
for (ipp = match->allowed_ips; ipp; ipp = ipp->next) {
|
||
|
if (likely
|
||
|
((ntohl(curr_ip) & ipp->netmask) ==
|
||
|
(ntohl(ipp->addr) & ipp->netmask)))
|
||
|
return match;
|
||
|
}
|
||
|
goto try_group;
|
||
|
}
|
||
|
|
||
|
return match;
|
||
|
}
|
||
|
|
||
|
static struct acl_role_label *
|
||
|
lookup_acl_role_label(const struct task_struct *task, const uid_t uid,
|
||
|
const gid_t gid)
|
||
|
{
|
||
|
return __lookup_acl_role_label(&running_polstate, task, uid, gid);
|
||
|
}
|
||
|
|
||
|
struct acl_subject_label *
|
||
|
lookup_acl_subj_label(const u64 ino, const dev_t dev,
|
||
|
const struct acl_role_label *role)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(ino, dev, role->subj_hash_size);
|
||
|
struct acl_subject_label *match;
|
||
|
|
||
|
match = role->subj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != ino || match->device != dev ||
|
||
|
(match->mode & GR_DELETED))) {
|
||
|
match = match->next;
|
||
|
}
|
||
|
|
||
|
if (match && !(match->mode & GR_DELETED))
|
||
|
return match;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
struct acl_subject_label *
|
||
|
lookup_acl_subj_label_deleted(const u64 ino, const dev_t dev,
|
||
|
const struct acl_role_label *role)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(ino, dev, role->subj_hash_size);
|
||
|
struct acl_subject_label *match;
|
||
|
|
||
|
match = role->subj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != ino || match->device != dev ||
|
||
|
!(match->mode & GR_DELETED))) {
|
||
|
match = match->next;
|
||
|
}
|
||
|
|
||
|
if (match && (match->mode & GR_DELETED))
|
||
|
return match;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
lookup_acl_obj_label(const u64 ino, const dev_t dev,
|
||
|
const struct acl_subject_label *subj)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(ino, dev, subj->obj_hash_size);
|
||
|
struct acl_object_label *match;
|
||
|
|
||
|
match = subj->obj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != ino || match->device != dev ||
|
||
|
(match->mode & GR_DELETED))) {
|
||
|
match = match->next;
|
||
|
}
|
||
|
|
||
|
if (match && !(match->mode & GR_DELETED))
|
||
|
return match;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
lookup_acl_obj_label_create(const u64 ino, const dev_t dev,
|
||
|
const struct acl_subject_label *subj)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(ino, dev, subj->obj_hash_size);
|
||
|
struct acl_object_label *match;
|
||
|
|
||
|
match = subj->obj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != ino || match->device != dev ||
|
||
|
!(match->mode & GR_DELETED))) {
|
||
|
match = match->next;
|
||
|
}
|
||
|
|
||
|
if (match && (match->mode & GR_DELETED))
|
||
|
return match;
|
||
|
|
||
|
match = subj->obj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != ino || match->device != dev ||
|
||
|
(match->mode & GR_DELETED))) {
|
||
|
match = match->next;
|
||
|
}
|
||
|
|
||
|
if (match && !(match->mode & GR_DELETED))
|
||
|
return match;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
struct name_entry *
|
||
|
__lookup_name_entry(const struct gr_policy_state *state, const char *name)
|
||
|
{
|
||
|
unsigned int len = strlen(name);
|
||
|
unsigned int key = full_name_hash(NULL, (const unsigned char *)name, len);
|
||
|
unsigned int index = key % state->name_set.n_size;
|
||
|
struct name_entry *match;
|
||
|
|
||
|
match = state->name_set.n_hash[index];
|
||
|
|
||
|
while (match && (match->key != key || !gr_streq(match->name, name, match->len, len)))
|
||
|
match = match->next;
|
||
|
|
||
|
return match;
|
||
|
}
|
||
|
|
||
|
static struct name_entry *
|
||
|
lookup_name_entry(const char *name)
|
||
|
{
|
||
|
return __lookup_name_entry(&running_polstate, name);
|
||
|
}
|
||
|
|
||
|
static struct name_entry *
|
||
|
lookup_name_entry_create(const char *name)
|
||
|
{
|
||
|
unsigned int len = strlen(name);
|
||
|
unsigned int key = full_name_hash(NULL, (const unsigned char *)name, len);
|
||
|
unsigned int index = key % running_polstate.name_set.n_size;
|
||
|
struct name_entry *match;
|
||
|
|
||
|
match = running_polstate.name_set.n_hash[index];
|
||
|
|
||
|
while (match && (match->key != key || !gr_streq(match->name, name, match->len, len) ||
|
||
|
!match->deleted))
|
||
|
match = match->next;
|
||
|
|
||
|
if (match && match->deleted)
|
||
|
return match;
|
||
|
|
||
|
match = running_polstate.name_set.n_hash[index];
|
||
|
|
||
|
while (match && (match->key != key || !gr_streq(match->name, name, match->len, len) ||
|
||
|
match->deleted))
|
||
|
match = match->next;
|
||
|
|
||
|
if (match && !match->deleted)
|
||
|
return match;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct inodev_entry *
|
||
|
lookup_inodev_entry(const u64 ino, const dev_t dev)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(ino, dev, running_polstate.inodev_set.i_size);
|
||
|
struct inodev_entry *match;
|
||
|
|
||
|
match = running_polstate.inodev_set.i_hash[index];
|
||
|
|
||
|
while (match && (match->nentry->inode != ino || match->nentry->device != dev))
|
||
|
match = match->next;
|
||
|
|
||
|
return match;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
__insert_inodev_entry(const struct gr_policy_state *state, struct inodev_entry *entry)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(entry->nentry->inode, entry->nentry->device,
|
||
|
state->inodev_set.i_size);
|
||
|
struct inodev_entry **curr;
|
||
|
|
||
|
entry->prev = NULL;
|
||
|
|
||
|
curr = &state->inodev_set.i_hash[index];
|
||
|
if (*curr != NULL)
|
||
|
(*curr)->prev = entry;
|
||
|
|
||
|
entry->next = *curr;
|
||
|
*curr = entry;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
insert_inodev_entry(struct inodev_entry *entry)
|
||
|
{
|
||
|
__insert_inodev_entry(&running_polstate, entry);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
insert_acl_obj_label(struct acl_object_label *obj,
|
||
|
struct acl_subject_label *subj)
|
||
|
{
|
||
|
unsigned int index =
|
||
|
gr_fhash(obj->inode, obj->device, subj->obj_hash_size);
|
||
|
struct acl_object_label **curr;
|
||
|
|
||
|
obj->prev = NULL;
|
||
|
|
||
|
curr = &subj->obj_hash[index];
|
||
|
if (*curr != NULL)
|
||
|
(*curr)->prev = obj;
|
||
|
|
||
|
obj->next = *curr;
|
||
|
*curr = obj;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
insert_acl_subj_label(struct acl_subject_label *obj,
|
||
|
struct acl_role_label *role)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(obj->inode, obj->device, role->subj_hash_size);
|
||
|
struct acl_subject_label **curr;
|
||
|
|
||
|
obj->prev = NULL;
|
||
|
|
||
|
curr = &role->subj_hash[index];
|
||
|
if (*curr != NULL)
|
||
|
(*curr)->prev = obj;
|
||
|
|
||
|
obj->next = *curr;
|
||
|
*curr = obj;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* derived from glibc fnmatch() 0: match, 1: no match*/
|
||
|
|
||
|
static int
|
||
|
glob_match(const char *p, const char *n)
|
||
|
{
|
||
|
char c;
|
||
|
|
||
|
while ((c = *p++) != '\0') {
|
||
|
switch (c) {
|
||
|
case '?':
|
||
|
if (*n == '\0')
|
||
|
return 1;
|
||
|
else if (*n == '/')
|
||
|
return 1;
|
||
|
break;
|
||
|
case '\\':
|
||
|
if (*n != c)
|
||
|
return 1;
|
||
|
break;
|
||
|
case '*':
|
||
|
for (c = *p++; c == '?' || c == '*'; c = *p++) {
|
||
|
if (*n == '/')
|
||
|
return 1;
|
||
|
else if (c == '?') {
|
||
|
if (*n == '\0')
|
||
|
return 1;
|
||
|
else
|
||
|
++n;
|
||
|
}
|
||
|
}
|
||
|
if (c == '\0') {
|
||
|
return 0;
|
||
|
} else {
|
||
|
const char *endp;
|
||
|
|
||
|
if ((endp = strchr(n, '/')) == NULL)
|
||
|
endp = n + strlen(n);
|
||
|
|
||
|
if (c == '[') {
|
||
|
for (--p; n < endp; ++n)
|
||
|
if (!glob_match(p, n))
|
||
|
return 0;
|
||
|
} else if (c == '/') {
|
||
|
while (*n != '\0' && *n != '/')
|
||
|
++n;
|
||
|
if (*n == '/' && !glob_match(p, n + 1))
|
||
|
return 0;
|
||
|
} else {
|
||
|
for (--p; n < endp; ++n)
|
||
|
if (*n == c && !glob_match(p, n))
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
case '[':
|
||
|
{
|
||
|
int not;
|
||
|
char cold;
|
||
|
|
||
|
if (*n == '\0' || *n == '/')
|
||
|
return 1;
|
||
|
|
||
|
not = (*p == '!' || *p == '^');
|
||
|
if (not)
|
||
|
++p;
|
||
|
|
||
|
c = *p++;
|
||
|
for (;;) {
|
||
|
unsigned char fn = (unsigned char)*n;
|
||
|
|
||
|
if (c == '\0')
|
||
|
return 1;
|
||
|
else {
|
||
|
if (c == fn)
|
||
|
goto matched;
|
||
|
cold = c;
|
||
|
c = *p++;
|
||
|
|
||
|
if (c == '-' && *p != ']') {
|
||
|
unsigned char cend = *p++;
|
||
|
|
||
|
if (cend == '\0')
|
||
|
return 1;
|
||
|
|
||
|
if (cold <= fn && fn <= cend)
|
||
|
goto matched;
|
||
|
|
||
|
c = *p++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (c == ']')
|
||
|
break;
|
||
|
}
|
||
|
if (!not)
|
||
|
return 1;
|
||
|
break;
|
||
|
matched:
|
||
|
while (c != ']') {
|
||
|
if (c == '\0')
|
||
|
return 1;
|
||
|
|
||
|
c = *p++;
|
||
|
}
|
||
|
if (not)
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
if (c != *n)
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
++n;
|
||
|
}
|
||
|
|
||
|
if (*n == '\0')
|
||
|
return 0;
|
||
|
|
||
|
if (*n == '/')
|
||
|
return 0;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
chk_glob_label(struct acl_object_label *globbed,
|
||
|
const struct dentry *dentry, const struct vfsmount *mnt, char **path)
|
||
|
{
|
||
|
struct acl_object_label *tmp;
|
||
|
|
||
|
if (*path == NULL)
|
||
|
*path = gr_to_filename_nolock(dentry, mnt);
|
||
|
|
||
|
tmp = globbed;
|
||
|
|
||
|
while (tmp) {
|
||
|
if (!glob_match(tmp->filename, *path))
|
||
|
return tmp;
|
||
|
tmp = tmp->next;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
__full_lookup(const struct dentry *orig_dentry, const struct vfsmount *orig_mnt,
|
||
|
const u64 curr_ino, const dev_t curr_dev,
|
||
|
const struct acl_subject_label *subj, char **path, const int checkglob)
|
||
|
{
|
||
|
struct acl_subject_label *tmpsubj;
|
||
|
struct acl_object_label *retval;
|
||
|
struct acl_object_label *retval2;
|
||
|
|
||
|
tmpsubj = (struct acl_subject_label *) subj;
|
||
|
read_lock(&gr_inode_lock);
|
||
|
do {
|
||
|
retval = lookup_acl_obj_label(curr_ino, curr_dev, tmpsubj);
|
||
|
if (retval) {
|
||
|
if (checkglob && retval->globbed) {
|
||
|
retval2 = chk_glob_label(retval->globbed, orig_dentry, orig_mnt, path);
|
||
|
if (retval2)
|
||
|
retval = retval2;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
} while ((tmpsubj = tmpsubj->parent_subject));
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
full_lookup(const struct dentry *orig_dentry, const struct vfsmount *orig_mnt,
|
||
|
struct dentry *curr_dentry,
|
||
|
const struct acl_subject_label *subj, char **path, const int checkglob)
|
||
|
{
|
||
|
int newglob = checkglob;
|
||
|
u64 inode;
|
||
|
dev_t device;
|
||
|
|
||
|
/* if we aren't checking a subdirectory of the original path yet, don't do glob checking
|
||
|
as we don't want a / * rule to match instead of the / object
|
||
|
don't do this for create lookups that call this function though, since they're looking up
|
||
|
on the parent and thus need globbing checks on all paths
|
||
|
*/
|
||
|
if (orig_dentry == curr_dentry && newglob != GR_CREATE_GLOB)
|
||
|
newglob = GR_NO_GLOB;
|
||
|
|
||
|
spin_lock(&curr_dentry->d_lock);
|
||
|
inode = __get_ino(curr_dentry);
|
||
|
device = __get_dev(curr_dentry);
|
||
|
spin_unlock(&curr_dentry->d_lock);
|
||
|
|
||
|
return __full_lookup(orig_dentry, orig_mnt, inode, device, subj, path, newglob);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_HUGETLBFS
|
||
|
static inline bool
|
||
|
is_hugetlbfs_mnt(const struct vfsmount *mnt)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < HUGE_MAX_HSTATE; i++) {
|
||
|
if (unlikely(hugetlbfs_vfsmount[i] == mnt))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
__chk_obj_label(const struct dentry *l_dentry, const struct vfsmount *l_mnt,
|
||
|
const struct acl_subject_label *subj, char *path, const int checkglob)
|
||
|
{
|
||
|
struct dentry *dentry = (struct dentry *) l_dentry;
|
||
|
struct vfsmount *mnt = (struct vfsmount *) l_mnt;
|
||
|
struct inode * inode = d_backing_inode(dentry);
|
||
|
struct mount *real_mnt = real_mount(mnt);
|
||
|
struct acl_object_label *retval;
|
||
|
struct dentry *parent;
|
||
|
|
||
|
read_seqlock_excl(&mount_lock);
|
||
|
write_seqlock(&rename_lock);
|
||
|
|
||
|
if (unlikely((mnt == shm_mnt && inode->i_nlink == 0) || mnt == pipe_mnt ||
|
||
|
#ifdef CONFIG_NET
|
||
|
mnt == sock_mnt ||
|
||
|
#endif
|
||
|
#ifdef CONFIG_HUGETLBFS
|
||
|
(is_hugetlbfs_mnt(mnt) && inode->i_nlink == 0) ||
|
||
|
#endif
|
||
|
/* ignore Eric Biederman */
|
||
|
IS_PRIVATE(inode))) {
|
||
|
retval = (subj->mode & GR_SHMEXEC) ? fakefs_obj_rwx : fakefs_obj_rw;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
for (;;) {
|
||
|
if (dentry == gr_real_root.dentry && mnt == gr_real_root.mnt)
|
||
|
break;
|
||
|
|
||
|
if (dentry == mnt->mnt_root || IS_ROOT(dentry)) {
|
||
|
if (!mnt_has_parent(real_mnt))
|
||
|
break;
|
||
|
|
||
|
retval = full_lookup(l_dentry, l_mnt, dentry, subj, &path, checkglob);
|
||
|
if (retval != NULL)
|
||
|
goto out;
|
||
|
|
||
|
dentry = real_mnt->mnt_mountpoint;
|
||
|
real_mnt = real_mnt->mnt_parent;
|
||
|
mnt = &real_mnt->mnt;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
parent = dentry->d_parent;
|
||
|
retval = full_lookup(l_dentry, l_mnt, dentry, subj, &path, checkglob);
|
||
|
if (retval != NULL)
|
||
|
goto out;
|
||
|
|
||
|
dentry = parent;
|
||
|
}
|
||
|
|
||
|
retval = full_lookup(l_dentry, l_mnt, dentry, subj, &path, checkglob);
|
||
|
|
||
|
/* gr_real_root is pinned so we don't have to hold a reference */
|
||
|
if (retval == NULL)
|
||
|
retval = full_lookup(l_dentry, l_mnt, gr_real_root.dentry, subj, &path, checkglob);
|
||
|
out:
|
||
|
write_sequnlock(&rename_lock);
|
||
|
read_sequnlock_excl(&mount_lock);
|
||
|
|
||
|
BUG_ON(retval == NULL);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
chk_obj_label(const struct dentry *l_dentry, const struct vfsmount *l_mnt,
|
||
|
const struct acl_subject_label *subj)
|
||
|
{
|
||
|
char *path = NULL;
|
||
|
return __chk_obj_label(l_dentry, l_mnt, subj, path, GR_REG_GLOB);
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
chk_obj_label_noglob(const struct dentry *l_dentry, const struct vfsmount *l_mnt,
|
||
|
const struct acl_subject_label *subj)
|
||
|
{
|
||
|
char *path = NULL;
|
||
|
return __chk_obj_label(l_dentry, l_mnt, subj, path, GR_NO_GLOB);
|
||
|
}
|
||
|
|
||
|
static struct acl_object_label *
|
||
|
chk_obj_create_label(const struct dentry *l_dentry, const struct vfsmount *l_mnt,
|
||
|
const struct acl_subject_label *subj, char *path)
|
||
|
{
|
||
|
return __chk_obj_label(l_dentry, l_mnt, subj, path, GR_CREATE_GLOB);
|
||
|
}
|
||
|
|
||
|
struct acl_subject_label *
|
||
|
chk_subj_label(const struct dentry *l_dentry, const struct vfsmount *l_mnt,
|
||
|
const struct acl_role_label *role)
|
||
|
{
|
||
|
struct dentry *dentry = (struct dentry *) l_dentry;
|
||
|
struct vfsmount *mnt = (struct vfsmount *) l_mnt;
|
||
|
struct mount *real_mnt = real_mount(mnt);
|
||
|
struct acl_subject_label *retval;
|
||
|
struct dentry *parent;
|
||
|
|
||
|
read_seqlock_excl(&mount_lock);
|
||
|
write_seqlock(&rename_lock);
|
||
|
|
||
|
for (;;) {
|
||
|
if (dentry == gr_real_root.dentry && mnt == gr_real_root.mnt)
|
||
|
break;
|
||
|
if (dentry == mnt->mnt_root || IS_ROOT(dentry)) {
|
||
|
if (!mnt_has_parent(real_mnt))
|
||
|
break;
|
||
|
|
||
|
spin_lock(&dentry->d_lock);
|
||
|
read_lock(&gr_inode_lock);
|
||
|
retval =
|
||
|
lookup_acl_subj_label(__get_ino(dentry),
|
||
|
__get_dev(dentry), role);
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
spin_unlock(&dentry->d_lock);
|
||
|
if (retval != NULL)
|
||
|
goto out;
|
||
|
|
||
|
dentry = real_mnt->mnt_mountpoint;
|
||
|
real_mnt = real_mnt->mnt_parent;
|
||
|
mnt = &real_mnt->mnt;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
spin_lock(&dentry->d_lock);
|
||
|
read_lock(&gr_inode_lock);
|
||
|
retval = lookup_acl_subj_label(__get_ino(dentry),
|
||
|
__get_dev(dentry), role);
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
parent = dentry->d_parent;
|
||
|
spin_unlock(&dentry->d_lock);
|
||
|
|
||
|
if (retval != NULL)
|
||
|
goto out;
|
||
|
|
||
|
dentry = parent;
|
||
|
}
|
||
|
|
||
|
spin_lock(&dentry->d_lock);
|
||
|
read_lock(&gr_inode_lock);
|
||
|
retval = lookup_acl_subj_label(__get_ino(dentry),
|
||
|
__get_dev(dentry), role);
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
spin_unlock(&dentry->d_lock);
|
||
|
|
||
|
if (unlikely(retval == NULL)) {
|
||
|
/* gr_real_root is pinned, we don't need to hold a reference */
|
||
|
read_lock(&gr_inode_lock);
|
||
|
retval = lookup_acl_subj_label(__get_ino(gr_real_root.dentry),
|
||
|
__get_dev(gr_real_root.dentry), role);
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
}
|
||
|
out:
|
||
|
write_sequnlock(&rename_lock);
|
||
|
read_sequnlock_excl(&mount_lock);
|
||
|
|
||
|
BUG_ON(retval == NULL);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
assign_special_role(const char *rolename)
|
||
|
{
|
||
|
struct acl_object_label *obj;
|
||
|
struct acl_role_label *r;
|
||
|
struct acl_role_label *assigned = NULL;
|
||
|
struct task_struct *tsk;
|
||
|
struct file *filp;
|
||
|
|
||
|
FOR_EACH_ROLE_START(r)
|
||
|
if (!strcmp(rolename, r->rolename) &&
|
||
|
(r->roletype & GR_ROLE_SPECIAL)) {
|
||
|
assigned = r;
|
||
|
break;
|
||
|
}
|
||
|
FOR_EACH_ROLE_END(r)
|
||
|
|
||
|
if (!assigned)
|
||
|
return;
|
||
|
|
||
|
read_lock(&tasklist_lock);
|
||
|
read_lock(&grsec_exec_file_lock);
|
||
|
|
||
|
tsk = current->real_parent;
|
||
|
if (tsk == NULL)
|
||
|
goto out_unlock;
|
||
|
|
||
|
filp = tsk->exec_file;
|
||
|
if (filp == NULL)
|
||
|
goto out_unlock;
|
||
|
|
||
|
tsk->is_writable = 0;
|
||
|
tsk->inherited = 0;
|
||
|
|
||
|
tsk->acl_sp_role = 1;
|
||
|
tsk->acl_role_id = ++acl_sp_role_value;
|
||
|
tsk->role = assigned;
|
||
|
tsk->acl = chk_subj_label(filp->f_path.dentry, filp->f_path.mnt, tsk->role);
|
||
|
|
||
|
/* ignore additional mmap checks for processes that are writable
|
||
|
by the default ACL */
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, running_polstate.default_role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
tsk->is_writable = 1;
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, tsk->role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
tsk->is_writable = 1;
|
||
|
|
||
|
#ifdef CONFIG_GRKERNSEC_RBAC_DEBUG
|
||
|
printk(KERN_ALERT "Assigning special role:%s subject:%s to process (%s:%d)\n", tsk->role->rolename,
|
||
|
tsk->acl->filename, tsk->comm, task_pid_nr(tsk));
|
||
|
#endif
|
||
|
|
||
|
out_unlock:
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
read_unlock(&tasklist_lock);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
gr_log_learn(const struct dentry *dentry, const struct vfsmount *mnt, const __u32 mode)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
const struct cred *cred = current_cred();
|
||
|
|
||
|
security_learn(GR_LEARN_AUDIT_MSG, task->role->rolename, task->role->roletype,
|
||
|
GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid), task->exec_file ? gr_to_filename1(task->exec_file->f_path.dentry,
|
||
|
task->exec_file->f_path.mnt) : task->acl->filename, task->acl->filename,
|
||
|
1UL, 1UL, gr_to_filename(dentry, mnt), (unsigned long) mode, &task->signal->saved_ip);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gr_log_learn_uid_change(const kuid_t real, const kuid_t effective, const kuid_t fs)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
const struct cred *cred = current_cred();
|
||
|
|
||
|
security_learn(GR_ID_LEARN_MSG, task->role->rolename, task->role->roletype,
|
||
|
GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid), task->exec_file ? gr_to_filename1(task->exec_file->f_path.dentry,
|
||
|
task->exec_file->f_path.mnt) : task->acl->filename, task->acl->filename,
|
||
|
'u', GR_GLOBAL_UID(real), GR_GLOBAL_UID(effective), GR_GLOBAL_UID(fs), &task->signal->saved_ip);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gr_log_learn_gid_change(const kgid_t real, const kgid_t effective, const kgid_t fs)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
const struct cred *cred = current_cred();
|
||
|
|
||
|
security_learn(GR_ID_LEARN_MSG, task->role->rolename, task->role->roletype,
|
||
|
GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid), task->exec_file ? gr_to_filename1(task->exec_file->f_path.dentry,
|
||
|
task->exec_file->f_path.mnt) : task->acl->filename, task->acl->filename,
|
||
|
'g', GR_GLOBAL_GID(real), GR_GLOBAL_GID(effective), GR_GLOBAL_GID(fs), &task->signal->saved_ip);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gr_set_proc_res(struct task_struct *task)
|
||
|
{
|
||
|
struct acl_subject_label *proc;
|
||
|
unsigned short i;
|
||
|
|
||
|
proc = task->acl;
|
||
|
|
||
|
if (proc->mode & (GR_LEARN | GR_INHERITLEARN))
|
||
|
return;
|
||
|
|
||
|
for (i = 0; i < RLIM_NLIMITS; i++) {
|
||
|
unsigned long rlim_cur, rlim_max;
|
||
|
|
||
|
if (!(proc->resmask & (1U << i)))
|
||
|
continue;
|
||
|
|
||
|
rlim_cur = proc->res[i].rlim_cur;
|
||
|
rlim_max = proc->res[i].rlim_max;
|
||
|
|
||
|
if (i == RLIMIT_NOFILE) {
|
||
|
unsigned long saved_sysctl_nr_open = sysctl_nr_open;
|
||
|
if (rlim_cur > saved_sysctl_nr_open)
|
||
|
rlim_cur = saved_sysctl_nr_open;
|
||
|
if (rlim_max > saved_sysctl_nr_open)
|
||
|
rlim_max = saved_sysctl_nr_open;
|
||
|
}
|
||
|
|
||
|
task->signal->rlim[i].rlim_cur = rlim_cur;
|
||
|
task->signal->rlim[i].rlim_max = rlim_max;
|
||
|
|
||
|
if (i == RLIMIT_CPU)
|
||
|
update_rlimit_cpu(task, rlim_cur);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* both of the below must be called with
|
||
|
rcu_read_lock();
|
||
|
read_lock(&tasklist_lock);
|
||
|
read_lock(&grsec_exec_file_lock);
|
||
|
except in the case of gr_set_role_label() (for __gr_get_subject_for_task)
|
||
|
*/
|
||
|
|
||
|
struct acl_subject_label *__gr_get_subject_for_task(const struct gr_policy_state *state, struct task_struct *task, const char *filename, int fallback)
|
||
|
{
|
||
|
char *tmpname;
|
||
|
struct acl_subject_label *tmpsubj;
|
||
|
struct file *filp;
|
||
|
struct name_entry *nmatch;
|
||
|
|
||
|
filp = task->exec_file;
|
||
|
if (filp == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
/* the following is to apply the correct subject
|
||
|
on binaries running when the RBAC system
|
||
|
is enabled, when the binaries have been
|
||
|
replaced or deleted since their execution
|
||
|
-----
|
||
|
when the RBAC system starts, the inode/dev
|
||
|
from exec_file will be one the RBAC system
|
||
|
is unaware of. It only knows the inode/dev
|
||
|
of the present file on disk, or the absence
|
||
|
of it.
|
||
|
*/
|
||
|
|
||
|
if (filename)
|
||
|
nmatch = __lookup_name_entry(state, filename);
|
||
|
else {
|
||
|
preempt_disable();
|
||
|
tmpname = gr_to_filename_rbac(filp->f_path.dentry, filp->f_path.mnt);
|
||
|
|
||
|
nmatch = __lookup_name_entry(state, tmpname);
|
||
|
preempt_enable();
|
||
|
}
|
||
|
tmpsubj = NULL;
|
||
|
if (nmatch) {
|
||
|
if (nmatch->deleted)
|
||
|
tmpsubj = lookup_acl_subj_label_deleted(nmatch->inode, nmatch->device, task->role);
|
||
|
else
|
||
|
tmpsubj = lookup_acl_subj_label(nmatch->inode, nmatch->device, task->role);
|
||
|
}
|
||
|
/* this also works for the reload case -- if we don't match a potentially inherited subject
|
||
|
then we fall back to a normal lookup based on the binary's ino/dev
|
||
|
*/
|
||
|
if (tmpsubj == NULL && fallback)
|
||
|
tmpsubj = chk_subj_label(filp->f_path.dentry, filp->f_path.mnt, task->role);
|
||
|
|
||
|
return tmpsubj;
|
||
|
}
|
||
|
|
||
|
static struct acl_subject_label *gr_get_subject_for_task(struct task_struct *task, const char *filename, int fallback)
|
||
|
{
|
||
|
return __gr_get_subject_for_task(&running_polstate, task, filename, fallback);
|
||
|
}
|
||
|
|
||
|
void __gr_apply_subject_to_task(const struct gr_policy_state *state, struct task_struct *task, struct acl_subject_label *subj)
|
||
|
{
|
||
|
struct acl_object_label *obj;
|
||
|
struct file *filp;
|
||
|
|
||
|
filp = task->exec_file;
|
||
|
|
||
|
task->acl = subj;
|
||
|
task->is_writable = 0;
|
||
|
/* ignore additional mmap checks for processes that are writable
|
||
|
by the default ACL */
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, state->default_role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
task->is_writable = 1;
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, task->role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
task->is_writable = 1;
|
||
|
|
||
|
gr_set_proc_res(task);
|
||
|
|
||
|
#ifdef CONFIG_GRKERNSEC_RBAC_DEBUG
|
||
|
printk(KERN_ALERT "gr_set_acls for (%s:%d): role:%s, subject:%s\n", task->comm, task_pid_nr(task), task->role->rolename, task->acl->filename);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void gr_apply_subject_to_task(struct task_struct *task, struct acl_subject_label *subj)
|
||
|
{
|
||
|
__gr_apply_subject_to_task(&running_polstate, task, subj);
|
||
|
}
|
||
|
|
||
|
__u32
|
||
|
gr_search_file(const struct dentry * dentry, const __u32 mode,
|
||
|
const struct vfsmount * mnt)
|
||
|
{
|
||
|
__u32 retval = mode;
|
||
|
struct acl_subject_label *curracl;
|
||
|
struct acl_object_label *currobj;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return (mode & ~GR_AUDITS);
|
||
|
|
||
|
curracl = current->acl;
|
||
|
|
||
|
currobj = chk_obj_label(dentry, mnt, curracl);
|
||
|
retval = currobj->mode & mode;
|
||
|
|
||
|
/* if we're opening a specified transfer file for writing
|
||
|
(e.g. /dev/initctl), then transfer our role to init
|
||
|
*/
|
||
|
if (unlikely(currobj->mode & GR_INIT_TRANSFER && retval & GR_WRITE &&
|
||
|
current->role->roletype & GR_ROLE_PERSIST)) {
|
||
|
struct task_struct *task = init_pid_ns.child_reaper;
|
||
|
|
||
|
if (task->role != current->role) {
|
||
|
struct acl_subject_label *subj;
|
||
|
|
||
|
task->acl_sp_role = 0;
|
||
|
task->acl_role_id = current->acl_role_id;
|
||
|
task->role = current->role;
|
||
|
rcu_read_lock();
|
||
|
read_lock(&grsec_exec_file_lock);
|
||
|
subj = gr_get_subject_for_task(task, NULL, 1);
|
||
|
gr_apply_subject_to_task(task, subj);
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
rcu_read_unlock();
|
||
|
gr_log_noargs(GR_DONT_AUDIT_GOOD, GR_INIT_TRANSFER_MSG);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (unlikely
|
||
|
((curracl->mode & (GR_LEARN | GR_INHERITLEARN)) && !(mode & GR_NOPTRACE)
|
||
|
&& (retval != (mode & ~(GR_AUDITS | GR_SUPPRESS))))) {
|
||
|
__u32 new_mode = mode;
|
||
|
|
||
|
new_mode &= ~(GR_AUDITS | GR_SUPPRESS);
|
||
|
|
||
|
retval = new_mode;
|
||
|
|
||
|
if (new_mode & GR_EXEC && curracl->mode & GR_INHERITLEARN)
|
||
|
new_mode |= GR_INHERIT;
|
||
|
|
||
|
if (!(mode & GR_NOLEARN))
|
||
|
gr_log_learn(dentry, mnt, new_mode);
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
struct acl_object_label *gr_get_create_object(const struct dentry *new_dentry,
|
||
|
const struct dentry *parent,
|
||
|
const struct vfsmount *mnt)
|
||
|
{
|
||
|
struct name_entry *match;
|
||
|
struct acl_object_label *matchpo;
|
||
|
struct acl_subject_label *curracl;
|
||
|
char *path;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return NULL;
|
||
|
|
||
|
preempt_disable();
|
||
|
path = gr_to_filename_rbac(new_dentry, mnt);
|
||
|
match = lookup_name_entry_create(path);
|
||
|
|
||
|
curracl = current->acl;
|
||
|
|
||
|
if (match) {
|
||
|
read_lock(&gr_inode_lock);
|
||
|
matchpo = lookup_acl_obj_label_create(match->inode, match->device, curracl);
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
|
||
|
if (matchpo) {
|
||
|
preempt_enable();
|
||
|
return matchpo;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// lookup parent
|
||
|
|
||
|
matchpo = chk_obj_create_label(parent, mnt, curracl, path);
|
||
|
|
||
|
preempt_enable();
|
||
|
return matchpo;
|
||
|
}
|
||
|
|
||
|
__u32
|
||
|
gr_check_create(const struct dentry * new_dentry, const struct dentry * parent,
|
||
|
const struct vfsmount * mnt, const __u32 mode)
|
||
|
{
|
||
|
struct acl_object_label *matchpo;
|
||
|
__u32 retval;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return (mode & ~GR_AUDITS);
|
||
|
|
||
|
matchpo = gr_get_create_object(new_dentry, parent, mnt);
|
||
|
|
||
|
retval = matchpo->mode & mode;
|
||
|
|
||
|
if ((retval != (mode & ~(GR_AUDITS | GR_SUPPRESS)))
|
||
|
&& (current->acl->mode & (GR_LEARN | GR_INHERITLEARN))) {
|
||
|
__u32 new_mode = mode;
|
||
|
|
||
|
new_mode &= ~(GR_AUDITS | GR_SUPPRESS);
|
||
|
|
||
|
gr_log_learn(new_dentry, mnt, new_mode);
|
||
|
return new_mode;
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
__u32
|
||
|
gr_check_link(const struct dentry * new_dentry,
|
||
|
const struct dentry * parent_dentry,
|
||
|
const struct vfsmount * parent_mnt,
|
||
|
const struct dentry * old_dentry, const struct vfsmount * old_mnt)
|
||
|
{
|
||
|
struct acl_object_label *obj;
|
||
|
__u32 oldmode, newmode;
|
||
|
__u32 needmode;
|
||
|
__u32 checkmodes = GR_FIND | GR_APPEND | GR_WRITE | GR_EXEC | GR_SETID | GR_READ |
|
||
|
GR_DELETE | GR_INHERIT;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return (GR_CREATE | GR_LINK);
|
||
|
|
||
|
obj = chk_obj_label(old_dentry, old_mnt, current->acl);
|
||
|
oldmode = obj->mode;
|
||
|
|
||
|
obj = gr_get_create_object(new_dentry, parent_dentry, parent_mnt);
|
||
|
newmode = obj->mode;
|
||
|
|
||
|
needmode = newmode & checkmodes;
|
||
|
|
||
|
// old name for hardlink must have at least the permissions of the new name
|
||
|
if ((oldmode & needmode) != needmode)
|
||
|
goto bad;
|
||
|
|
||
|
// if old name had restrictions/auditing, make sure the new name does as well
|
||
|
needmode = oldmode & (GR_NOPTRACE | GR_PTRACERD | GR_INHERIT | GR_AUDITS);
|
||
|
|
||
|
// don't allow hardlinking of suid/sgid/fcapped files without permission
|
||
|
if (is_privileged_binary(old_dentry))
|
||
|
needmode |= GR_SETID;
|
||
|
|
||
|
if ((newmode & needmode) != needmode)
|
||
|
goto bad;
|
||
|
|
||
|
// enforce minimum permissions
|
||
|
if ((newmode & (GR_CREATE | GR_LINK)) == (GR_CREATE | GR_LINK))
|
||
|
return newmode;
|
||
|
bad:
|
||
|
needmode = oldmode;
|
||
|
if (is_privileged_binary(old_dentry))
|
||
|
needmode |= GR_SETID;
|
||
|
|
||
|
if (current->acl->mode & (GR_LEARN | GR_INHERITLEARN)) {
|
||
|
gr_log_learn(old_dentry, old_mnt, needmode | GR_CREATE | GR_LINK);
|
||
|
return (GR_CREATE | GR_LINK);
|
||
|
} else if (newmode & GR_SUPPRESS)
|
||
|
return GR_SUPPRESS;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_check_hidden_task(const struct task_struct *task)
|
||
|
{
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
|
||
|
if (!(task->acl->mode & GR_PROCFIND) && !(current->acl->mode & GR_VIEW))
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_check_protected_task(const struct task_struct *task)
|
||
|
{
|
||
|
if (unlikely(!(gr_status & GR_READY) || !task))
|
||
|
return 0;
|
||
|
|
||
|
if ((task->acl->mode & GR_PROTECTED) && !(current->acl->mode & GR_KILL) &&
|
||
|
task->acl != current->acl)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_check_protected_task_fowner(struct pid *pid, enum pid_type type)
|
||
|
{
|
||
|
struct task_struct *p;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY) || !pid))
|
||
|
return ret;
|
||
|
|
||
|
read_lock(&tasklist_lock);
|
||
|
do_each_pid_task(pid, type, p) {
|
||
|
if ((p->acl->mode & GR_PROTECTED) && !(current->acl->mode & GR_KILL) &&
|
||
|
p->acl != current->acl) {
|
||
|
ret = 1;
|
||
|
goto out;
|
||
|
}
|
||
|
} while_each_pid_task(pid, type, p);
|
||
|
out:
|
||
|
read_unlock(&tasklist_lock);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gr_copy_label(struct task_struct *tsk)
|
||
|
{
|
||
|
struct task_struct *p = current;
|
||
|
|
||
|
tsk->inherited = p->inherited;
|
||
|
tsk->acl_sp_role = 0;
|
||
|
tsk->acl_role_id = p->acl_role_id;
|
||
|
tsk->acl = p->acl;
|
||
|
tsk->role = p->role;
|
||
|
tsk->signal->used_accept = 0;
|
||
|
tsk->signal->curr_ip = p->signal->curr_ip;
|
||
|
tsk->signal->saved_ip = p->signal->saved_ip;
|
||
|
if (p->exec_file)
|
||
|
get_file(p->exec_file);
|
||
|
tsk->exec_file = p->exec_file;
|
||
|
tsk->is_writable = p->is_writable;
|
||
|
if (unlikely(p->signal->used_accept)) {
|
||
|
p->signal->curr_ip = 0;
|
||
|
p->signal->saved_ip = 0;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
extern int gr_process_kernel_setuid_ban(struct user_struct *user);
|
||
|
|
||
|
int
|
||
|
gr_check_user_change(kuid_t real, kuid_t effective, kuid_t fs)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
__u16 num;
|
||
|
uid_t *uidlist;
|
||
|
uid_t curuid;
|
||
|
int realok = 0;
|
||
|
int effectiveok = 0;
|
||
|
int fsok = 0;
|
||
|
uid_t globalreal, globaleffective, globalfs;
|
||
|
|
||
|
#if defined(CONFIG_GRKERNSEC_KERN_LOCKOUT)
|
||
|
struct user_struct *user;
|
||
|
|
||
|
if (!uid_valid(real))
|
||
|
goto skipit;
|
||
|
|
||
|
/* find user based on global namespace */
|
||
|
|
||
|
globalreal = GR_GLOBAL_UID(real);
|
||
|
|
||
|
user = find_user(make_kuid(&init_user_ns, globalreal));
|
||
|
if (user == NULL)
|
||
|
goto skipit;
|
||
|
|
||
|
if (gr_process_kernel_setuid_ban(user)) {
|
||
|
/* for find_user */
|
||
|
free_uid(user);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* for find_user */
|
||
|
free_uid(user);
|
||
|
|
||
|
skipit:
|
||
|
#endif
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
|
||
|
if (current->acl->mode & (GR_LEARN | GR_INHERITLEARN))
|
||
|
gr_log_learn_uid_change(real, effective, fs);
|
||
|
|
||
|
num = current->acl->user_trans_num;
|
||
|
uidlist = current->acl->user_transitions;
|
||
|
|
||
|
if (uidlist == NULL)
|
||
|
return 0;
|
||
|
|
||
|
if (!uid_valid(real)) {
|
||
|
realok = 1;
|
||
|
globalreal = (uid_t)-1;
|
||
|
} else {
|
||
|
globalreal = GR_GLOBAL_UID(real);
|
||
|
}
|
||
|
if (!uid_valid(effective)) {
|
||
|
effectiveok = 1;
|
||
|
globaleffective = (uid_t)-1;
|
||
|
} else {
|
||
|
globaleffective = GR_GLOBAL_UID(effective);
|
||
|
}
|
||
|
if (!uid_valid(fs)) {
|
||
|
fsok = 1;
|
||
|
globalfs = (uid_t)-1;
|
||
|
} else {
|
||
|
globalfs = GR_GLOBAL_UID(fs);
|
||
|
}
|
||
|
|
||
|
if (current->acl->user_trans_type & GR_ID_ALLOW) {
|
||
|
for (i = 0; i < num; i++) {
|
||
|
curuid = uidlist[i];
|
||
|
if (globalreal == curuid)
|
||
|
realok = 1;
|
||
|
if (globaleffective == curuid)
|
||
|
effectiveok = 1;
|
||
|
if (globalfs == curuid)
|
||
|
fsok = 1;
|
||
|
}
|
||
|
} else if (current->acl->user_trans_type & GR_ID_DENY) {
|
||
|
for (i = 0; i < num; i++) {
|
||
|
curuid = uidlist[i];
|
||
|
if (globalreal == curuid)
|
||
|
break;
|
||
|
if (globaleffective == curuid)
|
||
|
break;
|
||
|
if (globalfs == curuid)
|
||
|
break;
|
||
|
}
|
||
|
/* not in deny list */
|
||
|
if (i == num) {
|
||
|
realok = 1;
|
||
|
effectiveok = 1;
|
||
|
fsok = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (realok && effectiveok && fsok)
|
||
|
return 0;
|
||
|
else {
|
||
|
gr_log_int(GR_DONT_AUDIT, GR_USRCHANGE_ACL_MSG, realok ? (effectiveok ? (fsok ? 0 : globalfs) : globaleffective) : globalreal);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_check_group_change(kgid_t real, kgid_t effective, kgid_t fs)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
__u16 num;
|
||
|
gid_t *gidlist;
|
||
|
gid_t curgid;
|
||
|
int realok = 0;
|
||
|
int effectiveok = 0;
|
||
|
int fsok = 0;
|
||
|
gid_t globalreal, globaleffective, globalfs;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
|
||
|
if (current->acl->mode & (GR_LEARN | GR_INHERITLEARN))
|
||
|
gr_log_learn_gid_change(real, effective, fs);
|
||
|
|
||
|
num = current->acl->group_trans_num;
|
||
|
gidlist = current->acl->group_transitions;
|
||
|
|
||
|
if (gidlist == NULL)
|
||
|
return 0;
|
||
|
|
||
|
if (!gid_valid(real)) {
|
||
|
realok = 1;
|
||
|
globalreal = (gid_t)-1;
|
||
|
} else {
|
||
|
globalreal = GR_GLOBAL_GID(real);
|
||
|
}
|
||
|
if (!gid_valid(effective)) {
|
||
|
effectiveok = 1;
|
||
|
globaleffective = (gid_t)-1;
|
||
|
} else {
|
||
|
globaleffective = GR_GLOBAL_GID(effective);
|
||
|
}
|
||
|
if (!gid_valid(fs)) {
|
||
|
fsok = 1;
|
||
|
globalfs = (gid_t)-1;
|
||
|
} else {
|
||
|
globalfs = GR_GLOBAL_GID(fs);
|
||
|
}
|
||
|
|
||
|
if (current->acl->group_trans_type & GR_ID_ALLOW) {
|
||
|
for (i = 0; i < num; i++) {
|
||
|
curgid = gidlist[i];
|
||
|
if (globalreal == curgid)
|
||
|
realok = 1;
|
||
|
if (globaleffective == curgid)
|
||
|
effectiveok = 1;
|
||
|
if (globalfs == curgid)
|
||
|
fsok = 1;
|
||
|
}
|
||
|
} else if (current->acl->group_trans_type & GR_ID_DENY) {
|
||
|
for (i = 0; i < num; i++) {
|
||
|
curgid = gidlist[i];
|
||
|
if (globalreal == curgid)
|
||
|
break;
|
||
|
if (globaleffective == curgid)
|
||
|
break;
|
||
|
if (globalfs == curgid)
|
||
|
break;
|
||
|
}
|
||
|
/* not in deny list */
|
||
|
if (i == num) {
|
||
|
realok = 1;
|
||
|
effectiveok = 1;
|
||
|
fsok = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (realok && effectiveok && fsok)
|
||
|
return 0;
|
||
|
else {
|
||
|
gr_log_int(GR_DONT_AUDIT, GR_GRPCHANGE_ACL_MSG, realok ? (effectiveok ? (fsok ? 0 : globalfs) : globaleffective) : globalreal);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern int gr_acl_is_capable(const int cap);
|
||
|
|
||
|
void
|
||
|
gr_set_role_label(struct task_struct *task, const kuid_t kuid, const kgid_t kgid)
|
||
|
{
|
||
|
struct acl_role_label *role = task->role;
|
||
|
struct acl_role_label *origrole = role;
|
||
|
struct acl_subject_label *subj = NULL;
|
||
|
struct acl_object_label *obj;
|
||
|
struct file *filp;
|
||
|
uid_t uid;
|
||
|
gid_t gid;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
uid = GR_GLOBAL_UID(kuid);
|
||
|
gid = GR_GLOBAL_GID(kgid);
|
||
|
|
||
|
filp = task->exec_file;
|
||
|
|
||
|
/* kernel process, we'll give them the kernel role */
|
||
|
if (unlikely(!filp)) {
|
||
|
task->role = running_polstate.kernel_role;
|
||
|
task->acl = running_polstate.kernel_role->root_label;
|
||
|
return;
|
||
|
} else if (!task->role || !(task->role->roletype & GR_ROLE_SPECIAL)) {
|
||
|
/* save the current ip at time of role lookup so that the proper
|
||
|
IP will be learned for role_allowed_ip */
|
||
|
task->signal->saved_ip = task->signal->curr_ip;
|
||
|
role = lookup_acl_role_label(task, uid, gid);
|
||
|
}
|
||
|
|
||
|
/* don't change the role if we're not a privileged process */
|
||
|
if (role && task->role != role &&
|
||
|
(((role->roletype & GR_ROLE_USER) && !gr_acl_is_capable(CAP_SETUID)) ||
|
||
|
((role->roletype & GR_ROLE_GROUP) && !gr_acl_is_capable(CAP_SETGID))))
|
||
|
return;
|
||
|
|
||
|
task->role = role;
|
||
|
|
||
|
if (task->inherited) {
|
||
|
/* if we reached our subject through inheritance, then first see
|
||
|
if there's a subject of the same name in the new role that has
|
||
|
an object that would result in the same inherited subject
|
||
|
*/
|
||
|
subj = gr_get_subject_for_task(task, task->acl->filename, 0);
|
||
|
if (subj) {
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, subj);
|
||
|
if (!(obj->mode & GR_INHERIT))
|
||
|
subj = NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
if (subj == NULL) {
|
||
|
/* otherwise:
|
||
|
perform subject lookup in possibly new role
|
||
|
we can use this result below in the case where role == task->role
|
||
|
*/
|
||
|
subj = chk_subj_label(filp->f_path.dentry, filp->f_path.mnt, role);
|
||
|
}
|
||
|
|
||
|
/* if we changed uid/gid, but result in the same role
|
||
|
and are using inheritance, don't lose the inherited subject
|
||
|
if current subject is other than what normal lookup
|
||
|
would result in, we arrived via inheritance, don't
|
||
|
lose subject
|
||
|
*/
|
||
|
if (role != origrole || (!(task->acl->mode & GR_INHERITLEARN) &&
|
||
|
(subj == task->acl)))
|
||
|
task->acl = subj;
|
||
|
|
||
|
/* leave task->inherited unaffected */
|
||
|
|
||
|
task->is_writable = 0;
|
||
|
|
||
|
/* ignore additional mmap checks for processes that are writable
|
||
|
by the default ACL */
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, running_polstate.default_role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
task->is_writable = 1;
|
||
|
obj = chk_obj_label(filp->f_path.dentry, filp->f_path.mnt, task->role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
task->is_writable = 1;
|
||
|
|
||
|
#ifdef CONFIG_GRKERNSEC_RBAC_DEBUG
|
||
|
printk(KERN_ALERT "Set role label for (%s:%d): role:%s, subject:%s\n", task->comm, task_pid_nr(task), task->role->rolename, task->acl->filename);
|
||
|
#endif
|
||
|
|
||
|
gr_set_proc_res(task);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_set_proc_label(const struct dentry *dentry, const struct vfsmount *mnt,
|
||
|
const int unsafe_flags)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
struct acl_subject_label *newacl;
|
||
|
struct acl_object_label *obj;
|
||
|
__u32 retmode;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
|
||
|
newacl = chk_subj_label(dentry, mnt, task->role);
|
||
|
|
||
|
/* special handling for if we did an strace -f -p <pid> from an admin role, where pid then
|
||
|
did an exec
|
||
|
*/
|
||
|
rcu_read_lock();
|
||
|
read_lock(&tasklist_lock);
|
||
|
if (task->ptrace && task->parent && ((task->parent->role->roletype & GR_ROLE_GOD) ||
|
||
|
(task->parent->acl->mode & GR_POVERRIDE))) {
|
||
|
read_unlock(&tasklist_lock);
|
||
|
rcu_read_unlock();
|
||
|
goto skip_check;
|
||
|
}
|
||
|
read_unlock(&tasklist_lock);
|
||
|
rcu_read_unlock();
|
||
|
|
||
|
if (unsafe_flags && !(task->acl->mode & GR_POVERRIDE) && (task->acl != newacl) &&
|
||
|
!(task->role->roletype & GR_ROLE_GOD) &&
|
||
|
!gr_search_file(dentry, GR_PTRACERD, mnt) &&
|
||
|
!(task->acl->mode & (GR_LEARN | GR_INHERITLEARN))) {
|
||
|
if (unsafe_flags & LSM_UNSAFE_SHARE)
|
||
|
gr_log_fs_generic(GR_DONT_AUDIT, GR_UNSAFESHARE_EXEC_ACL_MSG, dentry, mnt);
|
||
|
else if (unsafe_flags & (LSM_UNSAFE_PTRACE_CAP | LSM_UNSAFE_PTRACE))
|
||
|
gr_log_fs_generic(GR_DONT_AUDIT, GR_PTRACE_EXEC_ACL_MSG, dentry, mnt);
|
||
|
else
|
||
|
gr_log_fs_generic(GR_DONT_AUDIT, GR_NNP_EXEC_ACL_MSG, dentry, mnt);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
skip_check:
|
||
|
|
||
|
obj = chk_obj_label(dentry, mnt, task->acl);
|
||
|
retmode = obj->mode & (GR_INHERIT | GR_AUDIT_INHERIT);
|
||
|
|
||
|
if (!(task->acl->mode & GR_INHERITLEARN) &&
|
||
|
((newacl->mode & GR_LEARN) || !(retmode & GR_INHERIT))) {
|
||
|
if (obj->nested)
|
||
|
task->acl = obj->nested;
|
||
|
else
|
||
|
task->acl = newacl;
|
||
|
task->inherited = 0;
|
||
|
} else {
|
||
|
task->inherited = 1;
|
||
|
if (retmode & GR_INHERIT && retmode & GR_AUDIT_INHERIT)
|
||
|
gr_log_str_fs(GR_DO_AUDIT, GR_INHERIT_ACL_MSG, task->acl->filename, dentry, mnt);
|
||
|
}
|
||
|
|
||
|
task->is_writable = 0;
|
||
|
|
||
|
/* ignore additional mmap checks for processes that are writable
|
||
|
by the default ACL */
|
||
|
obj = chk_obj_label(dentry, mnt, running_polstate.default_role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
task->is_writable = 1;
|
||
|
obj = chk_obj_label(dentry, mnt, task->role->root_label);
|
||
|
if (unlikely(obj->mode & GR_WRITE))
|
||
|
task->is_writable = 1;
|
||
|
|
||
|
gr_set_proc_res(task);
|
||
|
|
||
|
#ifdef CONFIG_GRKERNSEC_RBAC_DEBUG
|
||
|
printk(KERN_ALERT "Set subject label for (%s:%d): role:%s, subject:%s\n", task->comm, task_pid_nr(task), task->role->rolename, task->acl->filename);
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* always called with valid inodev ptr */
|
||
|
static void
|
||
|
do_handle_delete(struct inodev_entry *inodev, const u64 ino, const dev_t dev)
|
||
|
{
|
||
|
struct acl_object_label *matchpo;
|
||
|
struct acl_subject_label *matchps;
|
||
|
struct acl_subject_label *subj;
|
||
|
struct acl_role_label *role;
|
||
|
unsigned int x;
|
||
|
|
||
|
FOR_EACH_ROLE_START(role)
|
||
|
FOR_EACH_SUBJECT_START(role, subj, x)
|
||
|
if ((matchpo = lookup_acl_obj_label(ino, dev, subj)) != NULL)
|
||
|
matchpo->mode |= GR_DELETED;
|
||
|
FOR_EACH_SUBJECT_END(subj,x)
|
||
|
FOR_EACH_NESTED_SUBJECT_START(role, subj)
|
||
|
/* nested subjects aren't in the role's subj_hash table */
|
||
|
if ((matchpo = lookup_acl_obj_label(ino, dev, subj)) != NULL)
|
||
|
matchpo->mode |= GR_DELETED;
|
||
|
FOR_EACH_NESTED_SUBJECT_END(subj)
|
||
|
if ((matchps = lookup_acl_subj_label(ino, dev, role)) != NULL)
|
||
|
matchps->mode |= GR_DELETED;
|
||
|
FOR_EACH_ROLE_END(role)
|
||
|
|
||
|
inodev->nentry->deleted = 1;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gr_handle_delete(const u64 ino, const dev_t dev)
|
||
|
{
|
||
|
struct inodev_entry *inodev;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
write_lock(&gr_inode_lock);
|
||
|
inodev = lookup_inodev_entry(ino, dev);
|
||
|
if (inodev != NULL)
|
||
|
do_handle_delete(inodev, ino, dev);
|
||
|
write_unlock(&gr_inode_lock);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_acl_obj_label(const u64 oldinode, const dev_t olddevice,
|
||
|
const u64 newinode, const dev_t newdevice,
|
||
|
struct acl_subject_label *subj)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(oldinode, olddevice, subj->obj_hash_size);
|
||
|
struct acl_object_label *match;
|
||
|
|
||
|
match = subj->obj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != oldinode ||
|
||
|
match->device != olddevice ||
|
||
|
!(match->mode & GR_DELETED)))
|
||
|
match = match->next;
|
||
|
|
||
|
if (match && (match->inode == oldinode)
|
||
|
&& (match->device == olddevice)
|
||
|
&& (match->mode & GR_DELETED)) {
|
||
|
if (match->prev == NULL) {
|
||
|
subj->obj_hash[index] = match->next;
|
||
|
if (match->next != NULL)
|
||
|
match->next->prev = NULL;
|
||
|
} else {
|
||
|
match->prev->next = match->next;
|
||
|
if (match->next != NULL)
|
||
|
match->next->prev = match->prev;
|
||
|
}
|
||
|
match->prev = NULL;
|
||
|
match->next = NULL;
|
||
|
match->inode = newinode;
|
||
|
match->device = newdevice;
|
||
|
match->mode &= ~GR_DELETED;
|
||
|
|
||
|
insert_acl_obj_label(match, subj);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_acl_subj_label(const u64 oldinode, const dev_t olddevice,
|
||
|
const u64 newinode, const dev_t newdevice,
|
||
|
struct acl_role_label *role)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(oldinode, olddevice, role->subj_hash_size);
|
||
|
struct acl_subject_label *match;
|
||
|
|
||
|
match = role->subj_hash[index];
|
||
|
|
||
|
while (match && (match->inode != oldinode ||
|
||
|
match->device != olddevice ||
|
||
|
!(match->mode & GR_DELETED)))
|
||
|
match = match->next;
|
||
|
|
||
|
if (match && (match->inode == oldinode)
|
||
|
&& (match->device == olddevice)
|
||
|
&& (match->mode & GR_DELETED)) {
|
||
|
if (match->prev == NULL) {
|
||
|
role->subj_hash[index] = match->next;
|
||
|
if (match->next != NULL)
|
||
|
match->next->prev = NULL;
|
||
|
} else {
|
||
|
match->prev->next = match->next;
|
||
|
if (match->next != NULL)
|
||
|
match->next->prev = match->prev;
|
||
|
}
|
||
|
match->prev = NULL;
|
||
|
match->next = NULL;
|
||
|
match->inode = newinode;
|
||
|
match->device = newdevice;
|
||
|
match->mode &= ~GR_DELETED;
|
||
|
|
||
|
insert_acl_subj_label(match, role);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
update_inodev_entry(const u64 oldinode, const dev_t olddevice,
|
||
|
const u64 newinode, const dev_t newdevice)
|
||
|
{
|
||
|
unsigned int index = gr_fhash(oldinode, olddevice, running_polstate.inodev_set.i_size);
|
||
|
struct inodev_entry *match;
|
||
|
|
||
|
match = running_polstate.inodev_set.i_hash[index];
|
||
|
|
||
|
while (match && (match->nentry->inode != oldinode ||
|
||
|
match->nentry->device != olddevice || !match->nentry->deleted))
|
||
|
match = match->next;
|
||
|
|
||
|
if (match && (match->nentry->inode == oldinode)
|
||
|
&& (match->nentry->device == olddevice) &&
|
||
|
match->nentry->deleted) {
|
||
|
if (match->prev == NULL) {
|
||
|
running_polstate.inodev_set.i_hash[index] = match->next;
|
||
|
if (match->next != NULL)
|
||
|
match->next->prev = NULL;
|
||
|
} else {
|
||
|
match->prev->next = match->next;
|
||
|
if (match->next != NULL)
|
||
|
match->next->prev = match->prev;
|
||
|
}
|
||
|
match->prev = NULL;
|
||
|
match->next = NULL;
|
||
|
match->nentry->inode = newinode;
|
||
|
match->nentry->device = newdevice;
|
||
|
match->nentry->deleted = 0;
|
||
|
|
||
|
insert_inodev_entry(match);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
__do_handle_create(const struct name_entry *matchn, u64 ino, dev_t dev)
|
||
|
{
|
||
|
struct acl_subject_label *subj;
|
||
|
struct acl_role_label *role;
|
||
|
unsigned int x;
|
||
|
|
||
|
FOR_EACH_ROLE_START(role)
|
||
|
update_acl_subj_label(matchn->inode, matchn->device, ino, dev, role);
|
||
|
|
||
|
FOR_EACH_NESTED_SUBJECT_START(role, subj)
|
||
|
if ((subj->inode == ino) && (subj->device == dev)) {
|
||
|
subj->inode = ino;
|
||
|
subj->device = dev;
|
||
|
}
|
||
|
/* nested subjects aren't in the role's subj_hash table */
|
||
|
update_acl_obj_label(matchn->inode, matchn->device,
|
||
|
ino, dev, subj);
|
||
|
FOR_EACH_NESTED_SUBJECT_END(subj)
|
||
|
FOR_EACH_SUBJECT_START(role, subj, x)
|
||
|
update_acl_obj_label(matchn->inode, matchn->device,
|
||
|
ino, dev, subj);
|
||
|
FOR_EACH_SUBJECT_END(subj,x)
|
||
|
FOR_EACH_ROLE_END(role)
|
||
|
|
||
|
update_inodev_entry(matchn->inode, matchn->device, ino, dev);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
do_handle_create(const struct name_entry *matchn, const struct dentry *dentry,
|
||
|
const struct vfsmount *mnt)
|
||
|
{
|
||
|
u64 ino = __get_ino(dentry);
|
||
|
dev_t dev = __get_dev(dentry);
|
||
|
|
||
|
__do_handle_create(matchn, ino, dev);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gr_handle_create(const struct dentry *dentry, const struct vfsmount *mnt)
|
||
|
{
|
||
|
struct name_entry *matchn;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
preempt_disable();
|
||
|
matchn = lookup_name_entry(gr_to_filename_rbac(dentry, mnt));
|
||
|
|
||
|
if (unlikely((unsigned long)matchn)) {
|
||
|
write_lock(&gr_inode_lock);
|
||
|
do_handle_create(matchn, dentry, mnt);
|
||
|
write_unlock(&gr_inode_lock);
|
||
|
}
|
||
|
preempt_enable();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gr_handle_proc_create(const struct dentry *dentry, const struct inode *inode)
|
||
|
{
|
||
|
struct name_entry *matchn;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
preempt_disable();
|
||
|
matchn = lookup_name_entry(gr_to_proc_filename_rbac(dentry, init_pid_ns.proc_mnt));
|
||
|
|
||
|
if (unlikely((unsigned long)matchn)) {
|
||
|
write_lock(&gr_inode_lock);
|
||
|
__do_handle_create(matchn, inode->i_ino, inode->i_sb->s_dev);
|
||
|
write_unlock(&gr_inode_lock);
|
||
|
}
|
||
|
preempt_enable();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gr_handle_rename(struct inode *old_dir, struct inode *new_dir,
|
||
|
struct dentry *old_dentry,
|
||
|
struct dentry *new_dentry,
|
||
|
struct vfsmount *mnt, const __u8 replace, unsigned int flags)
|
||
|
{
|
||
|
struct name_entry *matchn;
|
||
|
struct name_entry *matchn2 = NULL;
|
||
|
struct inodev_entry *inodev;
|
||
|
struct inode *inode = d_backing_inode(new_dentry);
|
||
|
struct inode *old_inode = d_backing_inode(old_dentry);
|
||
|
u64 old_ino = __get_ino(old_dentry);
|
||
|
dev_t old_dev = __get_dev(old_dentry);
|
||
|
unsigned int exchange = flags & RENAME_EXCHANGE;
|
||
|
|
||
|
/* vfs_rename swaps the name and parent link for old_dentry and
|
||
|
new_dentry
|
||
|
at this point, old_dentry has the new name, parent link, and inode
|
||
|
for the renamed file
|
||
|
if a file is being replaced by a rename, new_dentry has the inode
|
||
|
and name for the replaced file
|
||
|
*/
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
preempt_disable();
|
||
|
matchn = lookup_name_entry(gr_to_filename_rbac(old_dentry, mnt));
|
||
|
|
||
|
/* exchange cases:
|
||
|
a filename exists for the source, but not dest
|
||
|
do a recreate on source
|
||
|
a filename exists for the dest, but not source
|
||
|
do a recreate on dest
|
||
|
a filename exists for both source and dest
|
||
|
delete source and dest, then create source and dest
|
||
|
a filename exists for neither source nor dest
|
||
|
no updates needed
|
||
|
|
||
|
the name entry lookups get us the old inode/dev associated with
|
||
|
each name, so do the deletes first (if possible) so that when
|
||
|
we do the create, we pick up on the right entries
|
||
|
*/
|
||
|
|
||
|
if (exchange)
|
||
|
matchn2 = lookup_name_entry(gr_to_filename_rbac(new_dentry, mnt));
|
||
|
|
||
|
/* we wouldn't have to check d_inode if it weren't for
|
||
|
NFS silly-renaming
|
||
|
*/
|
||
|
|
||
|
write_lock(&gr_inode_lock);
|
||
|
if (unlikely((replace || exchange) && inode)) {
|
||
|
u64 new_ino = __get_ino(new_dentry);
|
||
|
dev_t new_dev = __get_dev(new_dentry);
|
||
|
|
||
|
inodev = lookup_inodev_entry(new_ino, new_dev);
|
||
|
if (inodev != NULL && ((inode->i_nlink <= 1) || d_is_dir(new_dentry)))
|
||
|
do_handle_delete(inodev, new_ino, new_dev);
|
||
|
}
|
||
|
|
||
|
inodev = lookup_inodev_entry(old_ino, old_dev);
|
||
|
if (inodev != NULL && ((old_inode->i_nlink <= 1) || d_is_dir(old_dentry)))
|
||
|
do_handle_delete(inodev, old_ino, old_dev);
|
||
|
|
||
|
if (unlikely(matchn != NULL))
|
||
|
do_handle_create(matchn, old_dentry, mnt);
|
||
|
|
||
|
if (unlikely(matchn2 != NULL))
|
||
|
do_handle_create(matchn2, new_dentry, mnt);
|
||
|
|
||
|
write_unlock(&gr_inode_lock);
|
||
|
preempt_enable();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_GRKERNSEC_RESLOG) || !defined(CONFIG_GRKERNSEC_NO_RBAC)
|
||
|
static const unsigned long res_learn_bumps[GR_NLIMITS] = {
|
||
|
[RLIMIT_CPU] = GR_RLIM_CPU_BUMP,
|
||
|
[RLIMIT_FSIZE] = GR_RLIM_FSIZE_BUMP,
|
||
|
[RLIMIT_DATA] = GR_RLIM_DATA_BUMP,
|
||
|
[RLIMIT_STACK] = GR_RLIM_STACK_BUMP,
|
||
|
[RLIMIT_CORE] = GR_RLIM_CORE_BUMP,
|
||
|
[RLIMIT_RSS] = GR_RLIM_RSS_BUMP,
|
||
|
[RLIMIT_NPROC] = GR_RLIM_NPROC_BUMP,
|
||
|
[RLIMIT_NOFILE] = GR_RLIM_NOFILE_BUMP,
|
||
|
[RLIMIT_MEMLOCK] = GR_RLIM_MEMLOCK_BUMP,
|
||
|
[RLIMIT_AS] = GR_RLIM_AS_BUMP,
|
||
|
[RLIMIT_LOCKS] = GR_RLIM_LOCKS_BUMP,
|
||
|
[RLIMIT_SIGPENDING] = GR_RLIM_SIGPENDING_BUMP,
|
||
|
[RLIMIT_MSGQUEUE] = GR_RLIM_MSGQUEUE_BUMP,
|
||
|
[RLIMIT_NICE] = GR_RLIM_NICE_BUMP,
|
||
|
[RLIMIT_RTPRIO] = GR_RLIM_RTPRIO_BUMP,
|
||
|
[RLIMIT_RTTIME] = GR_RLIM_RTTIME_BUMP
|
||
|
};
|
||
|
|
||
|
void
|
||
|
gr_learn_resource(const struct task_struct *task,
|
||
|
const int res, const unsigned long wanted, const int gt)
|
||
|
{
|
||
|
struct acl_subject_label *acl;
|
||
|
const struct cred *cred;
|
||
|
|
||
|
if (unlikely((gr_status & GR_READY) &&
|
||
|
task->acl && (task->acl->mode & (GR_LEARN | GR_INHERITLEARN))))
|
||
|
goto skip_reslog;
|
||
|
|
||
|
gr_log_resource(task, res, wanted, gt);
|
||
|
skip_reslog:
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY) || !wanted || res >= GR_NLIMITS))
|
||
|
return;
|
||
|
|
||
|
acl = task->acl;
|
||
|
|
||
|
if (likely(!acl || !(acl->mode & (GR_LEARN | GR_INHERITLEARN)) ||
|
||
|
!(acl->resmask & (1U << (unsigned short) res))))
|
||
|
return;
|
||
|
|
||
|
if (wanted >= acl->res[res].rlim_cur) {
|
||
|
unsigned long res_add;
|
||
|
|
||
|
res_add = wanted + res_learn_bumps[res];
|
||
|
|
||
|
acl->res[res].rlim_cur = res_add;
|
||
|
|
||
|
if (wanted > acl->res[res].rlim_max)
|
||
|
acl->res[res].rlim_max = res_add;
|
||
|
|
||
|
/* only log the subject filename, since resource logging is supported for
|
||
|
single-subject learning only */
|
||
|
rcu_read_lock();
|
||
|
cred = __task_cred(task);
|
||
|
security_learn(GR_LEARN_AUDIT_MSG, task->role->rolename,
|
||
|
task->role->roletype, GR_GLOBAL_UID(cred->uid), GR_GLOBAL_GID(cred->gid), acl->filename,
|
||
|
acl->filename, acl->res[res].rlim_cur, acl->res[res].rlim_max,
|
||
|
"", (unsigned long) res, &task->signal->saved_ip);
|
||
|
rcu_read_unlock();
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(gr_learn_resource);
|
||
|
#endif
|
||
|
|
||
|
#if defined(CONFIG_PAX_HAVE_ACL_FLAGS) && (defined(CONFIG_PAX_NOEXEC) || defined(CONFIG_PAX_ASLR))
|
||
|
void
|
||
|
pax_set_initial_flags(struct linux_binprm *bprm)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
struct acl_subject_label *proc;
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
flags = pax_get_flags(task);
|
||
|
|
||
|
proc = task->acl;
|
||
|
|
||
|
if (proc->pax_flags & GR_PAX_DISABLE_PAGEEXEC)
|
||
|
flags &= ~MF_PAX_PAGEEXEC;
|
||
|
if (proc->pax_flags & GR_PAX_DISABLE_SEGMEXEC)
|
||
|
flags &= ~MF_PAX_SEGMEXEC;
|
||
|
if (proc->pax_flags & GR_PAX_DISABLE_RANDMMAP)
|
||
|
flags &= ~MF_PAX_RANDMMAP;
|
||
|
if (proc->pax_flags & GR_PAX_DISABLE_EMUTRAMP)
|
||
|
flags &= ~MF_PAX_EMUTRAMP;
|
||
|
if (proc->pax_flags & GR_PAX_DISABLE_MPROTECT)
|
||
|
flags &= ~MF_PAX_MPROTECT;
|
||
|
|
||
|
if (proc->pax_flags & GR_PAX_ENABLE_PAGEEXEC)
|
||
|
flags |= MF_PAX_PAGEEXEC;
|
||
|
if (proc->pax_flags & GR_PAX_ENABLE_SEGMEXEC)
|
||
|
flags |= MF_PAX_SEGMEXEC;
|
||
|
if (proc->pax_flags & GR_PAX_ENABLE_RANDMMAP)
|
||
|
flags |= MF_PAX_RANDMMAP;
|
||
|
if (proc->pax_flags & GR_PAX_ENABLE_EMUTRAMP)
|
||
|
flags |= MF_PAX_EMUTRAMP;
|
||
|
if (proc->pax_flags & GR_PAX_ENABLE_MPROTECT)
|
||
|
flags |= MF_PAX_MPROTECT;
|
||
|
|
||
|
pax_set_flags(task, flags);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
int
|
||
|
gr_handle_proc_ptrace(struct task_struct *task)
|
||
|
{
|
||
|
struct file *filp;
|
||
|
struct task_struct *tmp = task;
|
||
|
struct task_struct *curtemp = current;
|
||
|
__u32 retmode;
|
||
|
|
||
|
#ifndef CONFIG_GRKERNSEC_HARDEN_PTRACE
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
#endif
|
||
|
|
||
|
read_lock(&tasklist_lock);
|
||
|
read_lock(&grsec_exec_file_lock);
|
||
|
filp = task->exec_file;
|
||
|
|
||
|
while (task_pid_nr(tmp) > 0) {
|
||
|
if (tmp == curtemp)
|
||
|
break;
|
||
|
tmp = tmp->real_parent;
|
||
|
}
|
||
|
|
||
|
if (!filp || (task_pid_nr(tmp) == 0 && ((grsec_enable_harden_ptrace && gr_is_global_nonroot(current_uid()) && !(gr_status & GR_READY)) ||
|
||
|
((gr_status & GR_READY) && !(current->acl->mode & GR_RELAXPTRACE))))) {
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
read_unlock(&tasklist_lock);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_GRKERNSEC_HARDEN_PTRACE
|
||
|
if (!(gr_status & GR_READY)) {
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
read_unlock(&tasklist_lock);
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
retmode = gr_search_file(filp->f_path.dentry, GR_NOPTRACE, filp->f_path.mnt);
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
read_unlock(&tasklist_lock);
|
||
|
|
||
|
if (retmode & GR_NOPTRACE)
|
||
|
return 1;
|
||
|
|
||
|
if (!(current->acl->mode & GR_POVERRIDE) && !(current->role->roletype & GR_ROLE_GOD)
|
||
|
&& (current->acl != task->acl || (current->acl != current->role->root_label
|
||
|
&& task_pid_nr(current) != task_pid_nr(task))))
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void task_grsec_rbac(struct seq_file *m, struct task_struct *p)
|
||
|
{
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return;
|
||
|
|
||
|
if (!(current->role->roletype & GR_ROLE_GOD))
|
||
|
return;
|
||
|
|
||
|
seq_printf(m, "RBAC:\t%.64s:%c:%.950s\n",
|
||
|
p->role->rolename, gr_task_roletype_to_char(p),
|
||
|
p->acl->filename);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_handle_ptrace(struct task_struct *task, const long request)
|
||
|
{
|
||
|
struct task_struct *tmp = task;
|
||
|
struct task_struct *curtemp = current;
|
||
|
__u32 retmode;
|
||
|
|
||
|
#ifndef CONFIG_GRKERNSEC_HARDEN_PTRACE
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
#endif
|
||
|
if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {
|
||
|
read_lock(&tasklist_lock);
|
||
|
while (task_pid_nr(tmp) > 0) {
|
||
|
if (tmp == curtemp)
|
||
|
break;
|
||
|
tmp = tmp->real_parent;
|
||
|
}
|
||
|
|
||
|
if (task_pid_nr(tmp) == 0 && ((grsec_enable_harden_ptrace && gr_is_global_nonroot(current_uid()) && !(gr_status & GR_READY)) ||
|
||
|
((gr_status & GR_READY) && !(current->acl->mode & GR_RELAXPTRACE)))) {
|
||
|
read_unlock(&tasklist_lock);
|
||
|
gr_log_ptrace(GR_DONT_AUDIT, GR_PTRACE_ACL_MSG, task);
|
||
|
return 1;
|
||
|
}
|
||
|
read_unlock(&tasklist_lock);
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_GRKERNSEC_HARDEN_PTRACE
|
||
|
if (!(gr_status & GR_READY))
|
||
|
return 0;
|
||
|
#endif
|
||
|
|
||
|
read_lock(&grsec_exec_file_lock);
|
||
|
if (unlikely(!task->exec_file)) {
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
retmode = gr_search_file(task->exec_file->f_path.dentry, GR_PTRACERD | GR_NOPTRACE, task->exec_file->f_path.mnt);
|
||
|
read_unlock(&grsec_exec_file_lock);
|
||
|
|
||
|
if (retmode & GR_NOPTRACE) {
|
||
|
gr_log_ptrace(GR_DONT_AUDIT, GR_PTRACE_ACL_MSG, task);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (retmode & GR_PTRACERD) {
|
||
|
switch (request) {
|
||
|
case PTRACE_SEIZE:
|
||
|
case PTRACE_POKETEXT:
|
||
|
case PTRACE_POKEDATA:
|
||
|
case PTRACE_POKEUSR:
|
||
|
#if !defined(CONFIG_PPC32) && !defined(CONFIG_PPC64) && !defined(CONFIG_PARISC) && !defined(CONFIG_ALPHA) && !defined(CONFIG_IA64) && !defined(CONFIG_ARM64)
|
||
|
case PTRACE_SETREGS:
|
||
|
case PTRACE_SETFPREGS:
|
||
|
#endif
|
||
|
#ifdef CONFIG_COMPAT
|
||
|
#ifdef CONFIG_ARM64
|
||
|
case COMPAT_PTRACE_SETREGS:
|
||
|
case COMPAT_PTRACE_SETVFPREGS:
|
||
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
||
|
case COMPAT_PTRACE_SETHBPREGS:
|
||
|
#endif
|
||
|
#endif
|
||
|
#endif
|
||
|
#ifdef CONFIG_X86
|
||
|
case PTRACE_SETFPXREGS:
|
||
|
#endif
|
||
|
#ifdef CONFIG_ALTIVEC
|
||
|
case PTRACE_SETVRREGS:
|
||
|
#endif
|
||
|
#ifdef CONFIG_ARM
|
||
|
case PTRACE_SET_SYSCALL:
|
||
|
case PTRACE_SETVFPREGS:
|
||
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
||
|
case PTRACE_SETHBPREGS:
|
||
|
#endif
|
||
|
#endif
|
||
|
return 1;
|
||
|
default:
|
||
|
return 0;
|
||
|
}
|
||
|
} else if (!(current->acl->mode & GR_POVERRIDE) &&
|
||
|
!(current->role->roletype & GR_ROLE_GOD) &&
|
||
|
(current->acl != task->acl)) {
|
||
|
gr_log_ptrace(GR_DONT_AUDIT, GR_PTRACE_ACL_MSG, task);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int is_writable_mmap(const struct file *filp)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
struct acl_object_label *obj, *obj2;
|
||
|
struct dentry *dentry = filp->f_path.dentry;
|
||
|
struct vfsmount *mnt = filp->f_path.mnt;
|
||
|
struct inode *inode = d_backing_inode(dentry);
|
||
|
|
||
|
if (gr_status & GR_READY && !(task->acl->mode & GR_OVERRIDE) &&
|
||
|
!task->is_writable && d_is_reg(dentry) && (mnt != shm_mnt || (inode->i_nlink > 0))) {
|
||
|
obj = chk_obj_label(dentry, mnt, running_polstate.default_role->root_label);
|
||
|
obj2 = chk_obj_label(dentry, mnt, task->role->root_label);
|
||
|
if (unlikely((obj->mode & GR_WRITE) || (obj2->mode & GR_WRITE))) {
|
||
|
gr_log_fs_generic(GR_DONT_AUDIT, GR_WRITLIB_ACL_MSG, dentry, mnt);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_acl_handle_mmap(const struct file *file, const unsigned long prot)
|
||
|
{
|
||
|
__u32 mode;
|
||
|
|
||
|
if (unlikely(!file || !(prot & PROT_EXEC)))
|
||
|
return 1;
|
||
|
|
||
|
if (is_writable_mmap(file))
|
||
|
return 0;
|
||
|
|
||
|
mode =
|
||
|
gr_search_file(file->f_path.dentry,
|
||
|
GR_EXEC | GR_AUDIT_EXEC | GR_SUPPRESS,
|
||
|
file->f_path.mnt);
|
||
|
|
||
|
if (!gr_tpe_allow(file))
|
||
|
return 0;
|
||
|
|
||
|
if (unlikely(!(mode & GR_EXEC) && !(mode & GR_SUPPRESS))) {
|
||
|
gr_log_fs_rbac_generic(GR_DONT_AUDIT, GR_MMAP_ACL_MSG, file->f_path.dentry, file->f_path.mnt);
|
||
|
return 0;
|
||
|
} else if (unlikely(!(mode & GR_EXEC))) {
|
||
|
return 0;
|
||
|
} else if (unlikely(mode & GR_EXEC && mode & GR_AUDIT_EXEC)) {
|
||
|
gr_log_fs_rbac_generic(GR_DO_AUDIT, GR_MMAP_ACL_MSG, file->f_path.dentry, file->f_path.mnt);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
gr_acl_handle_mprotect(const struct file *file, const unsigned long prot)
|
||
|
{
|
||
|
__u32 mode;
|
||
|
|
||
|
if (unlikely(!file || !(prot & PROT_EXEC)))
|
||
|
return 1;
|
||
|
|
||
|
if (is_writable_mmap(file))
|
||
|
return 0;
|
||
|
|
||
|
mode =
|
||
|
gr_search_file(file->f_path.dentry,
|
||
|
GR_EXEC | GR_AUDIT_EXEC | GR_SUPPRESS,
|
||
|
file->f_path.mnt);
|
||
|
|
||
|
if (!gr_tpe_allow(file))
|
||
|
return 0;
|
||
|
|
||
|
if (unlikely(!(mode & GR_EXEC) && !(mode & GR_SUPPRESS))) {
|
||
|
gr_log_fs_rbac_generic(GR_DONT_AUDIT, GR_MPROTECT_ACL_MSG, file->f_path.dentry, file->f_path.mnt);
|
||
|
return 0;
|
||
|
} else if (unlikely(!(mode & GR_EXEC))) {
|
||
|
return 0;
|
||
|
} else if (unlikely(mode & GR_EXEC && mode & GR_AUDIT_EXEC)) {
|
||
|
gr_log_fs_rbac_generic(GR_DO_AUDIT, GR_MPROTECT_ACL_MSG, file->f_path.dentry, file->f_path.mnt);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
gr_acl_handle_psacct(struct task_struct *task, const long code)
|
||
|
{
|
||
|
unsigned long runtime, cputime;
|
||
|
cputime_t utime, stime;
|
||
|
unsigned int wday, cday;
|
||
|
__u8 whr, chr;
|
||
|
__u8 wmin, cmin;
|
||
|
__u8 wsec, csec;
|
||
|
struct timespec curtime, starttime;
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY) || !task->acl ||
|
||
|
!(task->acl->mode & GR_PROCACCT)))
|
||
|
return;
|
||
|
|
||
|
curtime = ns_to_timespec(ktime_get_ns());
|
||
|
starttime = ns_to_timespec(task->start_time);
|
||
|
runtime = curtime.tv_sec - starttime.tv_sec;
|
||
|
wday = runtime / (60 * 60 * 24);
|
||
|
runtime -= wday * (60 * 60 * 24);
|
||
|
whr = runtime / (60 * 60);
|
||
|
runtime -= whr * (60 * 60);
|
||
|
wmin = runtime / 60;
|
||
|
runtime -= wmin * 60;
|
||
|
wsec = runtime;
|
||
|
|
||
|
task_cputime(task, &utime, &stime);
|
||
|
cputime = cputime_to_secs(utime + stime);
|
||
|
cday = cputime / (60 * 60 * 24);
|
||
|
cputime -= cday * (60 * 60 * 24);
|
||
|
chr = cputime / (60 * 60);
|
||
|
cputime -= chr * (60 * 60);
|
||
|
cmin = cputime / 60;
|
||
|
cputime -= cmin * 60;
|
||
|
csec = cputime;
|
||
|
|
||
|
gr_log_procacct(GR_DO_AUDIT, GR_ACL_PROCACCT_MSG, task, wday, whr, wmin, wsec, cday, chr, cmin, csec, code);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_TASKSTATS
|
||
|
int gr_is_taskstats_denied(int pid)
|
||
|
{
|
||
|
struct task_struct *task;
|
||
|
#if defined(CONFIG_GRKERNSEC_PROC_USER) || defined(CONFIG_GRKERNSEC_PROC_USERGROUP)
|
||
|
const struct cred *cred;
|
||
|
#endif
|
||
|
int ret = 0;
|
||
|
|
||
|
/* restrict taskstats viewing to un-chrooted root users
|
||
|
who have the 'view' subject flag if the RBAC system is enabled
|
||
|
*/
|
||
|
|
||
|
rcu_read_lock();
|
||
|
read_lock(&tasklist_lock);
|
||
|
task = find_task_by_vpid(pid);
|
||
|
if (task) {
|
||
|
#ifdef CONFIG_GRKERNSEC_CHROOT
|
||
|
if (proc_is_chrooted(task))
|
||
|
ret = -EACCES;
|
||
|
#endif
|
||
|
#if defined(CONFIG_GRKERNSEC_PROC_USER) || defined(CONFIG_GRKERNSEC_PROC_USERGROUP)
|
||
|
cred = __task_cred(task);
|
||
|
#ifdef CONFIG_GRKERNSEC_PROC_USER
|
||
|
if (gr_is_global_nonroot(cred->uid))
|
||
|
ret = -EACCES;
|
||
|
#elif defined(CONFIG_GRKERNSEC_PROC_USERGROUP)
|
||
|
if (gr_is_global_nonroot(cred->uid) && !groups_search(cred->group_info, grsec_proc_gid))
|
||
|
ret = -EACCES;
|
||
|
#endif
|
||
|
#endif
|
||
|
if (gr_status & GR_READY) {
|
||
|
if (!(task->acl->mode & GR_VIEW))
|
||
|
ret = -EACCES;
|
||
|
}
|
||
|
} else
|
||
|
ret = -ENOENT;
|
||
|
|
||
|
read_unlock(&tasklist_lock);
|
||
|
rcu_read_unlock();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* AUXV entries are filled via a descendant of search_binary_handler
|
||
|
after we've already applied the subject for the target
|
||
|
*/
|
||
|
int gr_acl_enable_at_secure(void)
|
||
|
{
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 0;
|
||
|
|
||
|
if (current->acl->mode & GR_ATSECURE)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int gr_acl_handle_filldir(const struct file *file, const char *name, const unsigned int namelen, const u64 ino)
|
||
|
{
|
||
|
struct task_struct *task = current;
|
||
|
struct dentry *dentry = file->f_path.dentry;
|
||
|
struct vfsmount *mnt = file->f_path.mnt;
|
||
|
struct acl_object_label *obj, *tmp;
|
||
|
struct acl_subject_label *subj;
|
||
|
unsigned int bufsize;
|
||
|
int is_not_root;
|
||
|
char *path;
|
||
|
dev_t dev = __get_dev(dentry);
|
||
|
|
||
|
if (unlikely(!(gr_status & GR_READY)))
|
||
|
return 1;
|
||
|
|
||
|
if (task->acl->mode & (GR_LEARN | GR_INHERITLEARN))
|
||
|
return 1;
|
||
|
|
||
|
/* ignore Eric Biederman */
|
||
|
if (IS_PRIVATE(d_backing_inode(dentry)))
|
||
|
return 1;
|
||
|
|
||
|
subj = task->acl;
|
||
|
read_lock(&gr_inode_lock);
|
||
|
do {
|
||
|
obj = lookup_acl_obj_label(ino, dev, subj);
|
||
|
if (obj != NULL) {
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
return (obj->mode & GR_FIND) ? 1 : 0;
|
||
|
}
|
||
|
} while ((subj = subj->parent_subject));
|
||
|
read_unlock(&gr_inode_lock);
|
||
|
|
||
|
/* this is purely an optimization since we're looking for an object
|
||
|
for the directory we're doing a readdir on
|
||
|
if it's possible for any globbed object to match the entry we're
|
||
|
filling into the directory, then the object we find here will be
|
||
|
an anchor point with attached globbed objects
|
||
|
*/
|
||
|
obj = chk_obj_label_noglob(dentry, mnt, task->acl);
|
||
|
if (obj->globbed == NULL)
|
||
|
return (obj->mode & GR_FIND) ? 1 : 0;
|
||
|
|
||
|
is_not_root = ((obj->filename[0] == '/') &&
|
||
|
(obj->filename[1] == '\0')) ? 0 : 1;
|
||
|
bufsize = PAGE_SIZE - namelen - is_not_root;
|
||
|
|
||
|
/* check bufsize > PAGE_SIZE || bufsize == 0 */
|
||
|
if (unlikely((bufsize - 1) > (PAGE_SIZE - 1)))
|
||
|
return 1;
|
||
|
|
||
|
preempt_disable();
|
||
|
path = d_real_path(dentry, mnt, per_cpu_ptr(gr_shared_page[0], smp_processor_id()),
|
||
|
bufsize);
|
||
|
|
||
|
bufsize = strlen(path);
|
||
|
|
||
|
/* if base is "/", don't append an additional slash */
|
||
|
if (is_not_root)
|
||
|
*(path + bufsize) = '/';
|
||
|
memcpy(path + bufsize + is_not_root, name, namelen);
|
||
|
*(path + bufsize + namelen + is_not_root) = '\0';
|
||
|
|
||
|
tmp = obj->globbed;
|
||
|
while (tmp) {
|
||
|
if (!glob_match(tmp->filename, path)) {
|
||
|
preempt_enable();
|
||
|
return (tmp->mode & GR_FIND) ? 1 : 0;
|
||
|
}
|
||
|
tmp = tmp->next;
|
||
|
}
|
||
|
preempt_enable();
|
||
|
return (obj->mode & GR_FIND) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
void gr_put_exec_file(struct task_struct *task)
|
||
|
{
|
||
|
struct file *filp;
|
||
|
|
||
|
write_lock(&grsec_exec_file_lock);
|
||
|
filp = task->exec_file;
|
||
|
task->exec_file = NULL;
|
||
|
write_unlock(&grsec_exec_file_lock);
|
||
|
|
||
|
if (filp)
|
||
|
fput(filp);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifdef CONFIG_NETFILTER_XT_MATCH_GRADM_MODULE
|
||
|
EXPORT_SYMBOL_GPL(gr_acl_is_enabled);
|
||
|
#endif
|
||
|
#ifdef CONFIG_SECURITY
|
||
|
EXPORT_SYMBOL_GPL(gr_check_user_change);
|
||
|
EXPORT_SYMBOL_GPL(gr_check_group_change);
|
||
|
#endif
|
||
|
|