mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
43
security/apparmor/Kconfig
Normal file
43
security/apparmor/Kconfig
Normal file
|
@ -0,0 +1,43 @@
|
|||
config SECURITY_APPARMOR
|
||||
bool "AppArmor support"
|
||||
depends on SECURITY && NET
|
||||
select AUDIT
|
||||
select SECURITY_PATH
|
||||
select SECURITYFS
|
||||
select SECURITY_NETWORK
|
||||
default n
|
||||
help
|
||||
This enables the AppArmor security module.
|
||||
Required userspace tools (if they are not included in your
|
||||
distribution) and further information may be found at
|
||||
http://apparmor.wiki.kernel.org
|
||||
|
||||
If you are unsure how to answer this question, answer N.
|
||||
|
||||
config SECURITY_APPARMOR_BOOTPARAM_VALUE
|
||||
int "AppArmor boot parameter default value"
|
||||
depends on SECURITY_APPARMOR
|
||||
range 0 1
|
||||
default 1
|
||||
help
|
||||
This option sets the default value for the kernel parameter
|
||||
'apparmor', which allows AppArmor to be enabled or disabled
|
||||
at boot. If this option is set to 0 (zero), the AppArmor
|
||||
kernel parameter will default to 0, disabling AppArmor at
|
||||
boot. If this option is set to 1 (one), the AppArmor
|
||||
kernel parameter will default to 1, enabling AppArmor at
|
||||
boot.
|
||||
|
||||
If you are unsure how to answer this question, answer 1.
|
||||
|
||||
config SECURITY_APPARMOR_HASH
|
||||
bool "SHA1 hash of loaded profiles"
|
||||
depends on SECURITY_APPARMOR
|
||||
depends on CRYPTO
|
||||
select CRYPTO_SHA1
|
||||
default y
|
||||
|
||||
help
|
||||
This option selects whether sha1 hashing is done against loaded
|
||||
profiles and exported for inspection to user space via the apparmor
|
||||
filesystem.
|
70
security/apparmor/Makefile
Normal file
70
security/apparmor/Makefile
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Makefile for AppArmor Linux Security Module
|
||||
#
|
||||
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
|
||||
|
||||
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
|
||||
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
|
||||
resource.o sid.o file.o
|
||||
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
|
||||
|
||||
clean-files := capability_names.h rlim_names.h
|
||||
|
||||
|
||||
# Build a lower case string table of capability names
|
||||
# Transforms lines from
|
||||
# #define CAP_DAC_OVERRIDE 1
|
||||
# to
|
||||
# [1] = "dac_override",
|
||||
quiet_cmd_make-caps = GEN $@
|
||||
cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
|
||||
sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \
|
||||
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
|
||||
echo "};" >> $@ ;\
|
||||
echo -n '\#define AA_FS_CAPS_MASK "' >> $@ ;\
|
||||
sed $< -r -n -e '/CAP_FS_MASK/d' \
|
||||
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \
|
||||
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
|
||||
|
||||
|
||||
# Build a lower case string table of rlimit names.
|
||||
# Transforms lines from
|
||||
# #define RLIMIT_STACK 3 /* max stack size */
|
||||
# to
|
||||
# [RLIMIT_STACK] = "stack",
|
||||
#
|
||||
# and build a second integer table (with the second sed cmd), that maps
|
||||
# RLIMIT defines to the order defined in asm-generic/resource.h This is
|
||||
# required by policy load to map policy ordering of RLIMITs to internal
|
||||
# ordering for architectures that redefine an RLIMIT.
|
||||
# Transforms lines from
|
||||
# #define RLIMIT_STACK 3 /* max stack size */
|
||||
# to
|
||||
# RLIMIT_STACK,
|
||||
#
|
||||
# and build the securityfs entries for the mapping.
|
||||
# Transforms lines from
|
||||
# #define RLIMIT_FSIZE 1 /* Maximum filesize */
|
||||
# #define RLIMIT_STACK 3 /* max stack size */
|
||||
# to
|
||||
# #define AA_FS_RLIMIT_MASK "fsize stack"
|
||||
quiet_cmd_make-rlim = GEN $@
|
||||
cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
|
||||
> $@ ;\
|
||||
sed $< >> $@ -r -n \
|
||||
-e 's/^\# ?define[ \t]+(RLIMIT_([A-Z0-9_]+)).*/[\1] = "\L\2",/p';\
|
||||
echo "};" >> $@ ;\
|
||||
echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\
|
||||
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
|
||||
echo "};" >> $@ ; \
|
||||
echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\
|
||||
sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \
|
||||
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
|
||||
|
||||
$(obj)/capability.o : $(obj)/capability_names.h
|
||||
$(obj)/resource.o : $(obj)/rlim_names.h
|
||||
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
|
||||
$(src)/Makefile
|
||||
$(call cmd,make-caps)
|
||||
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
|
||||
$(src)/Makefile
|
||||
$(call cmd,make-rlim)
|
969
security/apparmor/apparmorfs.c
Normal file
969
security/apparmor/apparmorfs.c
Normal file
|
@ -0,0 +1,969 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor /sys/kernel/security/apparmor interface functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/rcupdate.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/apparmorfs.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/context.h"
|
||||
#include "include/crypto.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/resource.h"
|
||||
|
||||
/**
|
||||
* aa_mangle_name - mangle a profile name to std profile layout form
|
||||
* @name: profile name to mangle (NOT NULL)
|
||||
* @target: buffer to store mangled name, same length as @name (MAYBE NULL)
|
||||
*
|
||||
* Returns: length of mangled name
|
||||
*/
|
||||
static int mangle_name(char *name, char *target)
|
||||
{
|
||||
char *t = target;
|
||||
|
||||
while (*name == '/' || *name == '.')
|
||||
name++;
|
||||
|
||||
if (target) {
|
||||
for (; *name; name++) {
|
||||
if (*name == '/')
|
||||
*(t)++ = '.';
|
||||
else if (isspace(*name))
|
||||
*(t)++ = '_';
|
||||
else if (isalnum(*name) || strchr("._-", *name))
|
||||
*(t)++ = *name;
|
||||
}
|
||||
|
||||
*t = 0;
|
||||
} else {
|
||||
int len = 0;
|
||||
for (; *name; name++) {
|
||||
if (isalnum(*name) || isspace(*name) ||
|
||||
strchr("/._-", *name))
|
||||
len++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
return t - target;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_simple_write_to_buffer - common routine for getting policy from user
|
||||
* @op: operation doing the user buffer copy
|
||||
* @userbuf: user buffer to copy data from (NOT NULL)
|
||||
* @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size)
|
||||
* @copy_size: size of data to copy from user buffer
|
||||
* @pos: position write is at in the file (NOT NULL)
|
||||
*
|
||||
* Returns: kernel buffer containing copy of user buffer data or an
|
||||
* ERR_PTR on failure.
|
||||
*/
|
||||
static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
|
||||
size_t alloc_size, size_t copy_size,
|
||||
loff_t *pos)
|
||||
{
|
||||
char *data;
|
||||
|
||||
BUG_ON(copy_size > alloc_size);
|
||||
|
||||
if (*pos != 0)
|
||||
/* only writes from pos 0, that is complete writes */
|
||||
return ERR_PTR(-ESPIPE);
|
||||
|
||||
/*
|
||||
* Don't allow profile load/replace/remove from profiles that don't
|
||||
* have CAP_MAC_ADMIN
|
||||
*/
|
||||
if (!aa_may_manage_policy(op))
|
||||
return ERR_PTR(-EACCES);
|
||||
|
||||
/* freed by caller to simple_write_to_buffer */
|
||||
data = kvmalloc(alloc_size);
|
||||
if (data == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (copy_from_user(data, userbuf, copy_size)) {
|
||||
kvfree(data);
|
||||
return ERR_PTR(-EFAULT);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/* .load file hook fn to load policy */
|
||||
static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
|
||||
loff_t *pos)
|
||||
{
|
||||
char *data;
|
||||
ssize_t error;
|
||||
|
||||
data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos);
|
||||
|
||||
error = PTR_ERR(data);
|
||||
if (!IS_ERR(data)) {
|
||||
error = aa_replace_profiles(data, size, PROF_ADD);
|
||||
kvfree(data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profile_load = {
|
||||
.write = profile_load,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
/* .replace file hook fn to load and/or replace policy */
|
||||
static ssize_t profile_replace(struct file *f, const char __user *buf,
|
||||
size_t size, loff_t *pos)
|
||||
{
|
||||
char *data;
|
||||
ssize_t error;
|
||||
|
||||
data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos);
|
||||
error = PTR_ERR(data);
|
||||
if (!IS_ERR(data)) {
|
||||
error = aa_replace_profiles(data, size, PROF_REPLACE);
|
||||
kvfree(data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profile_replace = {
|
||||
.write = profile_replace,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
/* .remove file hook fn to remove loaded policy */
|
||||
static ssize_t profile_remove(struct file *f, const char __user *buf,
|
||||
size_t size, loff_t *pos)
|
||||
{
|
||||
char *data;
|
||||
ssize_t error;
|
||||
|
||||
/*
|
||||
* aa_remove_profile needs a null terminated string so 1 extra
|
||||
* byte is allocated and the copied data is null terminated.
|
||||
*/
|
||||
data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos);
|
||||
|
||||
error = PTR_ERR(data);
|
||||
if (!IS_ERR(data)) {
|
||||
data[size] = 0;
|
||||
error = aa_remove_profiles(data, size);
|
||||
kvfree(data);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profile_remove = {
|
||||
.write = profile_remove,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static int aa_fs_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct aa_fs_entry *fs_file = seq->private;
|
||||
|
||||
if (!fs_file)
|
||||
return 0;
|
||||
|
||||
switch (fs_file->v_type) {
|
||||
case AA_FS_TYPE_BOOLEAN:
|
||||
seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no");
|
||||
break;
|
||||
case AA_FS_TYPE_STRING:
|
||||
seq_printf(seq, "%s\n", fs_file->v.string);
|
||||
break;
|
||||
case AA_FS_TYPE_U64:
|
||||
seq_printf(seq, "%#08lx\n", fs_file->v.u64);
|
||||
break;
|
||||
default:
|
||||
/* Ignore unpritable entry types. */
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aa_fs_seq_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, aa_fs_seq_show, inode->i_private);
|
||||
}
|
||||
|
||||
const struct file_operations aa_fs_seq_file_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = aa_fs_seq_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static int aa_fs_seq_profile_open(struct inode *inode, struct file *file,
|
||||
int (*show)(struct seq_file *, void *))
|
||||
{
|
||||
struct aa_replacedby *r = aa_get_replacedby(inode->i_private);
|
||||
int error = single_open(file, show, r);
|
||||
|
||||
if (error) {
|
||||
file->private_data = NULL;
|
||||
aa_put_replacedby(r);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int aa_fs_seq_profile_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct seq_file *seq = (struct seq_file *) file->private_data;
|
||||
if (seq)
|
||||
aa_put_replacedby(seq->private);
|
||||
return single_release(inode, file);
|
||||
}
|
||||
|
||||
static int aa_fs_seq_profname_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct aa_replacedby *r = seq->private;
|
||||
struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
|
||||
seq_printf(seq, "%s\n", profile->base.name);
|
||||
aa_put_profile(profile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aa_fs_seq_profname_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profname_show);
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profname_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = aa_fs_seq_profname_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = aa_fs_seq_profile_release,
|
||||
};
|
||||
|
||||
static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct aa_replacedby *r = seq->private;
|
||||
struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
|
||||
seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]);
|
||||
aa_put_profile(profile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profmode_show);
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profmode_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = aa_fs_seq_profmode_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = aa_fs_seq_profile_release,
|
||||
};
|
||||
|
||||
static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct aa_replacedby *r = seq->private;
|
||||
struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
|
||||
if (profile->attach)
|
||||
seq_printf(seq, "%s\n", profile->attach);
|
||||
else if (profile->xmatch)
|
||||
seq_puts(seq, "<unknown>\n");
|
||||
else
|
||||
seq_printf(seq, "%s\n", profile->base.name);
|
||||
aa_put_profile(profile);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profattach_show);
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profattach_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = aa_fs_seq_profattach_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = aa_fs_seq_profile_release,
|
||||
};
|
||||
|
||||
static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct aa_replacedby *r = seq->private;
|
||||
struct aa_profile *profile = aa_get_profile_rcu(&r->profile);
|
||||
unsigned int i, size = aa_hash_size();
|
||||
|
||||
if (profile->hash) {
|
||||
for (i = 0; i < size; i++)
|
||||
seq_printf(seq, "%.2x", profile->hash[i]);
|
||||
seq_puts(seq, "\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aa_fs_seq_hash_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, aa_fs_seq_hash_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_seq_hash_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = aa_fs_seq_hash_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
/** fns to setup dynamic per profile/namespace files **/
|
||||
void __aa_fs_profile_rmdir(struct aa_profile *profile)
|
||||
{
|
||||
struct aa_profile *child;
|
||||
int i;
|
||||
|
||||
if (!profile)
|
||||
return;
|
||||
|
||||
list_for_each_entry(child, &profile->base.profiles, base.list)
|
||||
__aa_fs_profile_rmdir(child);
|
||||
|
||||
for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) {
|
||||
struct aa_replacedby *r;
|
||||
if (!profile->dents[i])
|
||||
continue;
|
||||
|
||||
r = profile->dents[i]->d_inode->i_private;
|
||||
securityfs_remove(profile->dents[i]);
|
||||
aa_put_replacedby(r);
|
||||
profile->dents[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void __aa_fs_profile_migrate_dents(struct aa_profile *old,
|
||||
struct aa_profile *new)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < AAFS_PROF_SIZEOF; i++) {
|
||||
new->dents[i] = old->dents[i];
|
||||
old->dents[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct dentry *create_profile_file(struct dentry *dir, const char *name,
|
||||
struct aa_profile *profile,
|
||||
const struct file_operations *fops)
|
||||
{
|
||||
struct aa_replacedby *r = aa_get_replacedby(profile->replacedby);
|
||||
struct dentry *dent;
|
||||
|
||||
dent = securityfs_create_file(name, S_IFREG | 0444, dir, r, fops);
|
||||
if (IS_ERR(dent))
|
||||
aa_put_replacedby(r);
|
||||
|
||||
return dent;
|
||||
}
|
||||
|
||||
/* requires lock be held */
|
||||
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
|
||||
{
|
||||
struct aa_profile *child;
|
||||
struct dentry *dent = NULL, *dir;
|
||||
int error;
|
||||
|
||||
if (!parent) {
|
||||
struct aa_profile *p;
|
||||
p = aa_deref_parent(profile);
|
||||
dent = prof_dir(p);
|
||||
/* adding to parent that previously didn't have children */
|
||||
dent = securityfs_create_dir("profiles", dent);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
prof_child_dir(p) = parent = dent;
|
||||
}
|
||||
|
||||
if (!profile->dirname) {
|
||||
int len, id_len;
|
||||
len = mangle_name(profile->base.name, NULL);
|
||||
id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id);
|
||||
|
||||
profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL);
|
||||
if (!profile->dirname)
|
||||
goto fail;
|
||||
|
||||
mangle_name(profile->base.name, profile->dirname);
|
||||
sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++);
|
||||
}
|
||||
|
||||
dent = securityfs_create_dir(profile->dirname, parent);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
prof_dir(profile) = dir = dent;
|
||||
|
||||
dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
profile->dents[AAFS_PROF_NAME] = dent;
|
||||
|
||||
dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
profile->dents[AAFS_PROF_MODE] = dent;
|
||||
|
||||
dent = create_profile_file(dir, "attach", profile,
|
||||
&aa_fs_profattach_fops);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
profile->dents[AAFS_PROF_ATTACH] = dent;
|
||||
|
||||
if (profile->hash) {
|
||||
dent = create_profile_file(dir, "sha1", profile,
|
||||
&aa_fs_seq_hash_fops);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
profile->dents[AAFS_PROF_HASH] = dent;
|
||||
}
|
||||
|
||||
list_for_each_entry(child, &profile->base.profiles, base.list) {
|
||||
error = __aa_fs_profile_mkdir(child, prof_child_dir(profile));
|
||||
if (error)
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
error = PTR_ERR(dent);
|
||||
|
||||
fail2:
|
||||
__aa_fs_profile_rmdir(profile);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void __aa_fs_namespace_rmdir(struct aa_namespace *ns)
|
||||
{
|
||||
struct aa_namespace *sub;
|
||||
struct aa_profile *child;
|
||||
int i;
|
||||
|
||||
if (!ns)
|
||||
return;
|
||||
|
||||
list_for_each_entry(child, &ns->base.profiles, base.list)
|
||||
__aa_fs_profile_rmdir(child);
|
||||
|
||||
list_for_each_entry(sub, &ns->sub_ns, base.list) {
|
||||
mutex_lock(&sub->lock);
|
||||
__aa_fs_namespace_rmdir(sub);
|
||||
mutex_unlock(&sub->lock);
|
||||
}
|
||||
|
||||
for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) {
|
||||
securityfs_remove(ns->dents[i]);
|
||||
ns->dents[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
|
||||
const char *name)
|
||||
{
|
||||
struct aa_namespace *sub;
|
||||
struct aa_profile *child;
|
||||
struct dentry *dent, *dir;
|
||||
int error;
|
||||
|
||||
if (!name)
|
||||
name = ns->base.name;
|
||||
|
||||
dent = securityfs_create_dir(name, parent);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
ns_dir(ns) = dir = dent;
|
||||
|
||||
dent = securityfs_create_dir("profiles", dir);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
ns_subprofs_dir(ns) = dent;
|
||||
|
||||
dent = securityfs_create_dir("namespaces", dir);
|
||||
if (IS_ERR(dent))
|
||||
goto fail;
|
||||
ns_subns_dir(ns) = dent;
|
||||
|
||||
list_for_each_entry(child, &ns->base.profiles, base.list) {
|
||||
error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns));
|
||||
if (error)
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
list_for_each_entry(sub, &ns->sub_ns, base.list) {
|
||||
mutex_lock(&sub->lock);
|
||||
error = __aa_fs_namespace_mkdir(sub, ns_subns_dir(ns), NULL);
|
||||
mutex_unlock(&sub->lock);
|
||||
if (error)
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
error = PTR_ERR(dent);
|
||||
|
||||
fail2:
|
||||
__aa_fs_namespace_rmdir(ns);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
#define list_entry_next(pos, member) \
|
||||
list_entry(pos->member.next, typeof(*pos), member)
|
||||
#define list_entry_is_head(pos, head, member) (&pos->member == (head))
|
||||
|
||||
/**
|
||||
* __next_namespace - find the next namespace to list
|
||||
* @root: root namespace to stop search at (NOT NULL)
|
||||
* @ns: current ns position (NOT NULL)
|
||||
*
|
||||
* Find the next namespace from @ns under @root and handle all locking needed
|
||||
* while switching current namespace.
|
||||
*
|
||||
* Returns: next namespace or NULL if at last namespace under @root
|
||||
* Requires: ns->parent->lock to be held
|
||||
* NOTE: will not unlock root->lock
|
||||
*/
|
||||
static struct aa_namespace *__next_namespace(struct aa_namespace *root,
|
||||
struct aa_namespace *ns)
|
||||
{
|
||||
struct aa_namespace *parent, *next;
|
||||
|
||||
/* is next namespace a child */
|
||||
if (!list_empty(&ns->sub_ns)) {
|
||||
next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list);
|
||||
mutex_lock(&next->lock);
|
||||
return next;
|
||||
}
|
||||
|
||||
/* check if the next ns is a sibling, parent, gp, .. */
|
||||
parent = ns->parent;
|
||||
while (ns != root) {
|
||||
mutex_unlock(&ns->lock);
|
||||
next = list_entry_next(ns, base.list);
|
||||
if (!list_entry_is_head(next, &parent->sub_ns, base.list)) {
|
||||
mutex_lock(&next->lock);
|
||||
return next;
|
||||
}
|
||||
ns = parent;
|
||||
parent = parent->parent;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* __first_profile - find the first profile in a namespace
|
||||
* @root: namespace that is root of profiles being displayed (NOT NULL)
|
||||
* @ns: namespace to start in (NOT NULL)
|
||||
*
|
||||
* Returns: unrefcounted profile or NULL if no profile
|
||||
* Requires: profile->ns.lock to be held
|
||||
*/
|
||||
static struct aa_profile *__first_profile(struct aa_namespace *root,
|
||||
struct aa_namespace *ns)
|
||||
{
|
||||
for (; ns; ns = __next_namespace(root, ns)) {
|
||||
if (!list_empty(&ns->base.profiles))
|
||||
return list_first_entry(&ns->base.profiles,
|
||||
struct aa_profile, base.list);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* __next_profile - step to the next profile in a profile tree
|
||||
* @profile: current profile in tree (NOT NULL)
|
||||
*
|
||||
* Perform a depth first traversal on the profile tree in a namespace
|
||||
*
|
||||
* Returns: next profile or NULL if done
|
||||
* Requires: profile->ns.lock to be held
|
||||
*/
|
||||
static struct aa_profile *__next_profile(struct aa_profile *p)
|
||||
{
|
||||
struct aa_profile *parent;
|
||||
struct aa_namespace *ns = p->ns;
|
||||
|
||||
/* is next profile a child */
|
||||
if (!list_empty(&p->base.profiles))
|
||||
return list_first_entry(&p->base.profiles, typeof(*p),
|
||||
base.list);
|
||||
|
||||
/* is next profile a sibling, parent sibling, gp, sibling, .. */
|
||||
parent = rcu_dereference_protected(p->parent,
|
||||
mutex_is_locked(&p->ns->lock));
|
||||
while (parent) {
|
||||
p = list_entry_next(p, base.list);
|
||||
if (!list_entry_is_head(p, &parent->base.profiles, base.list))
|
||||
return p;
|
||||
p = parent;
|
||||
parent = rcu_dereference_protected(parent->parent,
|
||||
mutex_is_locked(&parent->ns->lock));
|
||||
}
|
||||
|
||||
/* is next another profile in the namespace */
|
||||
p = list_entry_next(p, base.list);
|
||||
if (!list_entry_is_head(p, &ns->base.profiles, base.list))
|
||||
return p;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* next_profile - step to the next profile in where ever it may be
|
||||
* @root: root namespace (NOT NULL)
|
||||
* @profile: current profile (NOT NULL)
|
||||
*
|
||||
* Returns: next profile or NULL if there isn't one
|
||||
*/
|
||||
static struct aa_profile *next_profile(struct aa_namespace *root,
|
||||
struct aa_profile *profile)
|
||||
{
|
||||
struct aa_profile *next = __next_profile(profile);
|
||||
if (next)
|
||||
return next;
|
||||
|
||||
/* finished all profiles in namespace move to next namespace */
|
||||
return __first_profile(root, __next_namespace(root, profile->ns));
|
||||
}
|
||||
|
||||
/**
|
||||
* p_start - start a depth first traversal of profile tree
|
||||
* @f: seq_file to fill
|
||||
* @pos: current position
|
||||
*
|
||||
* Returns: first profile under current namespace or NULL if none found
|
||||
*
|
||||
* acquires first ns->lock
|
||||
*/
|
||||
static void *p_start(struct seq_file *f, loff_t *pos)
|
||||
{
|
||||
struct aa_profile *profile = NULL;
|
||||
struct aa_namespace *root = aa_current_profile()->ns;
|
||||
loff_t l = *pos;
|
||||
f->private = aa_get_namespace(root);
|
||||
|
||||
|
||||
/* find the first profile */
|
||||
mutex_lock(&root->lock);
|
||||
profile = __first_profile(root, root);
|
||||
|
||||
/* skip to position */
|
||||
for (; profile && l > 0; l--)
|
||||
profile = next_profile(root, profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* p_next - read the next profile entry
|
||||
* @f: seq_file to fill
|
||||
* @p: profile previously returned
|
||||
* @pos: current position
|
||||
*
|
||||
* Returns: next profile after @p or NULL if none
|
||||
*
|
||||
* may acquire/release locks in namespace tree as necessary
|
||||
*/
|
||||
static void *p_next(struct seq_file *f, void *p, loff_t *pos)
|
||||
{
|
||||
struct aa_profile *profile = p;
|
||||
struct aa_namespace *ns = f->private;
|
||||
(*pos)++;
|
||||
|
||||
return next_profile(ns, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* p_stop - stop depth first traversal
|
||||
* @f: seq_file we are filling
|
||||
* @p: the last profile writen
|
||||
*
|
||||
* Release all locking done by p_start/p_next on namespace tree
|
||||
*/
|
||||
static void p_stop(struct seq_file *f, void *p)
|
||||
{
|
||||
struct aa_profile *profile = p;
|
||||
struct aa_namespace *root = f->private, *ns;
|
||||
|
||||
if (profile) {
|
||||
for (ns = profile->ns; ns && ns != root; ns = ns->parent)
|
||||
mutex_unlock(&ns->lock);
|
||||
}
|
||||
mutex_unlock(&root->lock);
|
||||
aa_put_namespace(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* seq_show_profile - show a profile entry
|
||||
* @f: seq_file to file
|
||||
* @p: current position (profile) (NOT NULL)
|
||||
*
|
||||
* Returns: error on failure
|
||||
*/
|
||||
static int seq_show_profile(struct seq_file *f, void *p)
|
||||
{
|
||||
struct aa_profile *profile = (struct aa_profile *)p;
|
||||
struct aa_namespace *root = f->private;
|
||||
|
||||
if (profile->ns != root)
|
||||
seq_printf(f, ":%s://", aa_ns_name(root, profile->ns));
|
||||
seq_printf(f, "%s (%s)\n", profile->base.hname,
|
||||
aa_profile_mode_names[profile->mode]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct seq_operations aa_fs_profiles_op = {
|
||||
.start = p_start,
|
||||
.next = p_next,
|
||||
.stop = p_stop,
|
||||
.show = seq_show_profile,
|
||||
};
|
||||
|
||||
static int profiles_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_open(file, &aa_fs_profiles_op);
|
||||
}
|
||||
|
||||
static int profiles_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
return seq_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations aa_fs_profiles_fops = {
|
||||
.open = profiles_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = profiles_release,
|
||||
};
|
||||
|
||||
|
||||
/** Base file system setup **/
|
||||
static struct aa_fs_entry aa_fs_entry_file[] = {
|
||||
AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \
|
||||
"link lock"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct aa_fs_entry aa_fs_entry_domain[] = {
|
||||
AA_FS_FILE_BOOLEAN("change_hat", 1),
|
||||
AA_FS_FILE_BOOLEAN("change_hatv", 1),
|
||||
AA_FS_FILE_BOOLEAN("change_onexec", 1),
|
||||
AA_FS_FILE_BOOLEAN("change_profile", 1),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct aa_fs_entry aa_fs_entry_policy[] = {
|
||||
AA_FS_FILE_BOOLEAN("set_load", 1),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct aa_fs_entry aa_fs_entry_features[] = {
|
||||
AA_FS_DIR("policy", aa_fs_entry_policy),
|
||||
AA_FS_DIR("domain", aa_fs_entry_domain),
|
||||
AA_FS_DIR("file", aa_fs_entry_file),
|
||||
AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
|
||||
AA_FS_DIR("rlimit", aa_fs_entry_rlimit),
|
||||
AA_FS_DIR("caps", aa_fs_entry_caps),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct aa_fs_entry aa_fs_entry_apparmor[] = {
|
||||
AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load),
|
||||
AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace),
|
||||
AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove),
|
||||
AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops),
|
||||
AA_FS_DIR("features", aa_fs_entry_features),
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct aa_fs_entry aa_fs_entry =
|
||||
AA_FS_DIR("apparmor", aa_fs_entry_apparmor);
|
||||
|
||||
/**
|
||||
* aafs_create_file - create a file entry in the apparmor securityfs
|
||||
* @fs_file: aa_fs_entry to build an entry for (NOT NULL)
|
||||
* @parent: the parent dentry in the securityfs
|
||||
*
|
||||
* Use aafs_remove_file to remove entries created with this fn.
|
||||
*/
|
||||
static int __init aafs_create_file(struct aa_fs_entry *fs_file,
|
||||
struct dentry *parent)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
fs_file->dentry = securityfs_create_file(fs_file->name,
|
||||
S_IFREG | fs_file->mode,
|
||||
parent, fs_file,
|
||||
fs_file->file_ops);
|
||||
if (IS_ERR(fs_file->dentry)) {
|
||||
error = PTR_ERR(fs_file->dentry);
|
||||
fs_file->dentry = NULL;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir);
|
||||
/**
|
||||
* aafs_create_dir - recursively create a directory entry in the securityfs
|
||||
* @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL)
|
||||
* @parent: the parent dentry in the securityfs
|
||||
*
|
||||
* Use aafs_remove_dir to remove entries created with this fn.
|
||||
*/
|
||||
static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
|
||||
struct dentry *parent)
|
||||
{
|
||||
struct aa_fs_entry *fs_file;
|
||||
struct dentry *dir;
|
||||
int error;
|
||||
|
||||
dir = securityfs_create_dir(fs_dir->name, parent);
|
||||
if (IS_ERR(dir))
|
||||
return PTR_ERR(dir);
|
||||
fs_dir->dentry = dir;
|
||||
|
||||
for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) {
|
||||
if (fs_file->v_type == AA_FS_TYPE_DIR)
|
||||
error = aafs_create_dir(fs_file, fs_dir->dentry);
|
||||
else
|
||||
error = aafs_create_file(fs_file, fs_dir->dentry);
|
||||
if (error)
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
aafs_remove_dir(fs_dir);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aafs_remove_file - drop a single file entry in the apparmor securityfs
|
||||
* @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL)
|
||||
*/
|
||||
static void __init aafs_remove_file(struct aa_fs_entry *fs_file)
|
||||
{
|
||||
if (!fs_file->dentry)
|
||||
return;
|
||||
|
||||
securityfs_remove(fs_file->dentry);
|
||||
fs_file->dentry = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* aafs_remove_dir - recursively drop a directory entry from the securityfs
|
||||
* @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL)
|
||||
*/
|
||||
static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir)
|
||||
{
|
||||
struct aa_fs_entry *fs_file;
|
||||
|
||||
for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) {
|
||||
if (fs_file->v_type == AA_FS_TYPE_DIR)
|
||||
aafs_remove_dir(fs_file);
|
||||
else
|
||||
aafs_remove_file(fs_file);
|
||||
}
|
||||
|
||||
aafs_remove_file(fs_dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_destroy_aafs - cleanup and free aafs
|
||||
*
|
||||
* releases dentries allocated by aa_create_aafs
|
||||
*/
|
||||
void __init aa_destroy_aafs(void)
|
||||
{
|
||||
aafs_remove_dir(&aa_fs_entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_create_aafs - create the apparmor security filesystem
|
||||
*
|
||||
* dentries created here are released by aa_destroy_aafs
|
||||
*
|
||||
* Returns: error on failure
|
||||
*/
|
||||
static int __init aa_create_aafs(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!apparmor_initialized)
|
||||
return 0;
|
||||
|
||||
if (aa_fs_entry.dentry) {
|
||||
AA_ERROR("%s: AppArmor securityfs already exists\n", __func__);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
/* Populate fs tree. */
|
||||
error = aafs_create_dir(&aa_fs_entry, NULL);
|
||||
if (error)
|
||||
goto error;
|
||||
|
||||
error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry,
|
||||
"policy");
|
||||
if (error)
|
||||
goto error;
|
||||
|
||||
/* TODO: add support for apparmorfs_null and apparmorfs_mnt */
|
||||
|
||||
/* Report that AppArmor fs is enabled */
|
||||
aa_info_message("AppArmor Filesystem Enabled");
|
||||
return 0;
|
||||
|
||||
error:
|
||||
aa_destroy_aafs();
|
||||
AA_ERROR("Error creating AppArmor securityfs\n");
|
||||
return error;
|
||||
}
|
||||
|
||||
fs_initcall(aa_create_aafs);
|
209
security/apparmor/audit.c
Normal file
209
security/apparmor/audit.c
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor auditing functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
const char *const op_table[] = {
|
||||
"null",
|
||||
|
||||
"sysctl",
|
||||
"capable",
|
||||
|
||||
"unlink",
|
||||
"mkdir",
|
||||
"rmdir",
|
||||
"mknod",
|
||||
"truncate",
|
||||
"link",
|
||||
"symlink",
|
||||
"rename_src",
|
||||
"rename_dest",
|
||||
"chmod",
|
||||
"chown",
|
||||
"getattr",
|
||||
"open",
|
||||
|
||||
"file_perm",
|
||||
"file_lock",
|
||||
"file_mmap",
|
||||
"file_mprotect",
|
||||
|
||||
"create",
|
||||
"post_create",
|
||||
"bind",
|
||||
"connect",
|
||||
"listen",
|
||||
"accept",
|
||||
"sendmsg",
|
||||
"recvmsg",
|
||||
"getsockname",
|
||||
"getpeername",
|
||||
"getsockopt",
|
||||
"setsockopt",
|
||||
"socket_shutdown",
|
||||
|
||||
"ptrace",
|
||||
|
||||
"exec",
|
||||
"change_hat",
|
||||
"change_profile",
|
||||
"change_onexec",
|
||||
|
||||
"setprocattr",
|
||||
"setrlimit",
|
||||
|
||||
"profile_replace",
|
||||
"profile_load",
|
||||
"profile_remove"
|
||||
};
|
||||
|
||||
const char *const audit_mode_names[] = {
|
||||
"normal",
|
||||
"quiet_denied",
|
||||
"quiet",
|
||||
"noquiet",
|
||||
"all"
|
||||
};
|
||||
|
||||
static const char *const aa_audit_type[] = {
|
||||
"AUDIT",
|
||||
"ALLOWED",
|
||||
"DENIED",
|
||||
"HINT",
|
||||
"STATUS",
|
||||
"ERROR",
|
||||
"KILLED",
|
||||
"AUTO"
|
||||
};
|
||||
|
||||
/*
|
||||
* Currently AppArmor auditing is fed straight into the audit framework.
|
||||
*
|
||||
* TODO:
|
||||
* netlink interface for complain mode
|
||||
* user auditing, - send user auditing to netlink interface
|
||||
* system control of whether user audit messages go to system log
|
||||
*/
|
||||
|
||||
/**
|
||||
* audit_base - core AppArmor function.
|
||||
* @ab: audit buffer to fill (NOT NULL)
|
||||
* @ca: audit structure containing data to audit (NOT NULL)
|
||||
*
|
||||
* Record common AppArmor audit data from @sa
|
||||
*/
|
||||
static void audit_pre(struct audit_buffer *ab, void *ca)
|
||||
{
|
||||
struct common_audit_data *sa = ca;
|
||||
|
||||
if (aa_g_audit_header) {
|
||||
audit_log_format(ab, "apparmor=");
|
||||
audit_log_string(ab, aa_audit_type[sa->aad->type]);
|
||||
}
|
||||
|
||||
if (sa->aad->op) {
|
||||
audit_log_format(ab, " operation=");
|
||||
audit_log_string(ab, op_table[sa->aad->op]);
|
||||
}
|
||||
|
||||
if (sa->aad->info) {
|
||||
audit_log_format(ab, " info=");
|
||||
audit_log_string(ab, sa->aad->info);
|
||||
if (sa->aad->error)
|
||||
audit_log_format(ab, " error=%d", sa->aad->error);
|
||||
}
|
||||
|
||||
if (sa->aad->profile) {
|
||||
struct aa_profile *profile = sa->aad->profile;
|
||||
if (profile->ns != root_ns) {
|
||||
audit_log_format(ab, " namespace=");
|
||||
audit_log_untrustedstring(ab, profile->ns->base.hname);
|
||||
}
|
||||
audit_log_format(ab, " profile=");
|
||||
audit_log_untrustedstring(ab, profile->base.hname);
|
||||
}
|
||||
|
||||
if (sa->aad->name) {
|
||||
audit_log_format(ab, " name=");
|
||||
audit_log_untrustedstring(ab, sa->aad->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_msg - Log a message to the audit subsystem
|
||||
* @sa: audit event structure (NOT NULL)
|
||||
* @cb: optional callback fn for type specific fields (MAYBE NULL)
|
||||
*/
|
||||
void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *))
|
||||
{
|
||||
sa->aad->type = type;
|
||||
common_lsm_audit(sa, audit_pre, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit - Log a profile based audit event to the audit subsystem
|
||||
* @type: audit type for the message
|
||||
* @profile: profile to check against (NOT NULL)
|
||||
* @gfp: allocation flags to use
|
||||
* @sa: audit event (NOT NULL)
|
||||
* @cb: optional callback fn for type specific fields (MAYBE NULL)
|
||||
*
|
||||
* Handle default message switching based off of audit mode flags
|
||||
*
|
||||
* Returns: error on failure
|
||||
*/
|
||||
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
||||
struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *))
|
||||
{
|
||||
BUG_ON(!profile);
|
||||
|
||||
if (type == AUDIT_APPARMOR_AUTO) {
|
||||
if (likely(!sa->aad->error)) {
|
||||
if (AUDIT_MODE(profile) != AUDIT_ALL)
|
||||
return 0;
|
||||
type = AUDIT_APPARMOR_AUDIT;
|
||||
} else if (COMPLAIN_MODE(profile))
|
||||
type = AUDIT_APPARMOR_ALLOWED;
|
||||
else
|
||||
type = AUDIT_APPARMOR_DENIED;
|
||||
}
|
||||
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
|
||||
(type == AUDIT_APPARMOR_DENIED &&
|
||||
AUDIT_MODE(profile) == AUDIT_QUIET))
|
||||
return sa->aad->error;
|
||||
|
||||
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
|
||||
type = AUDIT_APPARMOR_KILL;
|
||||
|
||||
if (!unconfined(profile))
|
||||
sa->aad->profile = profile;
|
||||
|
||||
aa_audit_msg(type, sa, cb);
|
||||
|
||||
if (sa->aad->type == AUDIT_APPARMOR_KILL)
|
||||
(void)send_sig_info(SIGKILL, NULL,
|
||||
sa->u.tsk ? sa->u.tsk : current);
|
||||
|
||||
if (sa->aad->type == AUDIT_APPARMOR_ALLOWED)
|
||||
return complain_error(sa->aad->error);
|
||||
|
||||
return sa->aad->error;
|
||||
}
|
143
security/apparmor/capability.c
Normal file
143
security/apparmor/capability.c
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor capability mediation functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/gfp.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/capability.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/audit.h"
|
||||
|
||||
/*
|
||||
* Table of capability names: we generate it from capabilities.h.
|
||||
*/
|
||||
#include "capability_names.h"
|
||||
|
||||
struct aa_fs_entry aa_fs_entry_caps[] = {
|
||||
AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK),
|
||||
{ }
|
||||
};
|
||||
|
||||
struct audit_cache {
|
||||
struct aa_profile *profile;
|
||||
kernel_cap_t caps;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct audit_cache, audit_cache);
|
||||
|
||||
/**
|
||||
* audit_cb - call back for capability components of audit struct
|
||||
* @ab - audit buffer (NOT NULL)
|
||||
* @va - audit struct to audit data from (NOT NULL)
|
||||
*/
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
audit_log_format(ab, " capname=");
|
||||
audit_log_untrustedstring(ab, capability_names[sa->u.cap]);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_caps - audit a capability
|
||||
* @profile: profile being tested for confinement (NOT NULL)
|
||||
* @cap: capability tested
|
||||
* @error: error code returned by test
|
||||
*
|
||||
* Do auditing of capability and handle, audit/complain/kill modes switching
|
||||
* and duplicate message elimination.
|
||||
*
|
||||
* Returns: 0 or sa->error on success, error code on failure
|
||||
*/
|
||||
static int audit_caps(struct aa_profile *profile, int cap, int error)
|
||||
{
|
||||
struct audit_cache *ent;
|
||||
int type = AUDIT_APPARMOR_AUTO;
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
sa.type = LSM_AUDIT_DATA_CAP;
|
||||
sa.aad = &aad;
|
||||
sa.u.cap = cap;
|
||||
sa.aad->op = OP_CAPABLE;
|
||||
sa.aad->error = error;
|
||||
|
||||
if (likely(!error)) {
|
||||
/* test if auditing is being forced */
|
||||
if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
|
||||
!cap_raised(profile->caps.audit, cap)))
|
||||
return 0;
|
||||
type = AUDIT_APPARMOR_AUDIT;
|
||||
} else if (KILL_MODE(profile) ||
|
||||
cap_raised(profile->caps.kill, cap)) {
|
||||
type = AUDIT_APPARMOR_KILL;
|
||||
} else if (cap_raised(profile->caps.quiet, cap) &&
|
||||
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
||||
AUDIT_MODE(profile) != AUDIT_ALL) {
|
||||
/* quiet auditing */
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Do simple duplicate message elimination */
|
||||
ent = &get_cpu_var(audit_cache);
|
||||
if (profile == ent->profile && cap_raised(ent->caps, cap)) {
|
||||
put_cpu_var(audit_cache);
|
||||
if (COMPLAIN_MODE(profile))
|
||||
return complain_error(error);
|
||||
return error;
|
||||
} else {
|
||||
aa_put_profile(ent->profile);
|
||||
ent->profile = aa_get_profile(profile);
|
||||
cap_raise(ent->caps, cap);
|
||||
}
|
||||
put_cpu_var(audit_cache);
|
||||
|
||||
return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* profile_capable - test if profile allows use of capability @cap
|
||||
* @profile: profile being enforced (NOT NULL, NOT unconfined)
|
||||
* @cap: capability to test if allowed
|
||||
*
|
||||
* Returns: 0 if allowed else -EPERM
|
||||
*/
|
||||
static int profile_capable(struct aa_profile *profile, int cap)
|
||||
{
|
||||
return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_capable - test permission to use capability
|
||||
* @profile: profile being tested against (NOT NULL)
|
||||
* @cap: capability to be tested
|
||||
* @audit: whether an audit record should be generated
|
||||
*
|
||||
* Look up capability in profile capability set.
|
||||
*
|
||||
* Returns: 0 on success, or else an error code.
|
||||
*/
|
||||
int aa_capable(struct aa_profile *profile, int cap, int audit)
|
||||
{
|
||||
int error = profile_capable(profile, cap);
|
||||
|
||||
if (!audit) {
|
||||
if (COMPLAIN_MODE(profile))
|
||||
return complain_error(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
return audit_caps(profile, cap, error);
|
||||
}
|
222
security/apparmor/context.c
Normal file
222
security/apparmor/context.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor functions used to manipulate object security
|
||||
* contexts.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
*
|
||||
* AppArmor sets confinement on every task, via the the aa_task_cxt and
|
||||
* the aa_task_cxt.profile, both of which are required and are not allowed
|
||||
* to be NULL. The aa_task_cxt is not reference counted and is unique
|
||||
* to each cred (which is reference count). The profile pointed to by
|
||||
* the task_cxt is reference counted.
|
||||
*
|
||||
* TODO
|
||||
* If a task uses change_hat it currently does not return to the old
|
||||
* cred or task context but instead creates a new one. Ideally the task
|
||||
* should return to the previous cred if it has not been modified.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/**
|
||||
* aa_alloc_task_context - allocate a new task_cxt
|
||||
* @flags: gfp flags for allocation
|
||||
*
|
||||
* Returns: allocated buffer or NULL on failure
|
||||
*/
|
||||
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags)
|
||||
{
|
||||
return kzalloc(sizeof(struct aa_task_cxt), flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_free_task_context - free a task_cxt
|
||||
* @cxt: task_cxt to free (MAYBE NULL)
|
||||
*/
|
||||
void aa_free_task_context(struct aa_task_cxt *cxt)
|
||||
{
|
||||
if (cxt) {
|
||||
aa_put_profile(cxt->profile);
|
||||
aa_put_profile(cxt->previous);
|
||||
aa_put_profile(cxt->onexec);
|
||||
|
||||
kzfree(cxt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dup_task_context - duplicate a task context, incrementing reference counts
|
||||
* @new: a blank task context (NOT NULL)
|
||||
* @old: the task context to copy (NOT NULL)
|
||||
*/
|
||||
void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old)
|
||||
{
|
||||
*new = *old;
|
||||
aa_get_profile(new->profile);
|
||||
aa_get_profile(new->previous);
|
||||
aa_get_profile(new->onexec);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_task_profile - Get another task's profile
|
||||
* @task: task to query (NOT NULL)
|
||||
*
|
||||
* Returns: counted reference to @task's profile
|
||||
*/
|
||||
struct aa_profile *aa_get_task_profile(struct task_struct *task)
|
||||
{
|
||||
struct aa_profile *p;
|
||||
|
||||
rcu_read_lock();
|
||||
p = aa_get_profile(__aa_task_profile(task));
|
||||
rcu_read_unlock();
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_replace_current_profile - replace the current tasks profiles
|
||||
* @profile: new profile (NOT NULL)
|
||||
*
|
||||
* Returns: 0 or error on failure
|
||||
*/
|
||||
int aa_replace_current_profile(struct aa_profile *profile)
|
||||
{
|
||||
struct aa_task_cxt *cxt = current_cxt();
|
||||
struct cred *new;
|
||||
BUG_ON(!profile);
|
||||
|
||||
if (cxt->profile == profile)
|
||||
return 0;
|
||||
|
||||
new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt = cred_cxt(new);
|
||||
if (unconfined(profile) || (cxt->profile->ns != profile->ns))
|
||||
/* if switching to unconfined or a different profile namespace
|
||||
* clear out context state
|
||||
*/
|
||||
aa_clear_task_cxt_trans(cxt);
|
||||
|
||||
/* be careful switching cxt->profile, when racing replacement it
|
||||
* is possible that cxt->profile->replacedby->profile is the reference
|
||||
* keeping @profile valid, so make sure to get its reference before
|
||||
* dropping the reference on cxt->profile */
|
||||
aa_get_profile(profile);
|
||||
aa_put_profile(cxt->profile);
|
||||
cxt->profile = profile;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_set_current_onexec - set the tasks change_profile to happen onexec
|
||||
* @profile: system profile to set at exec (MAYBE NULL to clear value)
|
||||
*
|
||||
* Returns: 0 or error on failure
|
||||
*/
|
||||
int aa_set_current_onexec(struct aa_profile *profile)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct cred *new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt = cred_cxt(new);
|
||||
aa_get_profile(profile);
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->onexec = profile;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_set_current_hat - set the current tasks hat
|
||||
* @profile: profile to set as the current hat (NOT NULL)
|
||||
* @token: token value that must be specified to change from the hat
|
||||
*
|
||||
* Do switch of tasks hat. If the task is currently in a hat
|
||||
* validate the token to match.
|
||||
*
|
||||
* Returns: 0 or error on failure
|
||||
*/
|
||||
int aa_set_current_hat(struct aa_profile *profile, u64 token)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct cred *new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
BUG_ON(!profile);
|
||||
|
||||
cxt = cred_cxt(new);
|
||||
if (!cxt->previous) {
|
||||
/* transfer refcount */
|
||||
cxt->previous = cxt->profile;
|
||||
cxt->token = token;
|
||||
} else if (cxt->token == token) {
|
||||
aa_put_profile(cxt->profile);
|
||||
} else {
|
||||
/* previous_profile && cxt->token != token */
|
||||
abort_creds(new);
|
||||
return -EACCES;
|
||||
}
|
||||
cxt->profile = aa_get_newest_profile(profile);
|
||||
/* clear exec on switching context */
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->onexec = NULL;
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_restore_previous_profile - exit from hat context restoring the profile
|
||||
* @token: the token that must be matched to exit hat context
|
||||
*
|
||||
* Attempt to return out of a hat to the previous profile. The token
|
||||
* must match the stored token value.
|
||||
*
|
||||
* Returns: 0 or error of failure
|
||||
*/
|
||||
int aa_restore_previous_profile(u64 token)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct cred *new = prepare_creds();
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt = cred_cxt(new);
|
||||
if (cxt->token != token) {
|
||||
abort_creds(new);
|
||||
return -EACCES;
|
||||
}
|
||||
/* ignore restores when there is no saved profile */
|
||||
if (!cxt->previous) {
|
||||
abort_creds(new);
|
||||
return 0;
|
||||
}
|
||||
|
||||
aa_put_profile(cxt->profile);
|
||||
cxt->profile = aa_get_newest_profile(cxt->previous);
|
||||
BUG_ON(!cxt->profile);
|
||||
/* clear exec && prev information when restoring to previous context */
|
||||
aa_clear_task_cxt_trans(cxt);
|
||||
|
||||
commit_creds(new);
|
||||
return 0;
|
||||
}
|
95
security/apparmor/crypto.c
Normal file
95
security/apparmor/crypto.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy loading interface function definitions.
|
||||
*
|
||||
* Copyright 2013 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
* Fns to provide a checksum of policy that has been loaded this can be
|
||||
* compared to userspace policy compiles to check loaded policy is what
|
||||
* it should be.
|
||||
*/
|
||||
|
||||
#include <crypto/hash.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/crypto.h"
|
||||
|
||||
static unsigned int apparmor_hash_size;
|
||||
|
||||
static struct crypto_shash *apparmor_tfm;
|
||||
|
||||
unsigned int aa_hash_size(void)
|
||||
{
|
||||
return apparmor_hash_size;
|
||||
}
|
||||
|
||||
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
||||
size_t len)
|
||||
{
|
||||
struct {
|
||||
struct shash_desc shash;
|
||||
char ctx[crypto_shash_descsize(apparmor_tfm)];
|
||||
} desc;
|
||||
int error = -ENOMEM;
|
||||
u32 le32_version = cpu_to_le32(version);
|
||||
|
||||
if (!apparmor_tfm)
|
||||
return 0;
|
||||
|
||||
profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
|
||||
if (!profile->hash)
|
||||
goto fail;
|
||||
|
||||
desc.shash.tfm = apparmor_tfm;
|
||||
desc.shash.flags = 0;
|
||||
|
||||
error = crypto_shash_init(&desc.shash);
|
||||
if (error)
|
||||
goto fail;
|
||||
error = crypto_shash_update(&desc.shash, (u8 *) &le32_version, 4);
|
||||
if (error)
|
||||
goto fail;
|
||||
error = crypto_shash_update(&desc.shash, (u8 *) start, len);
|
||||
if (error)
|
||||
goto fail;
|
||||
error = crypto_shash_final(&desc.shash, profile->hash);
|
||||
if (error)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kfree(profile->hash);
|
||||
profile->hash = NULL;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int __init init_profile_hash(void)
|
||||
{
|
||||
struct crypto_shash *tfm;
|
||||
|
||||
if (!apparmor_initialized)
|
||||
return 0;
|
||||
|
||||
tfm = crypto_alloc_shash("sha1", 0, CRYPTO_ALG_ASYNC);
|
||||
if (IS_ERR(tfm)) {
|
||||
int error = PTR_ERR(tfm);
|
||||
AA_ERROR("failed to setup profile sha1 hashing: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
apparmor_tfm = tfm;
|
||||
apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm);
|
||||
|
||||
aa_info_message("AppArmor sha1 policy hashing enabled");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
late_initcall(init_profile_hash);
|
854
security/apparmor/domain.c
Normal file
854
security/apparmor/domain.c
Normal file
|
@ -0,0 +1,854 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy attachment and domain transitions
|
||||
*
|
||||
* Copyright (C) 2002-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fdtable.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/tracehook.h>
|
||||
#include <linux/personality.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/apparmorfs.h"
|
||||
#include "include/context.h"
|
||||
#include "include/domain.h"
|
||||
#include "include/file.h"
|
||||
#include "include/ipc.h"
|
||||
#include "include/match.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/**
|
||||
* aa_free_domain_entries - free entries in a domain table
|
||||
* @domain: the domain table to free (MAYBE NULL)
|
||||
*/
|
||||
void aa_free_domain_entries(struct aa_domain *domain)
|
||||
{
|
||||
int i;
|
||||
if (domain) {
|
||||
if (!domain->table)
|
||||
return;
|
||||
|
||||
for (i = 0; i < domain->size; i++)
|
||||
kzfree(domain->table[i]);
|
||||
kzfree(domain->table);
|
||||
domain->table = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* may_change_ptraced_domain - check if can change profile on ptraced task
|
||||
* @to_profile: profile to change to (NOT NULL)
|
||||
*
|
||||
* Check if current is ptraced and if so if the tracing task is allowed
|
||||
* to trace the new domain
|
||||
*
|
||||
* Returns: %0 or error if change not allowed
|
||||
*/
|
||||
static int may_change_ptraced_domain(struct aa_profile *to_profile)
|
||||
{
|
||||
struct task_struct *tracer;
|
||||
struct aa_profile *tracerp = NULL;
|
||||
int error = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
tracer = ptrace_parent(current);
|
||||
if (tracer)
|
||||
/* released below */
|
||||
tracerp = aa_get_task_profile(tracer);
|
||||
|
||||
/* not ptraced */
|
||||
if (!tracer || unconfined(tracerp))
|
||||
goto out;
|
||||
|
||||
error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH);
|
||||
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
aa_put_profile(tracerp);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* change_profile_perms - find permissions for change_profile
|
||||
* @profile: the current profile (NOT NULL)
|
||||
* @ns: the namespace being switched to (NOT NULL)
|
||||
* @name: the name of the profile to change to (NOT NULL)
|
||||
* @request: requested perms
|
||||
* @start: state to start matching in
|
||||
*
|
||||
* Returns: permission set
|
||||
*/
|
||||
static struct file_perms change_profile_perms(struct aa_profile *profile,
|
||||
struct aa_namespace *ns,
|
||||
const char *name, u32 request,
|
||||
unsigned int start)
|
||||
{
|
||||
struct file_perms perms;
|
||||
struct path_cond cond = { };
|
||||
unsigned int state;
|
||||
|
||||
if (unconfined(profile)) {
|
||||
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
|
||||
perms.audit = perms.quiet = perms.kill = 0;
|
||||
return perms;
|
||||
} else if (!profile->file.dfa) {
|
||||
return nullperms;
|
||||
} else if ((ns == profile->ns)) {
|
||||
/* try matching against rules with out namespace prepended */
|
||||
aa_str_perms(profile->file.dfa, start, name, &cond, &perms);
|
||||
if (COMBINED_PERM_MASK(perms) & request)
|
||||
return perms;
|
||||
}
|
||||
|
||||
/* try matching with namespace name and then profile */
|
||||
state = aa_dfa_match(profile->file.dfa, start, ns->base.name);
|
||||
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
|
||||
aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* __attach_match_ - find an attachment match
|
||||
* @name - to match against (NOT NULL)
|
||||
* @head - profile list to walk (NOT NULL)
|
||||
*
|
||||
* Do a linear search on the profiles in the list. There is a matching
|
||||
* preference where an exact match is preferred over a name which uses
|
||||
* expressions to match, and matching expressions with the greatest
|
||||
* xmatch_len are preferred.
|
||||
*
|
||||
* Requires: @head not be shared or have appropriate locks held
|
||||
*
|
||||
* Returns: profile or NULL if no match found
|
||||
*/
|
||||
static struct aa_profile *__attach_match(const char *name,
|
||||
struct list_head *head)
|
||||
{
|
||||
int len = 0;
|
||||
struct aa_profile *profile, *candidate = NULL;
|
||||
|
||||
list_for_each_entry_rcu(profile, head, base.list) {
|
||||
if (profile->flags & PFLAG_NULL)
|
||||
continue;
|
||||
if (profile->xmatch && profile->xmatch_len > len) {
|
||||
unsigned int state = aa_dfa_match(profile->xmatch,
|
||||
DFA_START, name);
|
||||
u32 perm = dfa_user_allow(profile->xmatch, state);
|
||||
/* any accepting state means a valid match. */
|
||||
if (perm & MAY_EXEC) {
|
||||
candidate = profile;
|
||||
len = profile->xmatch_len;
|
||||
}
|
||||
} else if (!strcmp(profile->base.name, name))
|
||||
/* exact non-re match, no more searching required */
|
||||
return profile;
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_attach - do attachment search for unconfined processes
|
||||
* @ns: the current namespace (NOT NULL)
|
||||
* @list: list to search (NOT NULL)
|
||||
* @name: the executable name to match against (NOT NULL)
|
||||
*
|
||||
* Returns: profile or NULL if no match found
|
||||
*/
|
||||
static struct aa_profile *find_attach(struct aa_namespace *ns,
|
||||
struct list_head *list, const char *name)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
|
||||
rcu_read_lock();
|
||||
profile = aa_get_profile(__attach_match(name, list));
|
||||
rcu_read_unlock();
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* separate_fqname - separate the namespace and profile names
|
||||
* @fqname: the fqname name to split (NOT NULL)
|
||||
* @ns_name: the namespace name if it exists (NOT NULL)
|
||||
*
|
||||
* This is the xtable equivalent routine of aa_split_fqname. It finds the
|
||||
* split in an xtable fqname which contains an embedded \0 instead of a :
|
||||
* if a namespace is specified. This is done so the xtable is constant and
|
||||
* isn't re-split on every lookup.
|
||||
*
|
||||
* Either the profile or namespace name may be optional but if the namespace
|
||||
* is specified the profile name termination must be present. This results
|
||||
* in the following possible encodings:
|
||||
* profile_name\0
|
||||
* :ns_name\0profile_name\0
|
||||
* :ns_name\0\0
|
||||
*
|
||||
* NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table
|
||||
*
|
||||
* Returns: profile name if it is specified else NULL
|
||||
*/
|
||||
static const char *separate_fqname(const char *fqname, const char **ns_name)
|
||||
{
|
||||
const char *name;
|
||||
|
||||
if (fqname[0] == ':') {
|
||||
/* In this case there is guaranteed to be two \0 terminators
|
||||
* in the string. They are verified at load time by
|
||||
* by unpack_trans_table
|
||||
*/
|
||||
*ns_name = fqname + 1; /* skip : */
|
||||
name = *ns_name + strlen(*ns_name) + 1;
|
||||
if (!*name)
|
||||
name = NULL;
|
||||
} else {
|
||||
*ns_name = NULL;
|
||||
name = fqname;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static const char *next_name(int xtype, const char *name)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* x_table_lookup - lookup an x transition name via transition table
|
||||
* @profile: current profile (NOT NULL)
|
||||
* @xindex: index into x transition table
|
||||
*
|
||||
* Returns: refcounted profile, or NULL on failure (MAYBE NULL)
|
||||
*/
|
||||
static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
|
||||
{
|
||||
struct aa_profile *new_profile = NULL;
|
||||
struct aa_namespace *ns = profile->ns;
|
||||
u32 xtype = xindex & AA_X_TYPE_MASK;
|
||||
int index = xindex & AA_X_INDEX_MASK;
|
||||
const char *name;
|
||||
|
||||
/* index is guaranteed to be in range, validated at load time */
|
||||
for (name = profile->file.trans.table[index]; !new_profile && name;
|
||||
name = next_name(xtype, name)) {
|
||||
struct aa_namespace *new_ns;
|
||||
const char *xname = NULL;
|
||||
|
||||
new_ns = NULL;
|
||||
if (xindex & AA_X_CHILD) {
|
||||
/* release by caller */
|
||||
new_profile = aa_find_child(profile, name);
|
||||
continue;
|
||||
} else if (*name == ':') {
|
||||
/* switching namespace */
|
||||
const char *ns_name;
|
||||
xname = name = separate_fqname(name, &ns_name);
|
||||
if (!xname)
|
||||
/* no name so use profile name */
|
||||
xname = profile->base.hname;
|
||||
if (*ns_name == '@') {
|
||||
/* TODO: variable support */
|
||||
;
|
||||
}
|
||||
/* released below */
|
||||
new_ns = aa_find_namespace(ns, ns_name);
|
||||
if (!new_ns)
|
||||
continue;
|
||||
} else if (*name == '@') {
|
||||
/* TODO: variable support */
|
||||
continue;
|
||||
} else {
|
||||
/* basic namespace lookup */
|
||||
xname = name;
|
||||
}
|
||||
|
||||
/* released by caller */
|
||||
new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname);
|
||||
aa_put_namespace(new_ns);
|
||||
}
|
||||
|
||||
/* released by caller */
|
||||
return new_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* x_to_profile - get target profile for a given xindex
|
||||
* @profile: current profile (NOT NULL)
|
||||
* @name: name to lookup (NOT NULL)
|
||||
* @xindex: index into x transition table
|
||||
*
|
||||
* find profile for a transition index
|
||||
*
|
||||
* Returns: refcounted profile or NULL if not found available
|
||||
*/
|
||||
static struct aa_profile *x_to_profile(struct aa_profile *profile,
|
||||
const char *name, u32 xindex)
|
||||
{
|
||||
struct aa_profile *new_profile = NULL;
|
||||
struct aa_namespace *ns = profile->ns;
|
||||
u32 xtype = xindex & AA_X_TYPE_MASK;
|
||||
|
||||
switch (xtype) {
|
||||
case AA_X_NONE:
|
||||
/* fail exec unless ix || ux fallback - handled by caller */
|
||||
return NULL;
|
||||
case AA_X_NAME:
|
||||
if (xindex & AA_X_CHILD)
|
||||
/* released by caller */
|
||||
new_profile = find_attach(ns, &profile->base.profiles,
|
||||
name);
|
||||
else
|
||||
/* released by caller */
|
||||
new_profile = find_attach(ns, &ns->base.profiles,
|
||||
name);
|
||||
break;
|
||||
case AA_X_TABLE:
|
||||
/* released by caller */
|
||||
new_profile = x_table_lookup(profile, xindex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* released by caller */
|
||||
return new_profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_set_creds - set the new creds on the bprm struct
|
||||
* @bprm: binprm for the exec (NOT NULL)
|
||||
*
|
||||
* Returns: %0 or error on failure
|
||||
*/
|
||||
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
|
||||
{
|
||||
struct aa_task_cxt *cxt;
|
||||
struct aa_profile *profile, *new_profile = NULL;
|
||||
struct aa_namespace *ns;
|
||||
char *buffer = NULL;
|
||||
unsigned int state;
|
||||
struct file_perms perms = {};
|
||||
struct path_cond cond = {
|
||||
file_inode(bprm->file)->i_uid,
|
||||
file_inode(bprm->file)->i_mode
|
||||
};
|
||||
const char *name = NULL, *target = NULL, *info = NULL;
|
||||
int error = cap_bprm_set_creds(bprm);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
if (bprm->cred_prepared)
|
||||
return 0;
|
||||
|
||||
cxt = cred_cxt(bprm->cred);
|
||||
BUG_ON(!cxt);
|
||||
|
||||
profile = aa_get_newest_profile(cxt->profile);
|
||||
/*
|
||||
* get the namespace from the replacement profile as replacement
|
||||
* can change the namespace
|
||||
*/
|
||||
ns = profile->ns;
|
||||
state = profile->file.start;
|
||||
|
||||
/* buffer freed below, name is pointer into buffer */
|
||||
error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer,
|
||||
&name, &info);
|
||||
if (error) {
|
||||
if (unconfined(profile) ||
|
||||
(profile->flags & PFLAG_IX_ON_NAME_ERROR))
|
||||
error = 0;
|
||||
name = bprm->filename;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
/* Test for onexec first as onexec directives override other
|
||||
* x transitions.
|
||||
*/
|
||||
if (unconfined(profile)) {
|
||||
/* unconfined task */
|
||||
if (cxt->onexec)
|
||||
/* change_profile on exec already been granted */
|
||||
new_profile = aa_get_profile(cxt->onexec);
|
||||
else
|
||||
new_profile = find_attach(ns, &ns->base.profiles, name);
|
||||
if (!new_profile)
|
||||
goto cleanup;
|
||||
/*
|
||||
* NOTE: Domain transitions from unconfined are allowed
|
||||
* even when no_new_privs is set because this aways results
|
||||
* in a further reduction of permissions.
|
||||
*/
|
||||
goto apply;
|
||||
}
|
||||
|
||||
/* find exec permissions for name */
|
||||
state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
|
||||
if (cxt->onexec) {
|
||||
struct file_perms cp;
|
||||
info = "change_profile onexec";
|
||||
if (!(perms.allow & AA_MAY_ONEXEC))
|
||||
goto audit;
|
||||
|
||||
/* test if this exec can be paired with change_profile onexec.
|
||||
* onexec permission is linked to exec with a standard pairing
|
||||
* exec\0change_profile
|
||||
*/
|
||||
state = aa_dfa_null_transition(profile->file.dfa, state);
|
||||
cp = change_profile_perms(profile, cxt->onexec->ns,
|
||||
cxt->onexec->base.name,
|
||||
AA_MAY_ONEXEC, state);
|
||||
|
||||
if (!(cp.allow & AA_MAY_ONEXEC))
|
||||
goto audit;
|
||||
new_profile = aa_get_newest_profile(cxt->onexec);
|
||||
goto apply;
|
||||
}
|
||||
|
||||
if (perms.allow & MAY_EXEC) {
|
||||
/* exec permission determine how to transition */
|
||||
new_profile = x_to_profile(profile, name, perms.xindex);
|
||||
if (!new_profile) {
|
||||
if (perms.xindex & AA_X_INHERIT) {
|
||||
/* (p|c|n)ix - don't change profile but do
|
||||
* use the newest version, which was picked
|
||||
* up above when getting profile
|
||||
*/
|
||||
info = "ix fallback";
|
||||
new_profile = aa_get_profile(profile);
|
||||
goto x_clear;
|
||||
} else if (perms.xindex & AA_X_UNCONFINED) {
|
||||
new_profile = aa_get_newest_profile(ns->unconfined);
|
||||
info = "ux fallback";
|
||||
} else {
|
||||
error = -ENOENT;
|
||||
info = "profile not found";
|
||||
/* remove MAY_EXEC to audit as failure */
|
||||
perms.allow &= ~MAY_EXEC;
|
||||
}
|
||||
}
|
||||
} else if (COMPLAIN_MODE(profile)) {
|
||||
/* no exec permission - are we in learning mode */
|
||||
new_profile = aa_new_null_profile(profile, 0);
|
||||
if (!new_profile) {
|
||||
error = -ENOMEM;
|
||||
info = "could not create null profile";
|
||||
} else {
|
||||
error = -EACCES;
|
||||
target = new_profile->base.hname;
|
||||
}
|
||||
perms.xindex |= AA_X_UNSAFE;
|
||||
} else
|
||||
/* fail exec */
|
||||
error = -EACCES;
|
||||
|
||||
/*
|
||||
* Policy has specified a domain transition, if no_new_privs then
|
||||
* fail the exec.
|
||||
*/
|
||||
if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) {
|
||||
aa_put_profile(new_profile);
|
||||
error = -EPERM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!new_profile)
|
||||
goto audit;
|
||||
|
||||
if (bprm->unsafe & LSM_UNSAFE_SHARE) {
|
||||
/* FIXME: currently don't mediate shared state */
|
||||
;
|
||||
}
|
||||
|
||||
if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
|
||||
error = may_change_ptraced_domain(new_profile);
|
||||
if (error) {
|
||||
aa_put_profile(new_profile);
|
||||
goto audit;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine if secure exec is needed.
|
||||
* Can be at this point for the following reasons:
|
||||
* 1. unconfined switching to confined
|
||||
* 2. confined switching to different confinement
|
||||
* 3. confined switching to unconfined
|
||||
*
|
||||
* Cases 2 and 3 are marked as requiring secure exec
|
||||
* (unless policy specified "unsafe exec")
|
||||
*
|
||||
* bprm->unsafe is used to cache the AA_X_UNSAFE permission
|
||||
* to avoid having to recompute in secureexec
|
||||
*/
|
||||
if (!(perms.xindex & AA_X_UNSAFE)) {
|
||||
AA_DEBUG("scrubbing environment variables for %s profile=%s\n",
|
||||
name, new_profile->base.hname);
|
||||
bprm->unsafe |= AA_SECURE_X_NEEDED;
|
||||
}
|
||||
apply:
|
||||
target = new_profile->base.hname;
|
||||
/* when transitioning profiles clear unsafe personality bits */
|
||||
bprm->per_clear |= PER_CLEAR_ON_SETID;
|
||||
|
||||
x_clear:
|
||||
aa_put_profile(cxt->profile);
|
||||
/* transfer new profile reference will be released when cxt is freed */
|
||||
cxt->profile = new_profile;
|
||||
|
||||
/* clear out all temporary/transitional state from the context */
|
||||
aa_clear_task_cxt_trans(cxt);
|
||||
|
||||
audit:
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC,
|
||||
name, target, cond.uid, info, error);
|
||||
|
||||
cleanup:
|
||||
aa_put_profile(profile);
|
||||
kfree(buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_secureexec - determine if secureexec is needed
|
||||
* @bprm: binprm for exec (NOT NULL)
|
||||
*
|
||||
* Returns: %1 if secureexec is needed else %0
|
||||
*/
|
||||
int apparmor_bprm_secureexec(struct linux_binprm *bprm)
|
||||
{
|
||||
int ret = cap_bprm_secureexec(bprm);
|
||||
|
||||
/* the decision to use secure exec is computed in set_creds
|
||||
* and stored in bprm->unsafe.
|
||||
*/
|
||||
if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED))
|
||||
ret = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_committing_creds - do task cleanup on committing new creds
|
||||
* @bprm: binprm for the exec (NOT NULL)
|
||||
*/
|
||||
void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
|
||||
{
|
||||
struct aa_profile *profile = __aa_current_profile();
|
||||
struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred);
|
||||
|
||||
/* bail out if unconfined or not changing profile */
|
||||
if ((new_cxt->profile == profile) ||
|
||||
(unconfined(new_cxt->profile)))
|
||||
return;
|
||||
|
||||
current->pdeath_signal = 0;
|
||||
|
||||
/* reset soft limits and set hard limits for the new profile */
|
||||
__aa_transition_rlimits(profile, new_cxt->profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* apparmor_bprm_commited_cred - do cleanup after new creds committed
|
||||
* @bprm: binprm for the exec (NOT NULL)
|
||||
*/
|
||||
void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
|
||||
{
|
||||
/* TODO: cleanup signals - ipc mediation */
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for self directed profile change
|
||||
*/
|
||||
|
||||
/**
|
||||
* new_compound_name - create an hname with @n2 appended to @n1
|
||||
* @n1: base of hname (NOT NULL)
|
||||
* @n2: name to append (NOT NULL)
|
||||
*
|
||||
* Returns: new name or NULL on error
|
||||
*/
|
||||
static char *new_compound_name(const char *n1, const char *n2)
|
||||
{
|
||||
char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL);
|
||||
if (name)
|
||||
sprintf(name, "%s//%s", n1, n2);
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_change_hat - change hat to/from subprofile
|
||||
* @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0)
|
||||
* @count: number of hat names in @hats
|
||||
* @token: magic value to validate the hat change
|
||||
* @permtest: true if this is just a permission test
|
||||
*
|
||||
* Change to the first profile specified in @hats that exists, and store
|
||||
* the @hat_magic in the current task context. If the count == 0 and the
|
||||
* @token matches that stored in the current task context, return to the
|
||||
* top level profile.
|
||||
*
|
||||
* Returns %0 on success, error otherwise.
|
||||
*/
|
||||
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
|
||||
{
|
||||
const struct cred *cred;
|
||||
struct aa_task_cxt *cxt;
|
||||
struct aa_profile *profile, *previous_profile, *hat = NULL;
|
||||
char *name = NULL;
|
||||
int i;
|
||||
struct file_perms perms = {};
|
||||
const char *target = NULL, *info = NULL;
|
||||
int error = 0;
|
||||
|
||||
/*
|
||||
* Fail explicitly requested domain transitions if no_new_privs.
|
||||
* There is no exception for unconfined as change_hat is not
|
||||
* available.
|
||||
*/
|
||||
if (task_no_new_privs(current))
|
||||
return -EPERM;
|
||||
|
||||
/* released below */
|
||||
cred = get_current_cred();
|
||||
cxt = cred_cxt(cred);
|
||||
profile = aa_cred_profile(cred);
|
||||
previous_profile = cxt->previous;
|
||||
|
||||
if (unconfined(profile)) {
|
||||
info = "unconfined";
|
||||
error = -EPERM;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
if (count) {
|
||||
/* attempting to change into a new hat or switch to a sibling */
|
||||
struct aa_profile *root;
|
||||
if (PROFILE_IS_HAT(profile))
|
||||
root = aa_get_profile_rcu(&profile->parent);
|
||||
else
|
||||
root = aa_get_profile(profile);
|
||||
|
||||
/* find first matching hat */
|
||||
for (i = 0; i < count && !hat; i++)
|
||||
/* released below */
|
||||
hat = aa_find_child(root, hats[i]);
|
||||
if (!hat) {
|
||||
if (!COMPLAIN_MODE(root) || permtest) {
|
||||
if (list_empty(&root->base.profiles))
|
||||
error = -ECHILD;
|
||||
else
|
||||
error = -ENOENT;
|
||||
aa_put_profile(root);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* In complain mode and failed to match any hats.
|
||||
* Audit the failure is based off of the first hat
|
||||
* supplied. This is done due how userspace
|
||||
* interacts with change_hat.
|
||||
*
|
||||
* TODO: Add logging of all failed hats
|
||||
*/
|
||||
|
||||
/* freed below */
|
||||
name = new_compound_name(root->base.hname, hats[0]);
|
||||
aa_put_profile(root);
|
||||
target = name;
|
||||
/* released below */
|
||||
hat = aa_new_null_profile(profile, 1);
|
||||
if (!hat) {
|
||||
info = "failed null profile create";
|
||||
error = -ENOMEM;
|
||||
goto audit;
|
||||
}
|
||||
} else {
|
||||
aa_put_profile(root);
|
||||
target = hat->base.hname;
|
||||
if (!PROFILE_IS_HAT(hat)) {
|
||||
info = "target not hat";
|
||||
error = -EPERM;
|
||||
goto audit;
|
||||
}
|
||||
}
|
||||
|
||||
error = may_change_ptraced_domain(hat);
|
||||
if (error) {
|
||||
info = "ptraced";
|
||||
error = -EPERM;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
if (!permtest) {
|
||||
error = aa_set_current_hat(hat, token);
|
||||
if (error == -EACCES)
|
||||
/* kill task in case of brute force attacks */
|
||||
perms.kill = AA_MAY_CHANGEHAT;
|
||||
else if (name && !error)
|
||||
/* reset error for learning of new hats */
|
||||
error = -ENOENT;
|
||||
}
|
||||
} else if (previous_profile) {
|
||||
/* Return to saved profile. Kill task if restore fails
|
||||
* to avoid brute force attacks
|
||||
*/
|
||||
target = previous_profile->base.hname;
|
||||
error = aa_restore_previous_profile(token);
|
||||
perms.kill = AA_MAY_CHANGEHAT;
|
||||
} else
|
||||
/* ignore restores when there is no saved profile */
|
||||
goto out;
|
||||
|
||||
audit:
|
||||
if (!permtest)
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL,
|
||||
OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL,
|
||||
target, GLOBAL_ROOT_UID, info, error);
|
||||
|
||||
out:
|
||||
aa_put_profile(hat);
|
||||
kfree(name);
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_change_profile - perform a one-way profile transition
|
||||
* @ns_name: name of the profile namespace to change to (MAYBE NULL)
|
||||
* @hname: name of profile to change to (MAYBE NULL)
|
||||
* @onexec: whether this transition is to take place immediately or at exec
|
||||
* @permtest: true if this is just a permission test
|
||||
*
|
||||
* Change to new profile @name. Unlike with hats, there is no way
|
||||
* to change back. If @name isn't specified the current profile name is
|
||||
* used.
|
||||
* If @onexec then the transition is delayed until
|
||||
* the next exec.
|
||||
*
|
||||
* Returns %0 on success, error otherwise.
|
||||
*/
|
||||
int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
|
||||
bool permtest)
|
||||
{
|
||||
const struct cred *cred;
|
||||
struct aa_profile *profile, *target = NULL;
|
||||
struct aa_namespace *ns = NULL;
|
||||
struct file_perms perms = {};
|
||||
const char *name = NULL, *info = NULL;
|
||||
int op, error = 0;
|
||||
u32 request;
|
||||
|
||||
if (!hname && !ns_name)
|
||||
return -EINVAL;
|
||||
|
||||
if (onexec) {
|
||||
request = AA_MAY_ONEXEC;
|
||||
op = OP_CHANGE_ONEXEC;
|
||||
} else {
|
||||
request = AA_MAY_CHANGE_PROFILE;
|
||||
op = OP_CHANGE_PROFILE;
|
||||
}
|
||||
|
||||
cred = get_current_cred();
|
||||
profile = aa_cred_profile(cred);
|
||||
|
||||
/*
|
||||
* Fail explicitly requested domain transitions if no_new_privs
|
||||
* and not unconfined.
|
||||
* Domain transitions from unconfined are allowed even when
|
||||
* no_new_privs is set because this aways results in a reduction
|
||||
* of permissions.
|
||||
*/
|
||||
if (task_no_new_privs(current) && !unconfined(profile)) {
|
||||
put_cred(cred);
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
if (ns_name) {
|
||||
/* released below */
|
||||
ns = aa_find_namespace(profile->ns, ns_name);
|
||||
if (!ns) {
|
||||
/* we don't create new namespace in complain mode */
|
||||
name = ns_name;
|
||||
info = "namespace not found";
|
||||
error = -ENOENT;
|
||||
goto audit;
|
||||
}
|
||||
} else
|
||||
/* released below */
|
||||
ns = aa_get_namespace(profile->ns);
|
||||
|
||||
/* if the name was not specified, use the name of the current profile */
|
||||
if (!hname) {
|
||||
if (unconfined(profile))
|
||||
hname = ns->unconfined->base.hname;
|
||||
else
|
||||
hname = profile->base.hname;
|
||||
}
|
||||
|
||||
perms = change_profile_perms(profile, ns, hname, request,
|
||||
profile->file.start);
|
||||
if (!(perms.allow & request)) {
|
||||
error = -EACCES;
|
||||
goto audit;
|
||||
}
|
||||
|
||||
/* released below */
|
||||
target = aa_lookup_profile(ns, hname);
|
||||
if (!target) {
|
||||
info = "profile not found";
|
||||
error = -ENOENT;
|
||||
if (permtest || !COMPLAIN_MODE(profile))
|
||||
goto audit;
|
||||
/* released below */
|
||||
target = aa_new_null_profile(profile, 0);
|
||||
if (!target) {
|
||||
info = "failed null profile create";
|
||||
error = -ENOMEM;
|
||||
goto audit;
|
||||
}
|
||||
}
|
||||
|
||||
/* check if tracing task is allowed to trace target domain */
|
||||
error = may_change_ptraced_domain(target);
|
||||
if (error) {
|
||||
info = "ptrace prevents transition";
|
||||
goto audit;
|
||||
}
|
||||
|
||||
if (permtest)
|
||||
goto audit;
|
||||
|
||||
if (onexec)
|
||||
error = aa_set_current_onexec(target);
|
||||
else
|
||||
error = aa_replace_current_profile(target);
|
||||
|
||||
audit:
|
||||
if (!permtest)
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request,
|
||||
name, hname, GLOBAL_ROOT_UID, info, error);
|
||||
|
||||
aa_put_namespace(ns);
|
||||
aa_put_profile(target);
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
458
security/apparmor/file.c
Normal file
458
security/apparmor/file.c
Normal file
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor mediation of files
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/file.h"
|
||||
#include "include/match.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
struct file_perms nullperms;
|
||||
|
||||
|
||||
/**
|
||||
* audit_file_mask - convert mask to permission string
|
||||
* @buffer: buffer to write string to (NOT NULL)
|
||||
* @mask: permission mask to convert
|
||||
*/
|
||||
static void audit_file_mask(struct audit_buffer *ab, u32 mask)
|
||||
{
|
||||
char str[10];
|
||||
|
||||
char *m = str;
|
||||
|
||||
if (mask & AA_EXEC_MMAP)
|
||||
*m++ = 'm';
|
||||
if (mask & (MAY_READ | AA_MAY_META_READ))
|
||||
*m++ = 'r';
|
||||
if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
|
||||
AA_MAY_CHOWN))
|
||||
*m++ = 'w';
|
||||
else if (mask & MAY_APPEND)
|
||||
*m++ = 'a';
|
||||
if (mask & AA_MAY_CREATE)
|
||||
*m++ = 'c';
|
||||
if (mask & AA_MAY_DELETE)
|
||||
*m++ = 'd';
|
||||
if (mask & AA_MAY_LINK)
|
||||
*m++ = 'l';
|
||||
if (mask & AA_MAY_LOCK)
|
||||
*m++ = 'k';
|
||||
if (mask & MAY_EXEC)
|
||||
*m++ = 'x';
|
||||
*m = '\0';
|
||||
|
||||
audit_log_string(ab, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* file_audit_cb - call back for file specific audit fields
|
||||
* @ab: audit_buffer (NOT NULL)
|
||||
* @va: audit struct to audit values of (NOT NULL)
|
||||
*/
|
||||
static void file_audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
kuid_t fsuid = current_fsuid();
|
||||
|
||||
if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {
|
||||
audit_log_format(ab, " requested_mask=");
|
||||
audit_file_mask(ab, sa->aad->fs.request);
|
||||
}
|
||||
if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) {
|
||||
audit_log_format(ab, " denied_mask=");
|
||||
audit_file_mask(ab, sa->aad->fs.denied);
|
||||
}
|
||||
if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {
|
||||
audit_log_format(ab, " fsuid=%d",
|
||||
from_kuid(&init_user_ns, fsuid));
|
||||
audit_log_format(ab, " ouid=%d",
|
||||
from_kuid(&init_user_ns, sa->aad->fs.ouid));
|
||||
}
|
||||
|
||||
if (sa->aad->fs.target) {
|
||||
audit_log_format(ab, " target=");
|
||||
audit_log_untrustedstring(ab, sa->aad->fs.target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_file - handle the auditing of file operations
|
||||
* @profile: the profile being enforced (NOT NULL)
|
||||
* @perms: the permissions computed for the request (NOT NULL)
|
||||
* @gfp: allocation flags
|
||||
* @op: operation being mediated
|
||||
* @request: permissions requested
|
||||
* @name: name of object being mediated (MAYBE NULL)
|
||||
* @target: name of target (MAYBE NULL)
|
||||
* @ouid: object uid
|
||||
* @info: extra information message (MAYBE NULL)
|
||||
* @error: 0 if operation allowed else failure error code
|
||||
*
|
||||
* Returns: %0 or error on failure
|
||||
*/
|
||||
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
|
||||
gfp_t gfp, int op, u32 request, const char *name,
|
||||
const char *target, kuid_t ouid, const char *info, int error)
|
||||
{
|
||||
int type = AUDIT_APPARMOR_AUTO;
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
sa.type = LSM_AUDIT_DATA_NONE;
|
||||
sa.aad = &aad;
|
||||
aad.op = op,
|
||||
aad.fs.request = request;
|
||||
aad.name = name;
|
||||
aad.fs.target = target;
|
||||
aad.fs.ouid = ouid;
|
||||
aad.info = info;
|
||||
aad.error = error;
|
||||
|
||||
if (likely(!sa.aad->error)) {
|
||||
u32 mask = perms->audit;
|
||||
|
||||
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
|
||||
mask = 0xffff;
|
||||
|
||||
/* mask off perms that are not being force audited */
|
||||
sa.aad->fs.request &= mask;
|
||||
|
||||
if (likely(!sa.aad->fs.request))
|
||||
return 0;
|
||||
type = AUDIT_APPARMOR_AUDIT;
|
||||
} else {
|
||||
/* only report permissions that were denied */
|
||||
sa.aad->fs.request = sa.aad->fs.request & ~perms->allow;
|
||||
|
||||
if (sa.aad->fs.request & perms->kill)
|
||||
type = AUDIT_APPARMOR_KILL;
|
||||
|
||||
/* quiet known rejects, assumes quiet and kill do not overlap */
|
||||
if ((sa.aad->fs.request & perms->quiet) &&
|
||||
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
||||
AUDIT_MODE(profile) != AUDIT_ALL)
|
||||
sa.aad->fs.request &= ~perms->quiet;
|
||||
|
||||
if (!sa.aad->fs.request)
|
||||
return COMPLAIN_MODE(profile) ? 0 : sa.aad->error;
|
||||
}
|
||||
|
||||
sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow;
|
||||
return aa_audit(type, profile, gfp, &sa, file_audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* map_old_perms - map old file perms layout to the new layout
|
||||
* @old: permission set in old mapping
|
||||
*
|
||||
* Returns: new permission mapping
|
||||
*/
|
||||
static u32 map_old_perms(u32 old)
|
||||
{
|
||||
u32 new = old & 0xf;
|
||||
if (old & MAY_READ)
|
||||
new |= AA_MAY_META_READ;
|
||||
if (old & MAY_WRITE)
|
||||
new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
|
||||
AA_MAY_CHMOD | AA_MAY_CHOWN;
|
||||
if (old & 0x10)
|
||||
new |= AA_MAY_LINK;
|
||||
/* the old mapping lock and link_subset flags where overlaid
|
||||
* and use was determined by part of a pair that they were in
|
||||
*/
|
||||
if (old & 0x20)
|
||||
new |= AA_MAY_LOCK | AA_LINK_SUBSET;
|
||||
if (old & 0x40) /* AA_EXEC_MMAP */
|
||||
new |= AA_EXEC_MMAP;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
/**
|
||||
* compute_perms - convert dfa compressed perms to internal perms
|
||||
* @dfa: dfa to compute perms for (NOT NULL)
|
||||
* @state: state in dfa
|
||||
* @cond: conditions to consider (NOT NULL)
|
||||
*
|
||||
* TODO: convert from dfa + state to permission entry, do computation conversion
|
||||
* at load time.
|
||||
*
|
||||
* Returns: computed permission set
|
||||
*/
|
||||
static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
|
||||
struct path_cond *cond)
|
||||
{
|
||||
struct file_perms perms;
|
||||
|
||||
/* FIXME: change over to new dfa format
|
||||
* currently file perms are encoded in the dfa, new format
|
||||
* splits the permissions from the dfa. This mapping can be
|
||||
* done at profile load
|
||||
*/
|
||||
perms.kill = 0;
|
||||
|
||||
if (uid_eq(current_fsuid(), cond->uid)) {
|
||||
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
|
||||
perms.audit = map_old_perms(dfa_user_audit(dfa, state));
|
||||
perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
|
||||
perms.xindex = dfa_user_xindex(dfa, state);
|
||||
} else {
|
||||
perms.allow = map_old_perms(dfa_other_allow(dfa, state));
|
||||
perms.audit = map_old_perms(dfa_other_audit(dfa, state));
|
||||
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
|
||||
perms.xindex = dfa_other_xindex(dfa, state);
|
||||
}
|
||||
perms.allow |= AA_MAY_META_READ;
|
||||
|
||||
/* change_profile wasn't determined by ownership in old mapping */
|
||||
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
|
||||
perms.allow |= AA_MAY_CHANGE_PROFILE;
|
||||
if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
|
||||
perms.allow |= AA_MAY_ONEXEC;
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_str_perms - find permission that match @name
|
||||
* @dfa: to match against (MAYBE NULL)
|
||||
* @state: state to start matching in
|
||||
* @name: string to match against dfa (NOT NULL)
|
||||
* @cond: conditions to consider for permission set computation (NOT NULL)
|
||||
* @perms: Returns - the permissions found when matching @name
|
||||
*
|
||||
* Returns: the final state in @dfa when beginning @start and walking @name
|
||||
*/
|
||||
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *name, struct path_cond *cond,
|
||||
struct file_perms *perms)
|
||||
{
|
||||
unsigned int state;
|
||||
if (!dfa) {
|
||||
*perms = nullperms;
|
||||
return DFA_NOMATCH;
|
||||
}
|
||||
|
||||
state = aa_dfa_match(dfa, start, name);
|
||||
*perms = compute_perms(dfa, state, cond);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_deleted - test if a file has been completely unlinked
|
||||
* @dentry: dentry of file to test for deletion (NOT NULL)
|
||||
*
|
||||
* Returns: %1 if deleted else %0
|
||||
*/
|
||||
static inline bool is_deleted(struct dentry *dentry)
|
||||
{
|
||||
if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_path_perm - do permissions check & audit for @path
|
||||
* @op: operation being checked
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @path: path to check permissions of (NOT NULL)
|
||||
* @flags: any additional path flags beyond what the profile specifies
|
||||
* @request: requested permissions
|
||||
* @cond: conditional info for this request (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error if access denied or other error
|
||||
*/
|
||||
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
|
||||
int flags, u32 request, struct path_cond *cond)
|
||||
{
|
||||
char *buffer = NULL;
|
||||
struct file_perms perms = {};
|
||||
const char *name, *info = NULL;
|
||||
int error;
|
||||
|
||||
flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
|
||||
error = aa_path_name(path, flags, &buffer, &name, &info);
|
||||
if (error) {
|
||||
if (error == -ENOENT && is_deleted(path->dentry)) {
|
||||
/* Access to open files that are deleted are
|
||||
* give a pass (implicit delegation)
|
||||
*/
|
||||
error = 0;
|
||||
info = NULL;
|
||||
perms.allow = request;
|
||||
}
|
||||
} else {
|
||||
aa_str_perms(profile->file.dfa, profile->file.start, name, cond,
|
||||
&perms);
|
||||
if (request & ~perms.allow)
|
||||
error = -EACCES;
|
||||
}
|
||||
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name,
|
||||
NULL, cond->uid, info, error);
|
||||
kfree(buffer);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* xindex_is_subset - helper for aa_path_link
|
||||
* @link: link permission set
|
||||
* @target: target permission set
|
||||
*
|
||||
* test target x permissions are equal OR a subset of link x permissions
|
||||
* this is done as part of the subset test, where a hardlink must have
|
||||
* a subset of permissions that the target has.
|
||||
*
|
||||
* Returns: %1 if subset else %0
|
||||
*/
|
||||
static inline bool xindex_is_subset(u32 link, u32 target)
|
||||
{
|
||||
if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||
|
||||
((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_path_link - Handle hard link permission check
|
||||
* @profile: the profile being enforced (NOT NULL)
|
||||
* @old_dentry: the target dentry (NOT NULL)
|
||||
* @new_dir: directory the new link will be created in (NOT NULL)
|
||||
* @new_dentry: the link being created (NOT NULL)
|
||||
*
|
||||
* Handle the permission test for a link & target pair. Permission
|
||||
* is encoded as a pair where the link permission is determined
|
||||
* first, and if allowed, the target is tested. The target test
|
||||
* is done from the point of the link match (not start of DFA)
|
||||
* making the target permission dependent on the link permission match.
|
||||
*
|
||||
* The subset test if required forces that permissions granted
|
||||
* on link are a subset of the permission granted to target.
|
||||
*
|
||||
* Returns: %0 if allowed else error
|
||||
*/
|
||||
int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
||||
struct path *new_dir, struct dentry *new_dentry)
|
||||
{
|
||||
struct path link = { new_dir->mnt, new_dentry };
|
||||
struct path target = { new_dir->mnt, old_dentry };
|
||||
struct path_cond cond = {
|
||||
old_dentry->d_inode->i_uid,
|
||||
old_dentry->d_inode->i_mode
|
||||
};
|
||||
char *buffer = NULL, *buffer2 = NULL;
|
||||
const char *lname, *tname = NULL, *info = NULL;
|
||||
struct file_perms lperms, perms;
|
||||
u32 request = AA_MAY_LINK;
|
||||
unsigned int state;
|
||||
int error;
|
||||
|
||||
lperms = nullperms;
|
||||
|
||||
/* buffer freed below, lname is pointer in buffer */
|
||||
error = aa_path_name(&link, profile->path_flags, &buffer, &lname,
|
||||
&info);
|
||||
if (error)
|
||||
goto audit;
|
||||
|
||||
/* buffer2 freed below, tname is pointer in buffer2 */
|
||||
error = aa_path_name(&target, profile->path_flags, &buffer2, &tname,
|
||||
&info);
|
||||
if (error)
|
||||
goto audit;
|
||||
|
||||
error = -EACCES;
|
||||
/* aa_str_perms - handles the case of the dfa being NULL */
|
||||
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
|
||||
&cond, &lperms);
|
||||
|
||||
if (!(lperms.allow & AA_MAY_LINK))
|
||||
goto audit;
|
||||
|
||||
/* test to see if target can be paired with link */
|
||||
state = aa_dfa_null_transition(profile->file.dfa, state);
|
||||
aa_str_perms(profile->file.dfa, state, tname, &cond, &perms);
|
||||
|
||||
/* force audit/quiet masks for link are stored in the second entry
|
||||
* in the link pair.
|
||||
*/
|
||||
lperms.audit = perms.audit;
|
||||
lperms.quiet = perms.quiet;
|
||||
lperms.kill = perms.kill;
|
||||
|
||||
if (!(perms.allow & AA_MAY_LINK)) {
|
||||
info = "target restricted";
|
||||
goto audit;
|
||||
}
|
||||
|
||||
/* done if link subset test is not required */
|
||||
if (!(perms.allow & AA_LINK_SUBSET))
|
||||
goto done_tests;
|
||||
|
||||
/* Do link perm subset test requiring allowed permission on link are a
|
||||
* subset of the allowed permissions on target.
|
||||
*/
|
||||
aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond,
|
||||
&perms);
|
||||
|
||||
/* AA_MAY_LINK is not considered in the subset test */
|
||||
request = lperms.allow & ~AA_MAY_LINK;
|
||||
lperms.allow &= perms.allow | AA_MAY_LINK;
|
||||
|
||||
request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow);
|
||||
if (request & ~lperms.allow) {
|
||||
goto audit;
|
||||
} else if ((lperms.allow & MAY_EXEC) &&
|
||||
!xindex_is_subset(lperms.xindex, perms.xindex)) {
|
||||
lperms.allow &= ~MAY_EXEC;
|
||||
request |= MAY_EXEC;
|
||||
info = "link not subset of target";
|
||||
goto audit;
|
||||
}
|
||||
|
||||
done_tests:
|
||||
error = 0;
|
||||
|
||||
audit:
|
||||
error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request,
|
||||
lname, tname, cond.uid, info, error);
|
||||
kfree(buffer);
|
||||
kfree(buffer2);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_file_perm - do permission revalidation check & audit for @file
|
||||
* @op: operation being checked
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @file: file to revalidate access permissions on (NOT NULL)
|
||||
* @request: requested permissions
|
||||
*
|
||||
* Returns: %0 if access allowed else error
|
||||
*/
|
||||
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
|
||||
u32 request)
|
||||
{
|
||||
struct path_cond cond = {
|
||||
.uid = file_inode(file)->i_uid,
|
||||
.mode = file_inode(file)->i_mode
|
||||
};
|
||||
|
||||
return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
|
||||
request, &cond);
|
||||
}
|
120
security/apparmor/include/apparmor.h
Normal file
120
security/apparmor/include/apparmor.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor basic global and lib definitions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __APPARMOR_H
|
||||
#define __APPARMOR_H
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs.h>
|
||||
|
||||
#include "match.h"
|
||||
|
||||
/*
|
||||
* Class of mediation types in the AppArmor policy db
|
||||
*/
|
||||
#define AA_CLASS_ENTRY 0
|
||||
#define AA_CLASS_UNKNOWN 1
|
||||
#define AA_CLASS_FILE 2
|
||||
#define AA_CLASS_CAP 3
|
||||
#define AA_CLASS_NET 4
|
||||
#define AA_CLASS_RLIMITS 5
|
||||
#define AA_CLASS_DOMAIN 6
|
||||
|
||||
#define AA_CLASS_LAST AA_CLASS_DOMAIN
|
||||
|
||||
/* Control parameters settable through module/boot flags */
|
||||
extern enum audit_mode aa_g_audit;
|
||||
extern bool aa_g_audit_header;
|
||||
extern bool aa_g_debug;
|
||||
extern bool aa_g_lock_policy;
|
||||
extern bool aa_g_logsyscall;
|
||||
extern bool aa_g_paranoid_load;
|
||||
extern unsigned int aa_g_path_max;
|
||||
|
||||
/*
|
||||
* DEBUG remains global (no per profile flag) since it is mostly used in sysctl
|
||||
* which is not related to profile accesses.
|
||||
*/
|
||||
|
||||
#define AA_DEBUG(fmt, args...) \
|
||||
do { \
|
||||
if (aa_g_debug && printk_ratelimit()) \
|
||||
printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define AA_ERROR(fmt, args...) \
|
||||
do { \
|
||||
if (printk_ratelimit()) \
|
||||
printk(KERN_ERR "AppArmor: " fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
/* Flag indicating whether initialization completed */
|
||||
extern int apparmor_initialized __initdata;
|
||||
|
||||
/* fn's in lib */
|
||||
char *aa_split_fqname(char *args, char **ns_name);
|
||||
void aa_info_message(const char *str);
|
||||
void *__aa_kvmalloc(size_t size, gfp_t flags);
|
||||
|
||||
static inline void *kvmalloc(size_t size)
|
||||
{
|
||||
return __aa_kvmalloc(size, 0);
|
||||
}
|
||||
|
||||
static inline void *kvzalloc(size_t size)
|
||||
{
|
||||
return __aa_kvmalloc(size, __GFP_ZERO);
|
||||
}
|
||||
|
||||
/* returns 0 if kref not incremented */
|
||||
static inline int kref_get_not0(struct kref *kref)
|
||||
{
|
||||
return atomic_inc_not_zero(&kref->refcount);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_strneq - compare null terminated @str to a non null terminated substring
|
||||
* @str: a null terminated string
|
||||
* @sub: a substring, not necessarily null terminated
|
||||
* @len: length of @sub to compare
|
||||
*
|
||||
* The @str string must be full consumed for this to be considered a match
|
||||
*/
|
||||
static inline bool aa_strneq(const char *str, const char *sub, int len)
|
||||
{
|
||||
return !strncmp(str, sub, len) && !str[len];
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_null_transition - step to next state after null character
|
||||
* @dfa: the dfa to match against
|
||||
* @start: the state of the dfa to start matching in
|
||||
*
|
||||
* aa_dfa_null_transition transitions to the next state after a null
|
||||
* character which is not used in standard matching and is only
|
||||
* used to separate pairs.
|
||||
*/
|
||||
static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
|
||||
unsigned int start)
|
||||
{
|
||||
/* the null transition only needs the string's null terminator byte */
|
||||
return aa_dfa_next(dfa, start, 0);
|
||||
}
|
||||
|
||||
static inline bool mediated_filesystem(struct inode *inode)
|
||||
{
|
||||
return !(inode->i_sb->s_flags & MS_NOUSER);
|
||||
}
|
||||
|
||||
#endif /* __APPARMOR_H */
|
104
security/apparmor/include/apparmorfs.h
Normal file
104
security/apparmor/include/apparmorfs.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor filesystem definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_APPARMORFS_H
|
||||
#define __AA_APPARMORFS_H
|
||||
|
||||
enum aa_fs_type {
|
||||
AA_FS_TYPE_BOOLEAN,
|
||||
AA_FS_TYPE_STRING,
|
||||
AA_FS_TYPE_U64,
|
||||
AA_FS_TYPE_FOPS,
|
||||
AA_FS_TYPE_DIR,
|
||||
};
|
||||
|
||||
struct aa_fs_entry;
|
||||
|
||||
struct aa_fs_entry {
|
||||
const char *name;
|
||||
struct dentry *dentry;
|
||||
umode_t mode;
|
||||
enum aa_fs_type v_type;
|
||||
union {
|
||||
bool boolean;
|
||||
char *string;
|
||||
unsigned long u64;
|
||||
struct aa_fs_entry *files;
|
||||
} v;
|
||||
const struct file_operations *file_ops;
|
||||
};
|
||||
|
||||
extern const struct file_operations aa_fs_seq_file_ops;
|
||||
|
||||
#define AA_FS_FILE_BOOLEAN(_name, _value) \
|
||||
{ .name = (_name), .mode = 0444, \
|
||||
.v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \
|
||||
.file_ops = &aa_fs_seq_file_ops }
|
||||
#define AA_FS_FILE_STRING(_name, _value) \
|
||||
{ .name = (_name), .mode = 0444, \
|
||||
.v_type = AA_FS_TYPE_STRING, .v.string = (_value), \
|
||||
.file_ops = &aa_fs_seq_file_ops }
|
||||
#define AA_FS_FILE_U64(_name, _value) \
|
||||
{ .name = (_name), .mode = 0444, \
|
||||
.v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \
|
||||
.file_ops = &aa_fs_seq_file_ops }
|
||||
#define AA_FS_FILE_FOPS(_name, _mode, _fops) \
|
||||
{ .name = (_name), .v_type = AA_FS_TYPE_FOPS, \
|
||||
.mode = (_mode), .file_ops = (_fops) }
|
||||
#define AA_FS_DIR(_name, _value) \
|
||||
{ .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) }
|
||||
|
||||
extern void __init aa_destroy_aafs(void);
|
||||
|
||||
struct aa_profile;
|
||||
struct aa_namespace;
|
||||
|
||||
enum aafs_ns_type {
|
||||
AAFS_NS_DIR,
|
||||
AAFS_NS_PROFS,
|
||||
AAFS_NS_NS,
|
||||
AAFS_NS_COUNT,
|
||||
AAFS_NS_MAX_COUNT,
|
||||
AAFS_NS_SIZE,
|
||||
AAFS_NS_MAX_SIZE,
|
||||
AAFS_NS_OWNER,
|
||||
AAFS_NS_SIZEOF,
|
||||
};
|
||||
|
||||
enum aafs_prof_type {
|
||||
AAFS_PROF_DIR,
|
||||
AAFS_PROF_PROFS,
|
||||
AAFS_PROF_NAME,
|
||||
AAFS_PROF_MODE,
|
||||
AAFS_PROF_ATTACH,
|
||||
AAFS_PROF_HASH,
|
||||
AAFS_PROF_SIZEOF,
|
||||
};
|
||||
|
||||
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
|
||||
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
|
||||
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
|
||||
|
||||
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
|
||||
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
|
||||
|
||||
void __aa_fs_profile_rmdir(struct aa_profile *profile);
|
||||
void __aa_fs_profile_migrate_dents(struct aa_profile *old,
|
||||
struct aa_profile *new);
|
||||
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
|
||||
void __aa_fs_namespace_rmdir(struct aa_namespace *ns);
|
||||
int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
|
||||
const char *name);
|
||||
|
||||
#endif /* __AA_APPARMORFS_H */
|
147
security/apparmor/include/audit.h
Normal file
147
security/apparmor/include/audit.h
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor auditing function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_AUDIT_H
|
||||
#define __AA_AUDIT_H
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/lsm_audit.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "file.h"
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
extern const char *const audit_mode_names[];
|
||||
#define AUDIT_MAX_INDEX 5
|
||||
enum audit_mode {
|
||||
AUDIT_NORMAL, /* follow normal auditing of accesses */
|
||||
AUDIT_QUIET_DENIED, /* quiet all denied access messages */
|
||||
AUDIT_QUIET, /* quiet all messages */
|
||||
AUDIT_NOQUIET, /* do not quiet audit messages */
|
||||
AUDIT_ALL /* audit all accesses */
|
||||
};
|
||||
|
||||
enum audit_type {
|
||||
AUDIT_APPARMOR_AUDIT,
|
||||
AUDIT_APPARMOR_ALLOWED,
|
||||
AUDIT_APPARMOR_DENIED,
|
||||
AUDIT_APPARMOR_HINT,
|
||||
AUDIT_APPARMOR_STATUS,
|
||||
AUDIT_APPARMOR_ERROR,
|
||||
AUDIT_APPARMOR_KILL,
|
||||
AUDIT_APPARMOR_AUTO
|
||||
};
|
||||
|
||||
extern const char *const op_table[];
|
||||
enum aa_ops {
|
||||
OP_NULL,
|
||||
|
||||
OP_SYSCTL,
|
||||
OP_CAPABLE,
|
||||
|
||||
OP_UNLINK,
|
||||
OP_MKDIR,
|
||||
OP_RMDIR,
|
||||
OP_MKNOD,
|
||||
OP_TRUNC,
|
||||
OP_LINK,
|
||||
OP_SYMLINK,
|
||||
OP_RENAME_SRC,
|
||||
OP_RENAME_DEST,
|
||||
OP_CHMOD,
|
||||
OP_CHOWN,
|
||||
OP_GETATTR,
|
||||
OP_OPEN,
|
||||
|
||||
OP_FPERM,
|
||||
OP_FLOCK,
|
||||
OP_FMMAP,
|
||||
OP_FMPROT,
|
||||
|
||||
OP_CREATE,
|
||||
OP_POST_CREATE,
|
||||
OP_BIND,
|
||||
OP_CONNECT,
|
||||
OP_LISTEN,
|
||||
OP_ACCEPT,
|
||||
OP_SENDMSG,
|
||||
OP_RECVMSG,
|
||||
OP_GETSOCKNAME,
|
||||
OP_GETPEERNAME,
|
||||
OP_GETSOCKOPT,
|
||||
OP_SETSOCKOPT,
|
||||
OP_SOCK_SHUTDOWN,
|
||||
|
||||
OP_PTRACE,
|
||||
|
||||
OP_EXEC,
|
||||
OP_CHANGE_HAT,
|
||||
OP_CHANGE_PROFILE,
|
||||
OP_CHANGE_ONEXEC,
|
||||
|
||||
OP_SETPROCATTR,
|
||||
OP_SETRLIMIT,
|
||||
|
||||
OP_PROF_REPL,
|
||||
OP_PROF_LOAD,
|
||||
OP_PROF_RM,
|
||||
};
|
||||
|
||||
|
||||
struct apparmor_audit_data {
|
||||
int error;
|
||||
int op;
|
||||
int type;
|
||||
void *profile;
|
||||
const char *name;
|
||||
const char *info;
|
||||
union {
|
||||
void *target;
|
||||
struct {
|
||||
long pos;
|
||||
void *target;
|
||||
} iface;
|
||||
struct {
|
||||
int rlim;
|
||||
unsigned long max;
|
||||
} rlim;
|
||||
struct {
|
||||
const char *target;
|
||||
u32 request;
|
||||
u32 denied;
|
||||
kuid_t ouid;
|
||||
} fs;
|
||||
};
|
||||
};
|
||||
|
||||
/* define a short hand for apparmor_audit_data structure */
|
||||
#define aad apparmor_audit_data
|
||||
|
||||
void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *));
|
||||
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
||||
struct common_audit_data *sa,
|
||||
void (*cb) (struct audit_buffer *, void *));
|
||||
|
||||
static inline int complain_error(int error)
|
||||
{
|
||||
if (error == -EPERM || error == -EACCES)
|
||||
return 0;
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif /* __AA_AUDIT_H */
|
48
security/apparmor/include/capability.h
Normal file
48
security/apparmor/include/capability.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor capability mediation definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2013 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_CAPABILITY_H
|
||||
#define __AA_CAPABILITY_H
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "apparmorfs.h"
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/* aa_caps - confinement data for capabilities
|
||||
* @allowed: capabilities mask
|
||||
* @audit: caps that are to be audited
|
||||
* @quiet: caps that should not be audited
|
||||
* @kill: caps that when requested will result in the task being killed
|
||||
* @extended: caps that are subject finer grained mediation
|
||||
*/
|
||||
struct aa_caps {
|
||||
kernel_cap_t allow;
|
||||
kernel_cap_t audit;
|
||||
kernel_cap_t quiet;
|
||||
kernel_cap_t kill;
|
||||
kernel_cap_t extended;
|
||||
};
|
||||
|
||||
extern struct aa_fs_entry aa_fs_entry_caps[];
|
||||
|
||||
int aa_capable(struct aa_profile *profile, int cap, int audit);
|
||||
|
||||
static inline void aa_free_cap_rules(struct aa_caps *caps)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
#endif /* __AA_CAPBILITY_H */
|
178
security/apparmor/include/context.h
Normal file
178
security/apparmor/include/context.h
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor contexts used to associate "labels" to objects.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_CONTEXT_H
|
||||
#define __AA_CONTEXT_H
|
||||
|
||||
#include <linux/cred.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "policy.h"
|
||||
|
||||
#define cred_cxt(X) (X)->security
|
||||
#define current_cxt() cred_cxt(current_cred())
|
||||
|
||||
/* struct aa_file_cxt - the AppArmor context the file was opened in
|
||||
* @perms: the permission the file was opened with
|
||||
*
|
||||
* The file_cxt could currently be directly stored in file->f_security
|
||||
* as the profile reference is now stored in the f_cred. However the
|
||||
* cxt struct will expand in the future so we keep the struct.
|
||||
*/
|
||||
struct aa_file_cxt {
|
||||
u16 allow;
|
||||
};
|
||||
|
||||
/**
|
||||
* aa_alloc_file_context - allocate file_cxt
|
||||
* @gfp: gfp flags for allocation
|
||||
*
|
||||
* Returns: file_cxt or NULL on failure
|
||||
*/
|
||||
static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
|
||||
{
|
||||
return kzalloc(sizeof(struct aa_file_cxt), gfp);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_free_file_context - free a file_cxt
|
||||
* @cxt: file_cxt to free (MAYBE_NULL)
|
||||
*/
|
||||
static inline void aa_free_file_context(struct aa_file_cxt *cxt)
|
||||
{
|
||||
if (cxt)
|
||||
kzfree(cxt);
|
||||
}
|
||||
|
||||
/**
|
||||
* struct aa_task_cxt - primary label for confined tasks
|
||||
* @profile: the current profile (NOT NULL)
|
||||
* @exec: profile to transition to on next exec (MAYBE NULL)
|
||||
* @previous: profile the task may return to (MAYBE NULL)
|
||||
* @token: magic value the task must know for returning to @previous_profile
|
||||
*
|
||||
* Contains the task's current profile (which could change due to
|
||||
* change_hat). Plus the hat_magic needed during change_hat.
|
||||
*
|
||||
* TODO: make so a task can be confined by a stack of contexts
|
||||
*/
|
||||
struct aa_task_cxt {
|
||||
struct aa_profile *profile;
|
||||
struct aa_profile *onexec;
|
||||
struct aa_profile *previous;
|
||||
u64 token;
|
||||
};
|
||||
|
||||
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
|
||||
void aa_free_task_context(struct aa_task_cxt *cxt);
|
||||
void aa_dup_task_context(struct aa_task_cxt *new,
|
||||
const struct aa_task_cxt *old);
|
||||
int aa_replace_current_profile(struct aa_profile *profile);
|
||||
int aa_set_current_onexec(struct aa_profile *profile);
|
||||
int aa_set_current_hat(struct aa_profile *profile, u64 token);
|
||||
int aa_restore_previous_profile(u64 cookie);
|
||||
struct aa_profile *aa_get_task_profile(struct task_struct *task);
|
||||
|
||||
|
||||
/**
|
||||
* aa_cred_profile - obtain cred's profiles
|
||||
* @cred: cred to obtain profiles from (NOT NULL)
|
||||
*
|
||||
* Returns: confining profile
|
||||
*
|
||||
* does NOT increment reference count
|
||||
*/
|
||||
static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
|
||||
{
|
||||
struct aa_task_cxt *cxt = cred_cxt(cred);
|
||||
BUG_ON(!cxt || !cxt->profile);
|
||||
return cxt->profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_task_profile - retrieve another task's profile
|
||||
* @task: task to query (NOT NULL)
|
||||
*
|
||||
* Returns: @task's profile without incrementing its ref count
|
||||
*
|
||||
* If @task != current needs to be called in RCU safe critical section
|
||||
*/
|
||||
static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
|
||||
{
|
||||
return aa_cred_profile(__task_cred(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_task_is_confined - determine if @task has any confinement
|
||||
* @task: task to check confinement of (NOT NULL)
|
||||
*
|
||||
* If @task != current needs to be called in RCU safe critical section
|
||||
*/
|
||||
static inline bool __aa_task_is_confined(struct task_struct *task)
|
||||
{
|
||||
return !unconfined(__aa_task_profile(task));
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_current_profile - find the current tasks confining profile
|
||||
*
|
||||
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
|
||||
*
|
||||
* This fn will not update the tasks cred to the most up to date version
|
||||
* of the profile so it is safe to call when inside of locks.
|
||||
*/
|
||||
static inline struct aa_profile *__aa_current_profile(void)
|
||||
{
|
||||
return aa_cred_profile(current_cred());
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_current_profile - find the current tasks confining profile and do updates
|
||||
*
|
||||
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
|
||||
*
|
||||
* This fn will update the tasks cred structure if the profile has been
|
||||
* replaced. Not safe to call inside locks
|
||||
*/
|
||||
static inline struct aa_profile *aa_current_profile(void)
|
||||
{
|
||||
const struct aa_task_cxt *cxt = current_cxt();
|
||||
struct aa_profile *profile;
|
||||
BUG_ON(!cxt || !cxt->profile);
|
||||
|
||||
if (PROFILE_INVALID(cxt->profile)) {
|
||||
profile = aa_get_newest_profile(cxt->profile);
|
||||
aa_replace_current_profile(profile);
|
||||
aa_put_profile(profile);
|
||||
cxt = current_cxt();
|
||||
}
|
||||
|
||||
return cxt->profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_clear_task_cxt_trans - clear transition tracking info from the cxt
|
||||
* @cxt: task context to clear (NOT NULL)
|
||||
*/
|
||||
static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt)
|
||||
{
|
||||
aa_put_profile(cxt->previous);
|
||||
aa_put_profile(cxt->onexec);
|
||||
cxt->previous = NULL;
|
||||
cxt->onexec = NULL;
|
||||
cxt->token = 0;
|
||||
}
|
||||
|
||||
#endif /* __AA_CONTEXT_H */
|
36
security/apparmor/include/crypto.h
Normal file
36
security/apparmor/include/crypto.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy loading interface function definitions.
|
||||
*
|
||||
* Copyright 2013 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __APPARMOR_CRYPTO_H
|
||||
#define __APPARMOR_CRYPTO_H
|
||||
|
||||
#include "policy.h"
|
||||
|
||||
#ifdef CONFIG_SECURITY_APPARMOR_HASH
|
||||
unsigned int aa_hash_size(void);
|
||||
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
||||
size_t len);
|
||||
#else
|
||||
static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
|
||||
void *start, size_t len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline unsigned int aa_hash_size(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __APPARMOR_CRYPTO_H */
|
36
security/apparmor/include/domain.h
Normal file
36
security/apparmor/include/domain.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor security domain transition function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#ifndef __AA_DOMAIN_H
|
||||
#define __AA_DOMAIN_H
|
||||
|
||||
struct aa_domain {
|
||||
int size;
|
||||
char **table;
|
||||
};
|
||||
|
||||
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
|
||||
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
|
||||
void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
|
||||
void apparmor_bprm_committed_creds(struct linux_binprm *bprm);
|
||||
|
||||
void aa_free_domain_entries(struct aa_domain *domain);
|
||||
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest);
|
||||
int aa_change_profile(const char *ns_name, const char *name, bool onexec,
|
||||
bool permtest);
|
||||
|
||||
#endif /* __AA_DOMAIN_H */
|
216
security/apparmor/include/file.h
Normal file
216
security/apparmor/include/file.h
Normal file
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor file mediation function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_FILE_H
|
||||
#define __AA_FILE_H
|
||||
|
||||
#include "domain.h"
|
||||
#include "match.h"
|
||||
|
||||
struct aa_profile;
|
||||
struct path;
|
||||
|
||||
/*
|
||||
* We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
|
||||
* for profile permissions
|
||||
*/
|
||||
#define AA_MAY_CREATE 0x0010
|
||||
#define AA_MAY_DELETE 0x0020
|
||||
#define AA_MAY_META_WRITE 0x0040
|
||||
#define AA_MAY_META_READ 0x0080
|
||||
|
||||
#define AA_MAY_CHMOD 0x0100
|
||||
#define AA_MAY_CHOWN 0x0200
|
||||
#define AA_MAY_LOCK 0x0400
|
||||
#define AA_EXEC_MMAP 0x0800
|
||||
|
||||
#define AA_MAY_LINK 0x1000
|
||||
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
|
||||
#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
|
||||
#define AA_MAY_CHANGE_PROFILE 0x80000000
|
||||
#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
|
||||
|
||||
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
|
||||
AA_MAY_CREATE | AA_MAY_DELETE | \
|
||||
AA_MAY_META_READ | AA_MAY_META_WRITE | \
|
||||
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
|
||||
AA_EXEC_MMAP | AA_MAY_LINK)
|
||||
|
||||
/*
|
||||
* The xindex is broken into 3 parts
|
||||
* - index - an index into either the exec name table or the variable table
|
||||
* - exec type - which determines how the executable name and index are used
|
||||
* - flags - which modify how the destination name is applied
|
||||
*/
|
||||
#define AA_X_INDEX_MASK 0x03ff
|
||||
|
||||
#define AA_X_TYPE_MASK 0x0c00
|
||||
#define AA_X_TYPE_SHIFT 10
|
||||
#define AA_X_NONE 0x0000
|
||||
#define AA_X_NAME 0x0400 /* use executable name px */
|
||||
#define AA_X_TABLE 0x0800 /* use a specified name ->n# */
|
||||
|
||||
#define AA_X_UNSAFE 0x1000
|
||||
#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */
|
||||
#define AA_X_INHERIT 0x4000
|
||||
#define AA_X_UNCONFINED 0x8000
|
||||
|
||||
/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */
|
||||
#define AA_SECURE_X_NEEDED 0x8000
|
||||
|
||||
/* need to make conditional which ones are being set */
|
||||
struct path_cond {
|
||||
kuid_t uid;
|
||||
umode_t mode;
|
||||
};
|
||||
|
||||
/* struct file_perms - file permission
|
||||
* @allow: mask of permissions that are allowed
|
||||
* @audit: mask of permissions to force an audit message for
|
||||
* @quiet: mask of permissions to quiet audit messages for
|
||||
* @kill: mask of permissions that when matched will kill the task
|
||||
* @xindex: exec transition index if @allow contains MAY_EXEC
|
||||
*
|
||||
* The @audit and @queit mask should be mutually exclusive.
|
||||
*/
|
||||
struct file_perms {
|
||||
u32 allow;
|
||||
u32 audit;
|
||||
u32 quiet;
|
||||
u32 kill;
|
||||
u16 xindex;
|
||||
};
|
||||
|
||||
extern struct file_perms nullperms;
|
||||
|
||||
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
|
||||
|
||||
/* FIXME: split perms from dfa and match this to description
|
||||
* also add delegation info.
|
||||
*/
|
||||
static inline u16 dfa_map_xindex(u16 mask)
|
||||
{
|
||||
u16 old_index = (mask >> 10) & 0xf;
|
||||
u16 index = 0;
|
||||
|
||||
if (mask & 0x100)
|
||||
index |= AA_X_UNSAFE;
|
||||
if (mask & 0x200)
|
||||
index |= AA_X_INHERIT;
|
||||
if (mask & 0x80)
|
||||
index |= AA_X_UNCONFINED;
|
||||
|
||||
if (old_index == 1) {
|
||||
index |= AA_X_UNCONFINED;
|
||||
} else if (old_index == 2) {
|
||||
index |= AA_X_NAME;
|
||||
} else if (old_index == 3) {
|
||||
index |= AA_X_NAME | AA_X_CHILD;
|
||||
} else if (old_index) {
|
||||
index |= AA_X_TABLE;
|
||||
index |= old_index - 4;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/*
|
||||
* map old dfa inline permissions to new format
|
||||
*/
|
||||
#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
|
||||
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
|
||||
#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
|
||||
#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
|
||||
#define dfa_user_xindex(dfa, state) \
|
||||
(dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
|
||||
|
||||
#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
|
||||
0x7f) | \
|
||||
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
|
||||
#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
|
||||
#define dfa_other_quiet(dfa, state) \
|
||||
((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
|
||||
#define dfa_other_xindex(dfa, state) \
|
||||
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
|
||||
|
||||
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
|
||||
gfp_t gfp, int op, u32 request, const char *name,
|
||||
const char *target, kuid_t ouid, const char *info, int error);
|
||||
|
||||
/**
|
||||
* struct aa_file_rules - components used for file rule permissions
|
||||
* @dfa: dfa to match path names and conditionals against
|
||||
* @perms: permission table indexed by the matched state accept entry of @dfa
|
||||
* @trans: transition table for indexed by named x transitions
|
||||
*
|
||||
* File permission are determined by matching a path against @dfa and then
|
||||
* then using the value of the accept entry for the matching state as
|
||||
* an index into @perms. If a named exec transition is required it is
|
||||
* looked up in the transition table.
|
||||
*/
|
||||
struct aa_file_rules {
|
||||
unsigned int start;
|
||||
struct aa_dfa *dfa;
|
||||
/* struct perms perms; */
|
||||
struct aa_domain trans;
|
||||
/* TODO: add delegate table */
|
||||
};
|
||||
|
||||
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *name, struct path_cond *cond,
|
||||
struct file_perms *perms);
|
||||
|
||||
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
|
||||
int flags, u32 request, struct path_cond *cond);
|
||||
|
||||
int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
||||
struct path *new_dir, struct dentry *new_dentry);
|
||||
|
||||
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
|
||||
u32 request);
|
||||
|
||||
static inline void aa_free_file_rules(struct aa_file_rules *rules)
|
||||
{
|
||||
aa_put_dfa(rules->dfa);
|
||||
aa_free_domain_entries(&rules->trans);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_map_file_perms - map file flags to AppArmor permissions
|
||||
* @file: open file to map flags to AppArmor permissions
|
||||
*
|
||||
* Returns: apparmor permission set for the file
|
||||
*/
|
||||
static inline u32 aa_map_file_to_perms(struct file *file)
|
||||
{
|
||||
int flags = file->f_flags;
|
||||
u32 perms = 0;
|
||||
|
||||
if (file->f_mode & FMODE_WRITE)
|
||||
perms |= MAY_WRITE;
|
||||
if (file->f_mode & FMODE_READ)
|
||||
perms |= MAY_READ;
|
||||
|
||||
if ((flags & O_APPEND) && (perms & MAY_WRITE))
|
||||
perms = (perms & ~MAY_WRITE) | MAY_APPEND;
|
||||
/* trunc implies write permission */
|
||||
if (flags & O_TRUNC)
|
||||
perms |= MAY_WRITE;
|
||||
if (flags & O_CREAT)
|
||||
perms |= AA_MAY_CREATE;
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
#endif /* __AA_FILE_H */
|
28
security/apparmor/include/ipc.h
Normal file
28
security/apparmor/include/ipc.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor ipc mediation function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_IPC_H
|
||||
#define __AA_IPC_H
|
||||
|
||||
#include <linux/sched.h>
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
|
||||
unsigned int mode);
|
||||
|
||||
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
|
||||
unsigned int mode);
|
||||
|
||||
#endif /* __AA_IPC_H */
|
141
security/apparmor/include/match.h
Normal file
141
security/apparmor/include/match.h
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy dfa matching engine definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2012 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_MATCH_H
|
||||
#define __AA_MATCH_H
|
||||
|
||||
#include <linux/kref.h>
|
||||
|
||||
#define DFA_NOMATCH 0
|
||||
#define DFA_START 1
|
||||
|
||||
|
||||
/**
|
||||
* The format used for transition tables is based on the GNU flex table
|
||||
* file format (--tables-file option; see Table File Format in the flex
|
||||
* info pages and the flex sources for documentation). The magic number
|
||||
* used in the header is 0x1B5E783D instead of 0xF13C57B1 though, because
|
||||
* new tables have been defined and others YY_ID_CHK (check) and YY_ID_DEF
|
||||
* (default) tables are used slightly differently (see the apparmor-parser
|
||||
* package).
|
||||
*
|
||||
*
|
||||
* The data in the packed dfa is stored in network byte order, and the tables
|
||||
* are arranged for flexibility. We convert the table data to host native
|
||||
* byte order.
|
||||
*
|
||||
* The dfa begins with a table set header, and is followed by the actual
|
||||
* tables.
|
||||
*/
|
||||
|
||||
#define YYTH_MAGIC 0x1B5E783D
|
||||
|
||||
struct table_set_header {
|
||||
u32 th_magic; /* YYTH_MAGIC */
|
||||
u32 th_hsize;
|
||||
u32 th_ssize;
|
||||
u16 th_flags;
|
||||
char th_version[];
|
||||
};
|
||||
|
||||
/* The YYTD_ID are one less than flex table mappings. The flex id
|
||||
* has 1 subtracted at table load time, this allows us to directly use the
|
||||
* ID's as indexes.
|
||||
*/
|
||||
#define YYTD_ID_ACCEPT 0
|
||||
#define YYTD_ID_BASE 1
|
||||
#define YYTD_ID_CHK 2
|
||||
#define YYTD_ID_DEF 3
|
||||
#define YYTD_ID_EC 4
|
||||
#define YYTD_ID_META 5
|
||||
#define YYTD_ID_ACCEPT2 6
|
||||
#define YYTD_ID_NXT 7
|
||||
#define YYTD_ID_TSIZE 8
|
||||
|
||||
#define YYTD_DATA8 1
|
||||
#define YYTD_DATA16 2
|
||||
#define YYTD_DATA32 4
|
||||
#define YYTD_DATA64 8
|
||||
|
||||
/* ACCEPT & ACCEPT2 tables gets 6 dedicated flags, YYTD_DATAX define the
|
||||
* first flags
|
||||
*/
|
||||
#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
|
||||
#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2)
|
||||
#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X)
|
||||
#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2)
|
||||
#define DFA_FLAG_VERIFY_STATES 0x1000
|
||||
|
||||
struct table_header {
|
||||
u16 td_id;
|
||||
u16 td_flags;
|
||||
u32 td_hilen;
|
||||
u32 td_lolen;
|
||||
char td_data[];
|
||||
};
|
||||
|
||||
#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF]->td_data))
|
||||
#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data))
|
||||
#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data))
|
||||
#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data))
|
||||
#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data))
|
||||
#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data))
|
||||
#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data))
|
||||
|
||||
struct aa_dfa {
|
||||
struct kref count;
|
||||
u16 flags;
|
||||
struct table_header *tables[YYTD_ID_TSIZE];
|
||||
};
|
||||
|
||||
#define byte_to_byte(X) (X)
|
||||
|
||||
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
|
||||
do { \
|
||||
typeof(LEN) __i; \
|
||||
TYPE *__t = (TYPE *) TABLE; \
|
||||
TYPE *__b = (TYPE *) BLOB; \
|
||||
for (__i = 0; __i < LEN; __i++) { \
|
||||
__t[__i] = NTOHX(__b[__i]); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static inline size_t table_size(size_t len, size_t el_size)
|
||||
{
|
||||
return ALIGN(sizeof(struct table_header) + len * el_size, 8);
|
||||
}
|
||||
|
||||
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
|
||||
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str, int len);
|
||||
unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str);
|
||||
unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
|
||||
const char c);
|
||||
|
||||
void aa_dfa_free_kref(struct kref *kref);
|
||||
|
||||
/**
|
||||
* aa_put_dfa - put a dfa refcount
|
||||
* @dfa: dfa to put refcount (MAYBE NULL)
|
||||
*
|
||||
* Requires: if @dfa != NULL that a valid refcount be held
|
||||
*/
|
||||
static inline void aa_put_dfa(struct aa_dfa *dfa)
|
||||
{
|
||||
if (dfa)
|
||||
kref_put(&dfa->count, aa_dfa_free_kref);
|
||||
}
|
||||
|
||||
#endif /* __AA_MATCH_H */
|
32
security/apparmor/include/path.h
Normal file
32
security/apparmor/include/path.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor basic path manipulation function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_PATH_H
|
||||
#define __AA_PATH_H
|
||||
|
||||
|
||||
enum path_flags {
|
||||
PATH_IS_DIR = 0x1, /* path is a directory */
|
||||
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
|
||||
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
|
||||
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
|
||||
|
||||
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
|
||||
PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
|
||||
};
|
||||
|
||||
int aa_path_name(struct path *path, int flags, char **buffer,
|
||||
const char **name, const char **info);
|
||||
|
||||
#endif /* __AA_PATH_H */
|
408
security/apparmor/include/policy.h
Normal file
408
security/apparmor/include/policy.h
Normal file
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_POLICY_H
|
||||
#define __AA_POLICY_H
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cred.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
#include "apparmor.h"
|
||||
#include "audit.h"
|
||||
#include "capability.h"
|
||||
#include "domain.h"
|
||||
#include "file.h"
|
||||
#include "resource.h"
|
||||
|
||||
extern const char *const aa_profile_mode_names[];
|
||||
#define APPARMOR_MODE_NAMES_MAX_INDEX 4
|
||||
|
||||
#define PROFILE_MODE(_profile, _mode) \
|
||||
((aa_g_profile_mode == (_mode)) || \
|
||||
((_profile)->mode == (_mode)))
|
||||
|
||||
#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN)
|
||||
|
||||
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
|
||||
|
||||
#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
|
||||
|
||||
#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID)
|
||||
|
||||
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
|
||||
|
||||
/*
|
||||
* FIXME: currently need a clean way to replace and remove profiles as a
|
||||
* set. It should be done at the namespace level.
|
||||
* Either, with a set of profiles loaded at the namespace level or via
|
||||
* a mark and remove marked interface.
|
||||
*/
|
||||
enum profile_mode {
|
||||
APPARMOR_ENFORCE, /* enforce access rules */
|
||||
APPARMOR_COMPLAIN, /* allow and log access violations */
|
||||
APPARMOR_KILL, /* kill task on access violation */
|
||||
APPARMOR_UNCONFINED, /* profile set to unconfined */
|
||||
};
|
||||
|
||||
enum profile_flags {
|
||||
PFLAG_HAT = 1, /* profile is a hat */
|
||||
PFLAG_NULL = 4, /* profile is null learning profile */
|
||||
PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
|
||||
PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
|
||||
PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
|
||||
PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
|
||||
PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
|
||||
PFLAG_INVALID = 0x200, /* profile replaced/removed */
|
||||
PFLAG_NS_COUNT = 0x400, /* carries NS ref count */
|
||||
|
||||
/* These flags must correspond with PATH_flags */
|
||||
PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
|
||||
};
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/* struct aa_policy - common part of both namespaces and profiles
|
||||
* @name: name of the object
|
||||
* @hname - The hierarchical name
|
||||
* @list: list policy object is on
|
||||
* @profiles: head of the profiles list contained in the object
|
||||
*/
|
||||
struct aa_policy {
|
||||
char *name;
|
||||
char *hname;
|
||||
struct list_head list;
|
||||
struct list_head profiles;
|
||||
};
|
||||
|
||||
/* struct aa_ns_acct - accounting of profiles in namespace
|
||||
* @max_size: maximum space allowed for all profiles in namespace
|
||||
* @max_count: maximum number of profiles that can be in this namespace
|
||||
* @size: current size of profiles
|
||||
* @count: current count of profiles (includes null profiles)
|
||||
*/
|
||||
struct aa_ns_acct {
|
||||
int max_size;
|
||||
int max_count;
|
||||
int size;
|
||||
int count;
|
||||
};
|
||||
|
||||
/* struct aa_namespace - namespace for a set of profiles
|
||||
* @base: common policy
|
||||
* @parent: parent of namespace
|
||||
* @lock: lock for modifying the object
|
||||
* @acct: accounting for the namespace
|
||||
* @unconfined: special unconfined profile for the namespace
|
||||
* @sub_ns: list of namespaces under the current namespace.
|
||||
* @uniq_null: uniq value used for null learning profiles
|
||||
* @uniq_id: a unique id count for the profiles in the namespace
|
||||
* @dents: dentries for the namespaces file entries in apparmorfs
|
||||
*
|
||||
* An aa_namespace defines the set profiles that are searched to determine
|
||||
* which profile to attach to a task. Profiles can not be shared between
|
||||
* aa_namespaces and profile names within a namespace are guaranteed to be
|
||||
* unique. When profiles in separate namespaces have the same name they
|
||||
* are NOT considered to be equivalent.
|
||||
*
|
||||
* Namespaces are hierarchical and only namespaces and profiles below the
|
||||
* current namespace are visible.
|
||||
*
|
||||
* Namespace names must be unique and can not contain the characters :/\0
|
||||
*
|
||||
* FIXME TODO: add vserver support of namespaces (can it all be done in
|
||||
* userspace?)
|
||||
*/
|
||||
struct aa_namespace {
|
||||
struct aa_policy base;
|
||||
struct aa_namespace *parent;
|
||||
struct mutex lock;
|
||||
struct aa_ns_acct acct;
|
||||
struct aa_profile *unconfined;
|
||||
struct list_head sub_ns;
|
||||
atomic_t uniq_null;
|
||||
long uniq_id;
|
||||
|
||||
struct dentry *dents[AAFS_NS_SIZEOF];
|
||||
};
|
||||
|
||||
/* struct aa_policydb - match engine for a policy
|
||||
* dfa: dfa pattern match
|
||||
* start: set of start states for the different classes of data
|
||||
*/
|
||||
struct aa_policydb {
|
||||
/* Generic policy DFA specific rule types will be subsections of it */
|
||||
struct aa_dfa *dfa;
|
||||
unsigned int start[AA_CLASS_LAST + 1];
|
||||
|
||||
};
|
||||
|
||||
struct aa_replacedby {
|
||||
struct kref count;
|
||||
struct aa_profile __rcu *profile;
|
||||
};
|
||||
|
||||
|
||||
/* struct aa_profile - basic confinement data
|
||||
* @base - base components of the profile (name, refcount, lists, lock ...)
|
||||
* @count: reference count of the obj
|
||||
* @rcu: rcu head used when removing from @list
|
||||
* @parent: parent of profile
|
||||
* @ns: namespace the profile is in
|
||||
* @replacedby: is set to the profile that replaced this profile
|
||||
* @rename: optional profile name that this profile renamed
|
||||
* @attach: human readable attachment string
|
||||
* @xmatch: optional extended matching for unconfined executables names
|
||||
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
|
||||
* @audit: the auditing mode of the profile
|
||||
* @mode: the enforcement mode of the profile
|
||||
* @flags: flags controlling profile behavior
|
||||
* @path_flags: flags controlling path generation behavior
|
||||
* @size: the memory consumed by this profiles rules
|
||||
* @policy: general match rules governing policy
|
||||
* @file: The set of rules governing basic file access and domain transitions
|
||||
* @caps: capabilities for the profile
|
||||
* @rlimits: rlimits for the profile
|
||||
*
|
||||
* @dents: dentries for the profiles file entries in apparmorfs
|
||||
* @dirname: name of the profile dir in apparmorfs
|
||||
*
|
||||
* The AppArmor profile contains the basic confinement data. Each profile
|
||||
* has a name, and exists in a namespace. The @name and @exec_match are
|
||||
* used to determine profile attachment against unconfined tasks. All other
|
||||
* attachments are determined by profile X transition rules.
|
||||
*
|
||||
* The @replacedby struct is write protected by the profile lock.
|
||||
*
|
||||
* Profiles have a hierarchy where hats and children profiles keep
|
||||
* a reference to their parent.
|
||||
*
|
||||
* Profile names can not begin with a : and can not contain the \0
|
||||
* character. If a profile name begins with / it will be considered when
|
||||
* determining profile attachment on "unconfined" tasks.
|
||||
*/
|
||||
struct aa_profile {
|
||||
struct aa_policy base;
|
||||
struct kref count;
|
||||
struct rcu_head rcu;
|
||||
struct aa_profile __rcu *parent;
|
||||
|
||||
struct aa_namespace *ns;
|
||||
struct aa_replacedby *replacedby;
|
||||
const char *rename;
|
||||
|
||||
const char *attach;
|
||||
struct aa_dfa *xmatch;
|
||||
int xmatch_len;
|
||||
enum audit_mode audit;
|
||||
long mode;
|
||||
long flags;
|
||||
u32 path_flags;
|
||||
int size;
|
||||
|
||||
struct aa_policydb policy;
|
||||
struct aa_file_rules file;
|
||||
struct aa_caps caps;
|
||||
struct aa_rlimit rlimits;
|
||||
|
||||
unsigned char *hash;
|
||||
char *dirname;
|
||||
struct dentry *dents[AAFS_PROF_SIZEOF];
|
||||
};
|
||||
|
||||
extern struct aa_namespace *root_ns;
|
||||
extern enum profile_mode aa_g_profile_mode;
|
||||
|
||||
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
|
||||
|
||||
bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view);
|
||||
const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child);
|
||||
int aa_alloc_root_ns(void);
|
||||
void aa_free_root_ns(void);
|
||||
void aa_free_namespace_kref(struct kref *kref);
|
||||
|
||||
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
|
||||
const char *name);
|
||||
|
||||
|
||||
void aa_free_replacedby_kref(struct kref *kref);
|
||||
struct aa_profile *aa_alloc_profile(const char *name);
|
||||
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
|
||||
void aa_free_profile(struct aa_profile *profile);
|
||||
void aa_free_profile_kref(struct kref *kref);
|
||||
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
|
||||
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name);
|
||||
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
|
||||
|
||||
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
|
||||
ssize_t aa_remove_profiles(char *name, size_t size);
|
||||
|
||||
#define PROF_ADD 1
|
||||
#define PROF_REPLACE 0
|
||||
|
||||
#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
|
||||
|
||||
|
||||
static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
|
||||
{
|
||||
return rcu_dereference_protected(p->parent,
|
||||
mutex_is_locked(&p->ns->lock));
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_profile - increment refcount on profile @p
|
||||
* @p: profile (MAYBE NULL)
|
||||
*
|
||||
* Returns: pointer to @p if @p is NULL will return NULL
|
||||
* Requires: @p must be held with valid refcount when called
|
||||
*/
|
||||
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
|
||||
{
|
||||
if (p)
|
||||
kref_get(&(p->count));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_profile_not0 - increment refcount on profile @p found via lookup
|
||||
* @p: profile (MAYBE NULL)
|
||||
*
|
||||
* Returns: pointer to @p if @p is NULL will return NULL
|
||||
* Requires: @p must be held with valid refcount when called
|
||||
*/
|
||||
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
|
||||
{
|
||||
if (p && kref_get_not0(&p->count))
|
||||
return p;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_profile_rcu - increment a refcount profile that can be replaced
|
||||
* @p: pointer to profile that can be replaced (NOT NULL)
|
||||
*
|
||||
* Returns: pointer to a refcounted profile.
|
||||
* else NULL if no profile
|
||||
*/
|
||||
static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
|
||||
{
|
||||
struct aa_profile *c;
|
||||
|
||||
rcu_read_lock();
|
||||
do {
|
||||
c = rcu_dereference(*p);
|
||||
} while (c && !kref_get_not0(&c->count));
|
||||
rcu_read_unlock();
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_newest_profile - find the newest version of @profile
|
||||
* @profile: the profile to check for newer versions of
|
||||
*
|
||||
* Returns: refcounted newest version of @profile taking into account
|
||||
* replacement, renames and removals
|
||||
* return @profile.
|
||||
*/
|
||||
static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
|
||||
{
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
if (PROFILE_INVALID(p))
|
||||
return aa_get_profile_rcu(&p->replacedby->profile);
|
||||
|
||||
return aa_get_profile(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_put_profile - decrement refcount on profile @p
|
||||
* @p: profile (MAYBE NULL)
|
||||
*/
|
||||
static inline void aa_put_profile(struct aa_profile *p)
|
||||
{
|
||||
if (p)
|
||||
kref_put(&p->count, aa_free_profile_kref);
|
||||
}
|
||||
|
||||
static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *p)
|
||||
{
|
||||
if (p)
|
||||
kref_get(&(p->count));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline void aa_put_replacedby(struct aa_replacedby *p)
|
||||
{
|
||||
if (p)
|
||||
kref_put(&p->count, aa_free_replacedby_kref);
|
||||
}
|
||||
|
||||
/* requires profile list write lock held */
|
||||
static inline void __aa_update_replacedby(struct aa_profile *orig,
|
||||
struct aa_profile *new)
|
||||
{
|
||||
struct aa_profile *tmp;
|
||||
tmp = rcu_dereference_protected(orig->replacedby->profile,
|
||||
mutex_is_locked(&orig->ns->lock));
|
||||
rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new));
|
||||
orig->flags |= PFLAG_INVALID;
|
||||
aa_put_profile(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_namespace - increment references count on @ns
|
||||
* @ns: namespace to increment reference count of (MAYBE NULL)
|
||||
*
|
||||
* Returns: pointer to @ns, if @ns is NULL returns NULL
|
||||
* Requires: @ns must be held with valid refcount when called
|
||||
*/
|
||||
static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
|
||||
{
|
||||
if (ns)
|
||||
aa_get_profile(ns->unconfined);
|
||||
|
||||
return ns;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_put_namespace - decrement refcount on @ns
|
||||
* @ns: namespace to put reference of
|
||||
*
|
||||
* Decrement reference count of @ns and if no longer in use free it
|
||||
*/
|
||||
static inline void aa_put_namespace(struct aa_namespace *ns)
|
||||
{
|
||||
if (ns)
|
||||
aa_put_profile(ns->unconfined);
|
||||
}
|
||||
|
||||
static inline int AUDIT_MODE(struct aa_profile *profile)
|
||||
{
|
||||
if (aa_g_audit != AUDIT_NORMAL)
|
||||
return aa_g_audit;
|
||||
|
||||
return profile->audit;
|
||||
}
|
||||
|
||||
bool aa_may_manage_policy(int op);
|
||||
|
||||
#endif /* __AA_POLICY_H */
|
39
security/apparmor/include/policy_unpack.h
Normal file
39
security/apparmor/include/policy_unpack.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor policy loading interface function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __POLICY_INTERFACE_H
|
||||
#define __POLICY_INTERFACE_H
|
||||
|
||||
#include <linux/list.h>
|
||||
|
||||
struct aa_load_ent {
|
||||
struct list_head list;
|
||||
struct aa_profile *new;
|
||||
struct aa_profile *old;
|
||||
struct aa_profile *rename;
|
||||
};
|
||||
|
||||
void aa_load_ent_free(struct aa_load_ent *ent);
|
||||
struct aa_load_ent *aa_load_ent_alloc(void);
|
||||
|
||||
#define PACKED_FLAG_HAT 1
|
||||
|
||||
#define PACKED_MODE_ENFORCE 0
|
||||
#define PACKED_MODE_COMPLAIN 1
|
||||
#define PACKED_MODE_KILL 2
|
||||
#define PACKED_MODE_UNCONFINED 3
|
||||
|
||||
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns);
|
||||
|
||||
#endif /* __POLICY_INTERFACE_H */
|
25
security/apparmor/include/procattr.h
Normal file
25
security/apparmor/include/procattr.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor /proc/<pid>/attr/ interface function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_PROCATTR_H
|
||||
#define __AA_PROCATTR_H
|
||||
|
||||
#define AA_DO_TEST 1
|
||||
#define AA_ONEXEC 1
|
||||
|
||||
int aa_getprocattr(struct aa_profile *profile, char **string);
|
||||
int aa_setprocattr_changehat(char *args, size_t size, int test);
|
||||
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test);
|
||||
|
||||
#endif /* __AA_PROCATTR_H */
|
50
security/apparmor/include/resource.h
Normal file
50
security/apparmor/include/resource.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor resource limits function definitions.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_RESOURCE_H
|
||||
#define __AA_RESOURCE_H
|
||||
|
||||
#include <linux/resource.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include "apparmorfs.h"
|
||||
|
||||
struct aa_profile;
|
||||
|
||||
/* struct aa_rlimit - rlimit settings for the profile
|
||||
* @mask: which hard limits to set
|
||||
* @limits: rlimit values that override task limits
|
||||
*
|
||||
* AppArmor rlimits are used to set confined task rlimits. Only the
|
||||
* limits specified in @mask will be controlled by apparmor.
|
||||
*/
|
||||
struct aa_rlimit {
|
||||
unsigned int mask;
|
||||
struct rlimit limits[RLIM_NLIMITS];
|
||||
};
|
||||
|
||||
extern struct aa_fs_entry aa_fs_entry_rlimit[];
|
||||
|
||||
int aa_map_resource(int resource);
|
||||
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *,
|
||||
unsigned int resource, struct rlimit *new_rlim);
|
||||
|
||||
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
|
||||
|
||||
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
#endif /* __AA_RESOURCE_H */
|
26
security/apparmor/include/sid.h
Normal file
26
security/apparmor/include/sid.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor security identifier (sid) definitions
|
||||
*
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#ifndef __AA_SID_H
|
||||
#define __AA_SID_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/* sid value that will not be allocated */
|
||||
#define AA_SID_INVALID 0
|
||||
#define AA_SID_ALLOC AA_SID_INVALID
|
||||
|
||||
u32 aa_alloc_sid(void);
|
||||
void aa_free_sid(u32 sid);
|
||||
|
||||
#endif /* __AA_SID_H */
|
111
security/apparmor/ipc.c
Normal file
111
security/apparmor/ipc.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor ipc mediation
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/ptrace.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/capability.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/ipc.h"
|
||||
|
||||
/* call back to audit ptrace fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
audit_log_format(ab, " target=");
|
||||
audit_log_untrustedstring(ab, sa->aad->target);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_ptrace - do auditing for ptrace
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @target: profile being traced (NOT NULL)
|
||||
* @error: error condition
|
||||
*
|
||||
* Returns: %0 or error code
|
||||
*/
|
||||
static int aa_audit_ptrace(struct aa_profile *profile,
|
||||
struct aa_profile *target, int error)
|
||||
{
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
sa.type = LSM_AUDIT_DATA_NONE;
|
||||
sa.aad = &aad;
|
||||
aad.op = OP_PTRACE;
|
||||
aad.target = target;
|
||||
aad.error = error;
|
||||
|
||||
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa,
|
||||
audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_may_ptrace - test if tracer task can trace the tracee
|
||||
* @tracer: profile of the task doing the tracing (NOT NULL)
|
||||
* @tracee: task to be traced
|
||||
* @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
||||
*
|
||||
* Returns: %0 else error code if permission denied or error
|
||||
*/
|
||||
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
|
||||
unsigned int mode)
|
||||
{
|
||||
/* TODO: currently only based on capability, not extended ptrace
|
||||
* rules,
|
||||
* Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
||||
*/
|
||||
|
||||
if (unconfined(tracer) || tracer == tracee)
|
||||
return 0;
|
||||
/* log this capability request */
|
||||
return aa_capable(tracer, CAP_SYS_PTRACE, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_ptrace - do ptrace permission check and auditing
|
||||
* @tracer: task doing the tracing (NOT NULL)
|
||||
* @tracee: task being traced (NOT NULL)
|
||||
* @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
||||
*
|
||||
* Returns: %0 else error code if permission denied or error
|
||||
*/
|
||||
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
|
||||
unsigned int mode)
|
||||
{
|
||||
/*
|
||||
* tracer can ptrace tracee when
|
||||
* - tracer is unconfined ||
|
||||
* - tracer is in complain mode
|
||||
* - tracer has rules allowing it to trace tracee currently this is:
|
||||
* - confined by the same profile ||
|
||||
* - tracer profile has CAP_SYS_PTRACE
|
||||
*/
|
||||
|
||||
struct aa_profile *tracer_p = aa_get_task_profile(tracer);
|
||||
int error = 0;
|
||||
|
||||
if (!unconfined(tracer_p)) {
|
||||
struct aa_profile *tracee_p = aa_get_task_profile(tracee);
|
||||
|
||||
error = aa_may_ptrace(tracer_p, tracee_p, mode);
|
||||
error = aa_audit_ptrace(tracer_p, tracee_p, error);
|
||||
|
||||
aa_put_profile(tracee_p);
|
||||
}
|
||||
aa_put_profile(tracer_p);
|
||||
|
||||
return error;
|
||||
}
|
106
security/apparmor/lib.c
Normal file
106
security/apparmor/lib.c
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains basic common functions used in AppArmor
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/apparmor.h"
|
||||
|
||||
|
||||
/**
|
||||
* aa_split_fqname - split a fqname into a profile and namespace name
|
||||
* @fqname: a full qualified name in namespace profile format (NOT NULL)
|
||||
* @ns_name: pointer to portion of the string containing the ns name (NOT NULL)
|
||||
*
|
||||
* Returns: profile name or NULL if one is not specified
|
||||
*
|
||||
* Split a namespace name from a profile name (see policy.c for naming
|
||||
* description). If a portion of the name is missing it returns NULL for
|
||||
* that portion.
|
||||
*
|
||||
* NOTE: may modify the @fqname string. The pointers returned point
|
||||
* into the @fqname string.
|
||||
*/
|
||||
char *aa_split_fqname(char *fqname, char **ns_name)
|
||||
{
|
||||
char *name = strim(fqname);
|
||||
|
||||
*ns_name = NULL;
|
||||
if (name[0] == ':') {
|
||||
char *split = strchr(&name[1], ':');
|
||||
*ns_name = skip_spaces(&name[1]);
|
||||
if (split) {
|
||||
/* overwrite ':' with \0 */
|
||||
*split++ = 0;
|
||||
if (strncmp(split, "//", 2) == 0)
|
||||
split += 2;
|
||||
name = skip_spaces(split);
|
||||
} else
|
||||
/* a ns name without a following profile is allowed */
|
||||
name = NULL;
|
||||
}
|
||||
if (name && *name == 0)
|
||||
name = NULL;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_info_message - log a none profile related status message
|
||||
* @str: message to log
|
||||
*/
|
||||
void aa_info_message(const char *str)
|
||||
{
|
||||
if (audit_enabled) {
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
sa.type = LSM_AUDIT_DATA_NONE;
|
||||
sa.aad = &aad;
|
||||
aad.info = str;
|
||||
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
|
||||
}
|
||||
printk(KERN_INFO "AppArmor: %s\n", str);
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_kvmalloc - do allocation preferring kmalloc but falling back to vmalloc
|
||||
* @size: how many bytes of memory are required
|
||||
* @flags: the type of memory to allocate (see kmalloc).
|
||||
*
|
||||
* Return: allocated buffer or NULL if failed
|
||||
*
|
||||
* It is possible that policy being loaded from the user is larger than
|
||||
* what can be allocated by kmalloc, in those cases fall back to vmalloc.
|
||||
*/
|
||||
void *__aa_kvmalloc(size_t size, gfp_t flags)
|
||||
{
|
||||
void *buffer = NULL;
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
|
||||
/* do not attempt kmalloc if we need more than 16 pages at once */
|
||||
if (size <= (16*PAGE_SIZE))
|
||||
buffer = kmalloc(size, flags | GFP_NOIO | __GFP_NOWARN);
|
||||
if (!buffer) {
|
||||
if (flags & __GFP_ZERO)
|
||||
buffer = vzalloc(size);
|
||||
else
|
||||
buffer = vmalloc(size);
|
||||
}
|
||||
return buffer;
|
||||
}
|
949
security/apparmor/lsm.c
Normal file
949
security/apparmor/lsm.c
Normal file
|
@ -0,0 +1,949 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor LSM hooks.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/security.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mman.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/sysctl.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/apparmorfs.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/capability.h"
|
||||
#include "include/context.h"
|
||||
#include "include/file.h"
|
||||
#include "include/ipc.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/procattr.h"
|
||||
|
||||
/* Flag indicating whether initialization completed */
|
||||
int apparmor_initialized __initdata;
|
||||
|
||||
/*
|
||||
* LSM hook functions
|
||||
*/
|
||||
|
||||
/*
|
||||
* free the associated aa_task_cxt and put its profiles
|
||||
*/
|
||||
static void apparmor_cred_free(struct cred *cred)
|
||||
{
|
||||
aa_free_task_context(cred_cxt(cred));
|
||||
cred_cxt(cred) = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* allocate the apparmor part of blank credentials
|
||||
*/
|
||||
static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
|
||||
{
|
||||
/* freed by apparmor_cred_free */
|
||||
struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
|
||||
if (!cxt)
|
||||
return -ENOMEM;
|
||||
|
||||
cred_cxt(cred) = cxt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* prepare new aa_task_cxt for modification by prepare_cred block
|
||||
*/
|
||||
static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
|
||||
gfp_t gfp)
|
||||
{
|
||||
/* freed by apparmor_cred_free */
|
||||
struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
|
||||
if (!cxt)
|
||||
return -ENOMEM;
|
||||
|
||||
aa_dup_task_context(cxt, cred_cxt(old));
|
||||
cred_cxt(new) = cxt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* transfer the apparmor data to a blank set of creds
|
||||
*/
|
||||
static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
|
||||
{
|
||||
const struct aa_task_cxt *old_cxt = cred_cxt(old);
|
||||
struct aa_task_cxt *new_cxt = cred_cxt(new);
|
||||
|
||||
aa_dup_task_context(new_cxt, old_cxt);
|
||||
}
|
||||
|
||||
static int apparmor_ptrace_access_check(struct task_struct *child,
|
||||
unsigned int mode)
|
||||
{
|
||||
int error = cap_ptrace_access_check(child, mode);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return aa_ptrace(current, child, mode);
|
||||
}
|
||||
|
||||
static int apparmor_ptrace_traceme(struct task_struct *parent)
|
||||
{
|
||||
int error = cap_ptrace_traceme(parent);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
|
||||
}
|
||||
|
||||
/* Derived from security/commoncap.c:cap_capget */
|
||||
static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
|
||||
kernel_cap_t *inheritable, kernel_cap_t *permitted)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
const struct cred *cred;
|
||||
|
||||
rcu_read_lock();
|
||||
cred = __task_cred(target);
|
||||
profile = aa_cred_profile(cred);
|
||||
|
||||
*effective = cred->cap_effective;
|
||||
*inheritable = cred->cap_inheritable;
|
||||
*permitted = cred->cap_permitted;
|
||||
|
||||
if (!unconfined(profile) && !COMPLAIN_MODE(profile)) {
|
||||
*effective = cap_intersect(*effective, profile->caps.allow);
|
||||
*permitted = cap_intersect(*permitted, profile->caps.allow);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
|
||||
int cap, int audit)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
/* cap_capable returns 0 on success, else -EPERM */
|
||||
int error = cap_capable(cred, ns, cap, audit);
|
||||
if (!error) {
|
||||
profile = aa_cred_profile(cred);
|
||||
if (!unconfined(profile))
|
||||
error = aa_capable(profile, cap, audit);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm - basic common permission check wrapper fn for paths
|
||||
* @op: operation being checked
|
||||
* @path: path to check permission of (NOT NULL)
|
||||
* @mask: requested permissions mask
|
||||
* @cond: conditional info for the permission request (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm(int op, struct path *path, u32 mask,
|
||||
struct path_cond *cond)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
profile = __aa_current_profile();
|
||||
if (!unconfined(profile))
|
||||
error = aa_path_perm(op, profile, path, 0, mask, cond);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_dir_dentry - common permission wrapper when path is dir, dentry
|
||||
* @op: operation being checked
|
||||
* @dir: directory of the dentry (NOT NULL)
|
||||
* @dentry: dentry to check (NOT NULL)
|
||||
* @mask: requested permissions mask
|
||||
* @cond: conditional info for the permission request (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_dir_dentry(int op, struct path *dir,
|
||||
struct dentry *dentry, u32 mask,
|
||||
struct path_cond *cond)
|
||||
{
|
||||
struct path path = { dir->mnt, dentry };
|
||||
|
||||
return common_perm(op, &path, mask, cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_mnt_dentry - common permission wrapper when mnt, dentry
|
||||
* @op: operation being checked
|
||||
* @mnt: mount point of dentry (NOT NULL)
|
||||
* @dentry: dentry to check (NOT NULL)
|
||||
* @mask: requested permissions mask
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_mnt_dentry(int op, struct vfsmount *mnt,
|
||||
struct dentry *dentry, u32 mask)
|
||||
{
|
||||
struct path path = { mnt, dentry };
|
||||
struct path_cond cond = { dentry->d_inode->i_uid,
|
||||
dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
return common_perm(op, &path, mask, &cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_rm - common permission wrapper for operations doing rm
|
||||
* @op: operation being checked
|
||||
* @dir: directory that the dentry is in (NOT NULL)
|
||||
* @dentry: dentry being rm'd (NOT NULL)
|
||||
* @mask: requested permission mask
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_rm(int op, struct path *dir,
|
||||
struct dentry *dentry, u32 mask)
|
||||
{
|
||||
struct inode *inode = dentry->d_inode;
|
||||
struct path_cond cond = { };
|
||||
|
||||
if (!inode || !dir->mnt || !mediated_filesystem(inode))
|
||||
return 0;
|
||||
|
||||
cond.uid = inode->i_uid;
|
||||
cond.mode = inode->i_mode;
|
||||
|
||||
return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* common_perm_create - common permission wrapper for operations doing create
|
||||
* @op: operation being checked
|
||||
* @dir: directory that dentry will be created in (NOT NULL)
|
||||
* @dentry: dentry to create (NOT NULL)
|
||||
* @mask: request permission mask
|
||||
* @mode: created file mode
|
||||
*
|
||||
* Returns: %0 else error code if error or permission denied
|
||||
*/
|
||||
static int common_perm_create(int op, struct path *dir, struct dentry *dentry,
|
||||
u32 mask, umode_t mode)
|
||||
{
|
||||
struct path_cond cond = { current_fsuid(), mode };
|
||||
|
||||
if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
|
||||
}
|
||||
|
||||
static int apparmor_path_unlink(struct path *dir, struct dentry *dentry)
|
||||
{
|
||||
return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE);
|
||||
}
|
||||
|
||||
static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry,
|
||||
umode_t mode)
|
||||
{
|
||||
return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE,
|
||||
S_IFDIR);
|
||||
}
|
||||
|
||||
static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry)
|
||||
{
|
||||
return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE);
|
||||
}
|
||||
|
||||
static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
|
||||
umode_t mode, unsigned int dev)
|
||||
{
|
||||
return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode);
|
||||
}
|
||||
|
||||
static int apparmor_path_truncate(struct path *path)
|
||||
{
|
||||
struct path_cond cond = { path->dentry->d_inode->i_uid,
|
||||
path->dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
if (!path->mnt || !mediated_filesystem(path->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE,
|
||||
&cond);
|
||||
}
|
||||
|
||||
static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
|
||||
const char *old_name)
|
||||
{
|
||||
return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE,
|
||||
S_IFLNK);
|
||||
}
|
||||
|
||||
static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
|
||||
struct dentry *new_dentry)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
if (!mediated_filesystem(old_dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
profile = aa_current_profile();
|
||||
if (!unconfined(profile))
|
||||
error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
|
||||
struct path *new_dir, struct dentry *new_dentry)
|
||||
{
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
if (!mediated_filesystem(old_dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
profile = aa_current_profile();
|
||||
if (!unconfined(profile)) {
|
||||
struct path old_path = { old_dir->mnt, old_dentry };
|
||||
struct path new_path = { new_dir->mnt, new_dentry };
|
||||
struct path_cond cond = { old_dentry->d_inode->i_uid,
|
||||
old_dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0,
|
||||
MAY_READ | AA_MAY_META_READ | MAY_WRITE |
|
||||
AA_MAY_META_WRITE | AA_MAY_DELETE,
|
||||
&cond);
|
||||
if (!error)
|
||||
error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
|
||||
0, MAY_WRITE | AA_MAY_META_WRITE |
|
||||
AA_MAY_CREATE, &cond);
|
||||
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_path_chmod(struct path *path, umode_t mode)
|
||||
{
|
||||
if (!mediated_filesystem(path->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD);
|
||||
}
|
||||
|
||||
static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid)
|
||||
{
|
||||
struct path_cond cond = { path->dentry->d_inode->i_uid,
|
||||
path->dentry->d_inode->i_mode
|
||||
};
|
||||
|
||||
if (!mediated_filesystem(path->dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond);
|
||||
}
|
||||
|
||||
static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)
|
||||
{
|
||||
if (!mediated_filesystem(dentry->d_inode))
|
||||
return 0;
|
||||
|
||||
return common_perm_mnt_dentry(OP_GETATTR, mnt, dentry,
|
||||
AA_MAY_META_READ);
|
||||
}
|
||||
|
||||
static int apparmor_file_open(struct file *file, const struct cred *cred)
|
||||
{
|
||||
struct aa_file_cxt *fcxt = file->f_security;
|
||||
struct aa_profile *profile;
|
||||
int error = 0;
|
||||
|
||||
if (!mediated_filesystem(file_inode(file)))
|
||||
return 0;
|
||||
|
||||
/* If in exec, permission is handled by bprm hooks.
|
||||
* Cache permissions granted by the previous exec check, with
|
||||
* implicit read and executable mmap which are required to
|
||||
* actually execute the image.
|
||||
*/
|
||||
if (current->in_execve) {
|
||||
fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP;
|
||||
return 0;
|
||||
}
|
||||
|
||||
profile = aa_cred_profile(cred);
|
||||
if (!unconfined(profile)) {
|
||||
struct inode *inode = file_inode(file);
|
||||
struct path_cond cond = { inode->i_uid, inode->i_mode };
|
||||
|
||||
error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0,
|
||||
aa_map_file_to_perms(file), &cond);
|
||||
/* todo cache full allowed permissions set and state */
|
||||
fcxt->allow = aa_map_file_to_perms(file);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_file_alloc_security(struct file *file)
|
||||
{
|
||||
/* freed by apparmor_file_free_security */
|
||||
file->f_security = aa_alloc_file_context(GFP_KERNEL);
|
||||
if (!file->f_security)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static void apparmor_file_free_security(struct file *file)
|
||||
{
|
||||
struct aa_file_cxt *cxt = file->f_security;
|
||||
|
||||
aa_free_file_context(cxt);
|
||||
}
|
||||
|
||||
static int common_file_perm(int op, struct file *file, u32 mask)
|
||||
{
|
||||
struct aa_file_cxt *fcxt = file->f_security;
|
||||
struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred);
|
||||
int error = 0;
|
||||
|
||||
BUG_ON(!fprofile);
|
||||
|
||||
if (!file->f_path.mnt ||
|
||||
!mediated_filesystem(file_inode(file)))
|
||||
return 0;
|
||||
|
||||
profile = __aa_current_profile();
|
||||
|
||||
/* revalidate access, if task is unconfined, or the cached cred
|
||||
* doesn't match or if the request is for more permissions than
|
||||
* was granted.
|
||||
*
|
||||
* Note: the test for !unconfined(fprofile) is to handle file
|
||||
* delegation from unconfined tasks
|
||||
*/
|
||||
if (!unconfined(profile) && !unconfined(fprofile) &&
|
||||
((fprofile != profile) || (mask & ~fcxt->allow)))
|
||||
error = aa_file_perm(op, profile, file, mask);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_file_permission(struct file *file, int mask)
|
||||
{
|
||||
return common_file_perm(OP_FPERM, file, mask);
|
||||
}
|
||||
|
||||
static int apparmor_file_lock(struct file *file, unsigned int cmd)
|
||||
{
|
||||
u32 mask = AA_MAY_LOCK;
|
||||
|
||||
if (cmd == F_WRLCK)
|
||||
mask |= MAY_WRITE;
|
||||
|
||||
return common_file_perm(OP_FLOCK, file, mask);
|
||||
}
|
||||
|
||||
static int common_mmap(int op, struct file *file, unsigned long prot,
|
||||
unsigned long flags)
|
||||
{
|
||||
int mask = 0;
|
||||
|
||||
if (!file || !file->f_security)
|
||||
return 0;
|
||||
|
||||
if (prot & PROT_READ)
|
||||
mask |= MAY_READ;
|
||||
/*
|
||||
* Private mappings don't require write perms since they don't
|
||||
* write back to the files
|
||||
*/
|
||||
if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE))
|
||||
mask |= MAY_WRITE;
|
||||
if (prot & PROT_EXEC)
|
||||
mask |= AA_EXEC_MMAP;
|
||||
|
||||
return common_file_perm(op, file, mask);
|
||||
}
|
||||
|
||||
static int apparmor_mmap_file(struct file *file, unsigned long reqprot,
|
||||
unsigned long prot, unsigned long flags)
|
||||
{
|
||||
return common_mmap(OP_FMMAP, file, prot, flags);
|
||||
}
|
||||
|
||||
static int apparmor_file_mprotect(struct vm_area_struct *vma,
|
||||
unsigned long reqprot, unsigned long prot)
|
||||
{
|
||||
return common_mmap(OP_FMPROT, vma->vm_file, prot,
|
||||
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
|
||||
}
|
||||
|
||||
static int apparmor_getprocattr(struct task_struct *task, char *name,
|
||||
char **value)
|
||||
{
|
||||
int error = -ENOENT;
|
||||
/* released below */
|
||||
const struct cred *cred = get_task_cred(task);
|
||||
struct aa_task_cxt *cxt = cred_cxt(cred);
|
||||
struct aa_profile *profile = NULL;
|
||||
|
||||
if (strcmp(name, "current") == 0)
|
||||
profile = aa_get_newest_profile(cxt->profile);
|
||||
else if (strcmp(name, "prev") == 0 && cxt->previous)
|
||||
profile = aa_get_newest_profile(cxt->previous);
|
||||
else if (strcmp(name, "exec") == 0 && cxt->onexec)
|
||||
profile = aa_get_newest_profile(cxt->onexec);
|
||||
else
|
||||
error = -EINVAL;
|
||||
|
||||
if (profile)
|
||||
error = aa_getprocattr(profile, value);
|
||||
|
||||
aa_put_profile(profile);
|
||||
put_cred(cred);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_setprocattr(struct task_struct *task, char *name,
|
||||
void *value, size_t size)
|
||||
{
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
char *command, *args = value;
|
||||
size_t arg_size;
|
||||
int error;
|
||||
|
||||
if (size == 0)
|
||||
return -EINVAL;
|
||||
/* args points to a PAGE_SIZE buffer, AppArmor requires that
|
||||
* the buffer must be null terminated or have size <= PAGE_SIZE -1
|
||||
* so that AppArmor can null terminate them
|
||||
*/
|
||||
if (args[size - 1] != '\0') {
|
||||
if (size == PAGE_SIZE)
|
||||
return -EINVAL;
|
||||
args[size] = '\0';
|
||||
}
|
||||
|
||||
/* task can only write its own attributes */
|
||||
if (current != task)
|
||||
return -EACCES;
|
||||
|
||||
args = value;
|
||||
args = strim(args);
|
||||
command = strsep(&args, " ");
|
||||
if (!args)
|
||||
return -EINVAL;
|
||||
args = skip_spaces(args);
|
||||
if (!*args)
|
||||
return -EINVAL;
|
||||
|
||||
arg_size = size - (args - (char *) value);
|
||||
if (strcmp(name, "current") == 0) {
|
||||
if (strcmp(command, "changehat") == 0) {
|
||||
error = aa_setprocattr_changehat(args, arg_size,
|
||||
!AA_DO_TEST);
|
||||
} else if (strcmp(command, "permhat") == 0) {
|
||||
error = aa_setprocattr_changehat(args, arg_size,
|
||||
AA_DO_TEST);
|
||||
} else if (strcmp(command, "changeprofile") == 0) {
|
||||
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
|
||||
!AA_DO_TEST);
|
||||
} else if (strcmp(command, "permprofile") == 0) {
|
||||
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
|
||||
AA_DO_TEST);
|
||||
} else
|
||||
goto fail;
|
||||
} else if (strcmp(name, "exec") == 0) {
|
||||
if (strcmp(command, "exec") == 0)
|
||||
error = aa_setprocattr_changeprofile(args, AA_ONEXEC,
|
||||
!AA_DO_TEST);
|
||||
else
|
||||
goto fail;
|
||||
} else
|
||||
/* only support the "current" and "exec" process attributes */
|
||||
return -EINVAL;
|
||||
|
||||
if (!error)
|
||||
error = size;
|
||||
return error;
|
||||
|
||||
fail:
|
||||
sa.type = LSM_AUDIT_DATA_NONE;
|
||||
sa.aad = &aad;
|
||||
aad.profile = aa_current_profile();
|
||||
aad.op = OP_SETPROCATTR;
|
||||
aad.info = name;
|
||||
aad.error = -EINVAL;
|
||||
aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int apparmor_task_setrlimit(struct task_struct *task,
|
||||
unsigned int resource, struct rlimit *new_rlim)
|
||||
{
|
||||
struct aa_profile *profile = __aa_current_profile();
|
||||
int error = 0;
|
||||
|
||||
if (!unconfined(profile))
|
||||
error = aa_task_setrlimit(profile, task, resource, new_rlim);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static struct security_operations apparmor_ops = {
|
||||
.name = "apparmor",
|
||||
|
||||
.ptrace_access_check = apparmor_ptrace_access_check,
|
||||
.ptrace_traceme = apparmor_ptrace_traceme,
|
||||
.capget = apparmor_capget,
|
||||
.capable = apparmor_capable,
|
||||
|
||||
.path_link = apparmor_path_link,
|
||||
.path_unlink = apparmor_path_unlink,
|
||||
.path_symlink = apparmor_path_symlink,
|
||||
.path_mkdir = apparmor_path_mkdir,
|
||||
.path_rmdir = apparmor_path_rmdir,
|
||||
.path_mknod = apparmor_path_mknod,
|
||||
.path_rename = apparmor_path_rename,
|
||||
.path_chmod = apparmor_path_chmod,
|
||||
.path_chown = apparmor_path_chown,
|
||||
.path_truncate = apparmor_path_truncate,
|
||||
.inode_getattr = apparmor_inode_getattr,
|
||||
|
||||
.file_open = apparmor_file_open,
|
||||
.file_permission = apparmor_file_permission,
|
||||
.file_alloc_security = apparmor_file_alloc_security,
|
||||
.file_free_security = apparmor_file_free_security,
|
||||
.mmap_file = apparmor_mmap_file,
|
||||
.mmap_addr = cap_mmap_addr,
|
||||
.file_mprotect = apparmor_file_mprotect,
|
||||
.file_lock = apparmor_file_lock,
|
||||
|
||||
.getprocattr = apparmor_getprocattr,
|
||||
.setprocattr = apparmor_setprocattr,
|
||||
|
||||
.cred_alloc_blank = apparmor_cred_alloc_blank,
|
||||
.cred_free = apparmor_cred_free,
|
||||
.cred_prepare = apparmor_cred_prepare,
|
||||
.cred_transfer = apparmor_cred_transfer,
|
||||
|
||||
.bprm_set_creds = apparmor_bprm_set_creds,
|
||||
.bprm_committing_creds = apparmor_bprm_committing_creds,
|
||||
.bprm_committed_creds = apparmor_bprm_committed_creds,
|
||||
.bprm_secureexec = apparmor_bprm_secureexec,
|
||||
|
||||
.task_setrlimit = apparmor_task_setrlimit,
|
||||
};
|
||||
|
||||
/*
|
||||
* AppArmor sysfs module parameters
|
||||
*/
|
||||
|
||||
static int param_set_aabool(const char *val, const struct kernel_param *kp);
|
||||
static int param_get_aabool(char *buffer, const struct kernel_param *kp);
|
||||
#define param_check_aabool param_check_bool
|
||||
static struct kernel_param_ops param_ops_aabool = {
|
||||
.flags = KERNEL_PARAM_OPS_FL_NOARG,
|
||||
.set = param_set_aabool,
|
||||
.get = param_get_aabool
|
||||
};
|
||||
|
||||
static int param_set_aauint(const char *val, const struct kernel_param *kp);
|
||||
static int param_get_aauint(char *buffer, const struct kernel_param *kp);
|
||||
#define param_check_aauint param_check_uint
|
||||
static struct kernel_param_ops param_ops_aauint = {
|
||||
.set = param_set_aauint,
|
||||
.get = param_get_aauint
|
||||
};
|
||||
|
||||
static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp);
|
||||
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp);
|
||||
#define param_check_aalockpolicy param_check_bool
|
||||
static struct kernel_param_ops param_ops_aalockpolicy = {
|
||||
.flags = KERNEL_PARAM_OPS_FL_NOARG,
|
||||
.set = param_set_aalockpolicy,
|
||||
.get = param_get_aalockpolicy
|
||||
};
|
||||
|
||||
static int param_set_audit(const char *val, struct kernel_param *kp);
|
||||
static int param_get_audit(char *buffer, struct kernel_param *kp);
|
||||
|
||||
static int param_set_mode(const char *val, struct kernel_param *kp);
|
||||
static int param_get_mode(char *buffer, struct kernel_param *kp);
|
||||
|
||||
/* Flag values, also controllable via /sys/module/apparmor/parameters
|
||||
* We define special types as we want to do additional mediation.
|
||||
*/
|
||||
|
||||
/* AppArmor global enforcement switch - complain, enforce, kill */
|
||||
enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
|
||||
module_param_call(mode, param_set_mode, param_get_mode,
|
||||
&aa_g_profile_mode, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Debug mode */
|
||||
bool aa_g_debug;
|
||||
module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Audit mode */
|
||||
enum audit_mode aa_g_audit;
|
||||
module_param_call(audit, param_set_audit, param_get_audit,
|
||||
&aa_g_audit, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Determines if audit header is included in audited messages. This
|
||||
* provides more context if the audit daemon is not running
|
||||
*/
|
||||
bool aa_g_audit_header = 1;
|
||||
module_param_named(audit_header, aa_g_audit_header, aabool,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* lock out loading/removal of policy
|
||||
* TODO: add in at boot loading of policy, which is the only way to
|
||||
* load policy, if lock_policy is set
|
||||
*/
|
||||
bool aa_g_lock_policy;
|
||||
module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Syscall logging mode */
|
||||
bool aa_g_logsyscall;
|
||||
module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Maximum pathname length before accesses will start getting rejected */
|
||||
unsigned int aa_g_path_max = 2 * PATH_MAX;
|
||||
module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Determines how paranoid loading of policy is and how much verification
|
||||
* on the loaded policy is done.
|
||||
*/
|
||||
bool aa_g_paranoid_load = 1;
|
||||
module_param_named(paranoid_load, aa_g_paranoid_load, aabool,
|
||||
S_IRUSR | S_IWUSR);
|
||||
|
||||
/* Boot time disable flag */
|
||||
static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
|
||||
module_param_named(enabled, apparmor_enabled, bool, S_IRUGO);
|
||||
|
||||
static int __init apparmor_enabled_setup(char *str)
|
||||
{
|
||||
unsigned long enabled;
|
||||
int error = kstrtoul(str, 0, &enabled);
|
||||
if (!error)
|
||||
apparmor_enabled = enabled ? 1 : 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__setup("apparmor=", apparmor_enabled_setup);
|
||||
|
||||
/* set global flag turning off the ability to load policy */
|
||||
static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
if (aa_g_lock_policy)
|
||||
return -EACCES;
|
||||
return param_set_bool(val, kp);
|
||||
}
|
||||
|
||||
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_get_bool(buffer, kp);
|
||||
}
|
||||
|
||||
static int param_set_aabool(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_set_bool(val, kp);
|
||||
}
|
||||
|
||||
static int param_get_aabool(char *buffer, const struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_get_bool(buffer, kp);
|
||||
}
|
||||
|
||||
static int param_set_aauint(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_set_uint(val, kp);
|
||||
}
|
||||
|
||||
static int param_get_aauint(char *buffer, const struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
return param_get_uint(buffer, kp);
|
||||
}
|
||||
|
||||
static int param_get_audit(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
|
||||
}
|
||||
|
||||
static int param_set_audit(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
int i;
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < AUDIT_MAX_INDEX; i++) {
|
||||
if (strcmp(val, audit_mode_names[i]) == 0) {
|
||||
aa_g_audit = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int param_get_mode(char *buffer, struct kernel_param *kp)
|
||||
{
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]);
|
||||
}
|
||||
|
||||
static int param_set_mode(const char *val, struct kernel_param *kp)
|
||||
{
|
||||
int i;
|
||||
if (!capable(CAP_MAC_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!apparmor_enabled)
|
||||
return -EINVAL;
|
||||
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < APPARMOR_MODE_NAMES_MAX_INDEX; i++) {
|
||||
if (strcmp(val, aa_profile_mode_names[i]) == 0) {
|
||||
aa_g_profile_mode = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* AppArmor init functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* set_init_cxt - set a task context and profile on the first task.
|
||||
*
|
||||
* TODO: allow setting an alternate profile than unconfined
|
||||
*/
|
||||
static int __init set_init_cxt(void)
|
||||
{
|
||||
struct cred *cred = (struct cred *)current->real_cred;
|
||||
struct aa_task_cxt *cxt;
|
||||
|
||||
cxt = aa_alloc_task_context(GFP_KERNEL);
|
||||
if (!cxt)
|
||||
return -ENOMEM;
|
||||
|
||||
cxt->profile = aa_get_profile(root_ns->unconfined);
|
||||
cred_cxt(cred) = cxt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init apparmor_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) {
|
||||
aa_info_message("AppArmor disabled by boot time parameter");
|
||||
apparmor_enabled = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
error = aa_alloc_root_ns();
|
||||
if (error) {
|
||||
AA_ERROR("Unable to allocate default profile namespace\n");
|
||||
goto alloc_out;
|
||||
}
|
||||
|
||||
error = set_init_cxt();
|
||||
if (error) {
|
||||
AA_ERROR("Failed to set context on init task\n");
|
||||
goto register_security_out;
|
||||
}
|
||||
|
||||
error = register_security(&apparmor_ops);
|
||||
if (error) {
|
||||
struct cred *cred = (struct cred *)current->real_cred;
|
||||
aa_free_task_context(cred_cxt(cred));
|
||||
cred_cxt(cred) = NULL;
|
||||
AA_ERROR("Unable to register AppArmor\n");
|
||||
goto register_security_out;
|
||||
}
|
||||
|
||||
/* Report that AppArmor successfully initialized */
|
||||
apparmor_initialized = 1;
|
||||
if (aa_g_profile_mode == APPARMOR_COMPLAIN)
|
||||
aa_info_message("AppArmor initialized: complain mode enabled");
|
||||
else if (aa_g_profile_mode == APPARMOR_KILL)
|
||||
aa_info_message("AppArmor initialized: kill mode enabled");
|
||||
else
|
||||
aa_info_message("AppArmor initialized");
|
||||
|
||||
return error;
|
||||
|
||||
register_security_out:
|
||||
aa_free_root_ns();
|
||||
|
||||
alloc_out:
|
||||
aa_destroy_aafs();
|
||||
|
||||
apparmor_enabled = 0;
|
||||
return error;
|
||||
}
|
||||
|
||||
security_initcall(apparmor_init);
|
428
security/apparmor/match.c
Normal file
428
security/apparmor/match.c
Normal file
|
@ -0,0 +1,428 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor dfa based regular expression matching engine
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2012 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kref.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/match.h"
|
||||
|
||||
#define base_idx(X) ((X) & 0xffffff)
|
||||
|
||||
/**
|
||||
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
|
||||
* @blob: data to unpack (NOT NULL)
|
||||
* @bsize: size of blob
|
||||
*
|
||||
* Returns: pointer to table else NULL on failure
|
||||
*
|
||||
* NOTE: must be freed by kvfree (not kfree)
|
||||
*/
|
||||
static struct table_header *unpack_table(char *blob, size_t bsize)
|
||||
{
|
||||
struct table_header *table = NULL;
|
||||
struct table_header th;
|
||||
size_t tsize;
|
||||
|
||||
if (bsize < sizeof(struct table_header))
|
||||
goto out;
|
||||
|
||||
/* loaded td_id's start at 1, subtract 1 now to avoid doing
|
||||
* it every time we use td_id as an index
|
||||
*/
|
||||
th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1;
|
||||
th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
|
||||
th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
|
||||
blob += sizeof(struct table_header);
|
||||
|
||||
if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
|
||||
th.td_flags == YYTD_DATA8))
|
||||
goto out;
|
||||
|
||||
tsize = table_size(th.td_lolen, th.td_flags);
|
||||
if (bsize < tsize)
|
||||
goto out;
|
||||
|
||||
table = kvzalloc(tsize);
|
||||
if (table) {
|
||||
*table = th;
|
||||
if (th.td_flags == YYTD_DATA8)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u8, byte_to_byte);
|
||||
else if (th.td_flags == YYTD_DATA16)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u16, be16_to_cpu);
|
||||
else if (th.td_flags == YYTD_DATA32)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u32, be32_to_cpu);
|
||||
else
|
||||
goto fail;
|
||||
}
|
||||
|
||||
out:
|
||||
/* if table was vmalloced make sure the page tables are synced
|
||||
* before it is used, as it goes live to all cpus.
|
||||
*/
|
||||
if (is_vmalloc_addr(table))
|
||||
vm_unmap_aliases();
|
||||
return table;
|
||||
fail:
|
||||
kvfree(table);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_dfa - verify that transitions and states in the tables are in bounds.
|
||||
* @dfa: dfa to test (NOT NULL)
|
||||
* @flags: flags controlling what type of accept table are acceptable
|
||||
*
|
||||
* Assumes dfa has gone through the first pass verification done by unpacking
|
||||
* NOTE: this does not valid accept table values
|
||||
*
|
||||
* Returns: %0 else error code on failure to verify
|
||||
*/
|
||||
static int verify_dfa(struct aa_dfa *dfa, int flags)
|
||||
{
|
||||
size_t i, state_count, trans_count;
|
||||
int error = -EPROTO;
|
||||
|
||||
/* check that required tables exist */
|
||||
if (!(dfa->tables[YYTD_ID_DEF] &&
|
||||
dfa->tables[YYTD_ID_BASE] &&
|
||||
dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK]))
|
||||
goto out;
|
||||
|
||||
/* accept.size == default.size == base.size */
|
||||
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
|
||||
if (ACCEPT1_FLAGS(flags)) {
|
||||
if (!dfa->tables[YYTD_ID_ACCEPT])
|
||||
goto out;
|
||||
if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen)
|
||||
goto out;
|
||||
}
|
||||
if (ACCEPT2_FLAGS(flags)) {
|
||||
if (!dfa->tables[YYTD_ID_ACCEPT2])
|
||||
goto out;
|
||||
if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen)
|
||||
goto out;
|
||||
}
|
||||
if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen)
|
||||
goto out;
|
||||
|
||||
/* next.size == chk.size */
|
||||
trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen;
|
||||
if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen)
|
||||
goto out;
|
||||
|
||||
/* if equivalence classes then its table size must be 256 */
|
||||
if (dfa->tables[YYTD_ID_EC] &&
|
||||
dfa->tables[YYTD_ID_EC]->td_lolen != 256)
|
||||
goto out;
|
||||
|
||||
if (flags & DFA_FLAG_VERIFY_STATES) {
|
||||
for (i = 0; i < state_count; i++) {
|
||||
if (DEFAULT_TABLE(dfa)[i] >= state_count)
|
||||
goto out;
|
||||
if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) {
|
||||
printk(KERN_ERR "AppArmor DFA next/check upper "
|
||||
"bounds error\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < trans_count; i++) {
|
||||
if (NEXT_TABLE(dfa)[i] >= state_count)
|
||||
goto out;
|
||||
if (CHECK_TABLE(dfa)[i] >= state_count)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
error = 0;
|
||||
out:
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* dfa_free - free a dfa allocated by aa_dfa_unpack
|
||||
* @dfa: the dfa to free (MAYBE NULL)
|
||||
*
|
||||
* Requires: reference count to dfa == 0
|
||||
*/
|
||||
static void dfa_free(struct aa_dfa *dfa)
|
||||
{
|
||||
if (dfa) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
|
||||
kvfree(dfa->tables[i]);
|
||||
dfa->tables[i] = NULL;
|
||||
}
|
||||
kfree(dfa);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa)
|
||||
* @kr: kref callback for freeing of a dfa (NOT NULL)
|
||||
*/
|
||||
void aa_dfa_free_kref(struct kref *kref)
|
||||
{
|
||||
struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count);
|
||||
dfa_free(dfa);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_unpack - unpack the binary tables of a serialized dfa
|
||||
* @blob: aligned serialized stream of data to unpack (NOT NULL)
|
||||
* @size: size of data to unpack
|
||||
* @flags: flags controlling what type of accept tables are acceptable
|
||||
*
|
||||
* Unpack a dfa that has been serialized. To find information on the dfa
|
||||
* format look in Documentation/security/apparmor.txt
|
||||
* Assumes the dfa @blob stream has been aligned on a 8 byte boundary
|
||||
*
|
||||
* Returns: an unpacked dfa ready for matching or ERR_PTR on failure
|
||||
*/
|
||||
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
|
||||
{
|
||||
int hsize;
|
||||
int error = -ENOMEM;
|
||||
char *data = blob;
|
||||
struct table_header *table = NULL;
|
||||
struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
|
||||
if (!dfa)
|
||||
goto fail;
|
||||
|
||||
kref_init(&dfa->count);
|
||||
|
||||
error = -EPROTO;
|
||||
|
||||
/* get dfa table set header */
|
||||
if (size < sizeof(struct table_set_header))
|
||||
goto fail;
|
||||
|
||||
if (ntohl(*(u32 *) data) != YYTH_MAGIC)
|
||||
goto fail;
|
||||
|
||||
hsize = ntohl(*(u32 *) (data + 4));
|
||||
if (size < hsize)
|
||||
goto fail;
|
||||
|
||||
dfa->flags = ntohs(*(u16 *) (data + 12));
|
||||
data += hsize;
|
||||
size -= hsize;
|
||||
|
||||
while (size > 0) {
|
||||
table = unpack_table(data, size);
|
||||
if (!table)
|
||||
goto fail;
|
||||
|
||||
switch (table->td_id) {
|
||||
case YYTD_ID_ACCEPT:
|
||||
if (!(table->td_flags & ACCEPT1_FLAGS(flags)))
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_ACCEPT2:
|
||||
if (!(table->td_flags & ACCEPT2_FLAGS(flags)))
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_BASE:
|
||||
if (table->td_flags != YYTD_DATA32)
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_DEF:
|
||||
case YYTD_ID_NXT:
|
||||
case YYTD_ID_CHK:
|
||||
if (table->td_flags != YYTD_DATA16)
|
||||
goto fail;
|
||||
break;
|
||||
case YYTD_ID_EC:
|
||||
if (table->td_flags != YYTD_DATA8)
|
||||
goto fail;
|
||||
break;
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
/* check for duplicate table entry */
|
||||
if (dfa->tables[table->td_id])
|
||||
goto fail;
|
||||
dfa->tables[table->td_id] = table;
|
||||
data += table_size(table->td_lolen, table->td_flags);
|
||||
size -= table_size(table->td_lolen, table->td_flags);
|
||||
table = NULL;
|
||||
}
|
||||
|
||||
error = verify_dfa(dfa, flags);
|
||||
if (error)
|
||||
goto fail;
|
||||
|
||||
return dfa;
|
||||
|
||||
fail:
|
||||
kvfree(table);
|
||||
dfa_free(dfa);
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_match_len - traverse @dfa to find state @str stops at
|
||||
* @dfa: the dfa to match @str against (NOT NULL)
|
||||
* @start: the state of the dfa to start matching in
|
||||
* @str: the string of bytes to match against the dfa (NOT NULL)
|
||||
* @len: length of the string of bytes to match
|
||||
*
|
||||
* aa_dfa_match_len will match @str against the dfa and return the state it
|
||||
* finished matching in. The final state can be used to look up the accepting
|
||||
* label, or as the start state of a continuing match.
|
||||
*
|
||||
* This function will happily match again the 0 byte and only finishes
|
||||
* when @len input is consumed.
|
||||
*
|
||||
* Returns: final state reached after input is consumed
|
||||
*/
|
||||
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str, int len)
|
||||
{
|
||||
u16 *def = DEFAULT_TABLE(dfa);
|
||||
u32 *base = BASE_TABLE(dfa);
|
||||
u16 *next = NEXT_TABLE(dfa);
|
||||
u16 *check = CHECK_TABLE(dfa);
|
||||
unsigned int state = start, pos;
|
||||
|
||||
if (state == 0)
|
||||
return 0;
|
||||
|
||||
/* current state is <state>, matching character *str */
|
||||
if (dfa->tables[YYTD_ID_EC]) {
|
||||
/* Equivalence class table defined */
|
||||
u8 *equiv = EQUIV_TABLE(dfa);
|
||||
/* default is direct to next state */
|
||||
for (; len; len--) {
|
||||
pos = base_idx(base[state]) + equiv[(u8) *str++];
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
} else {
|
||||
/* default is direct to next state */
|
||||
for (; len; len--) {
|
||||
pos = base_idx(base[state]) + (u8) *str++;
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_match - traverse @dfa to find state @str stops at
|
||||
* @dfa: the dfa to match @str against (NOT NULL)
|
||||
* @start: the state of the dfa to start matching in
|
||||
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
|
||||
*
|
||||
* aa_dfa_match will match @str against the dfa and return the state it
|
||||
* finished matching in. The final state can be used to look up the accepting
|
||||
* label, or as the start state of a continuing match.
|
||||
*
|
||||
* Returns: final state reached after input is consumed
|
||||
*/
|
||||
unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
|
||||
const char *str)
|
||||
{
|
||||
u16 *def = DEFAULT_TABLE(dfa);
|
||||
u32 *base = BASE_TABLE(dfa);
|
||||
u16 *next = NEXT_TABLE(dfa);
|
||||
u16 *check = CHECK_TABLE(dfa);
|
||||
unsigned int state = start, pos;
|
||||
|
||||
if (state == 0)
|
||||
return 0;
|
||||
|
||||
/* current state is <state>, matching character *str */
|
||||
if (dfa->tables[YYTD_ID_EC]) {
|
||||
/* Equivalence class table defined */
|
||||
u8 *equiv = EQUIV_TABLE(dfa);
|
||||
/* default is direct to next state */
|
||||
while (*str) {
|
||||
pos = base_idx(base[state]) + equiv[(u8) *str++];
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
} else {
|
||||
/* default is direct to next state */
|
||||
while (*str) {
|
||||
pos = base_idx(base[state]) + (u8) *str++;
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_dfa_next - step one character to the next state in the dfa
|
||||
* @dfa: the dfa to tranverse (NOT NULL)
|
||||
* @state: the state to start in
|
||||
* @c: the input character to transition on
|
||||
*
|
||||
* aa_dfa_match will step through the dfa by one input character @c
|
||||
*
|
||||
* Returns: state reach after input @c
|
||||
*/
|
||||
unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
|
||||
const char c)
|
||||
{
|
||||
u16 *def = DEFAULT_TABLE(dfa);
|
||||
u32 *base = BASE_TABLE(dfa);
|
||||
u16 *next = NEXT_TABLE(dfa);
|
||||
u16 *check = CHECK_TABLE(dfa);
|
||||
unsigned int pos;
|
||||
|
||||
/* current state is <state>, matching character *str */
|
||||
if (dfa->tables[YYTD_ID_EC]) {
|
||||
/* Equivalence class table defined */
|
||||
u8 *equiv = EQUIV_TABLE(dfa);
|
||||
/* default is direct to next state */
|
||||
|
||||
pos = base_idx(base[state]) + equiv[(u8) c];
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
} else {
|
||||
/* default is direct to next state */
|
||||
pos = base_idx(base[state]) + (u8) c;
|
||||
if (check[pos] == state)
|
||||
state = next[pos];
|
||||
else
|
||||
state = def[state];
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
236
security/apparmor/path.c
Normal file
236
security/apparmor/path.c
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor function for pathnames
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/magic.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <linux/path.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs_struct.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
|
||||
/* modified from dcache.c */
|
||||
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;
|
||||
}
|
||||
|
||||
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
|
||||
|
||||
/**
|
||||
* d_namespace_path - lookup a name associated with a given path
|
||||
* @path: path to lookup (NOT NULL)
|
||||
* @buf: buffer to store path to (NOT NULL)
|
||||
* @buflen: length of @buf
|
||||
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
|
||||
* @flags: flags controlling path lookup
|
||||
*
|
||||
* Handle path name lookup.
|
||||
*
|
||||
* Returns: %0 else error code if path lookup fails
|
||||
* When no error the path name is returned in @name which points to
|
||||
* to a position in @buf
|
||||
*/
|
||||
static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
char **name, int flags)
|
||||
{
|
||||
char *res;
|
||||
int error = 0;
|
||||
int connected = 1;
|
||||
|
||||
if (path->mnt->mnt_flags & MNT_INTERNAL) {
|
||||
/* it's not mounted anywhere */
|
||||
res = dentry_path(path->dentry, buf, buflen);
|
||||
*name = res;
|
||||
if (IS_ERR(res)) {
|
||||
*name = buf;
|
||||
return PTR_ERR(res);
|
||||
}
|
||||
if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
|
||||
strncmp(*name, "/sys/", 5) == 0) {
|
||||
/* TODO: convert over to using a per namespace
|
||||
* control instead of hard coded /proc
|
||||
*/
|
||||
return prepend(name, *name - buf, "/proc", 5);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* resolve paths relative to chroot?*/
|
||||
if (flags & PATH_CHROOT_REL) {
|
||||
struct path root;
|
||||
get_fs_root(current->fs, &root);
|
||||
res = __d_path(path, &root, buf, buflen);
|
||||
path_put(&root);
|
||||
} else {
|
||||
res = d_absolute_path(path, buf, buflen);
|
||||
if (!our_mnt(path->mnt))
|
||||
connected = 0;
|
||||
}
|
||||
|
||||
/* handle error conditions - and still allow a partial path to
|
||||
* be returned.
|
||||
*/
|
||||
if (!res || IS_ERR(res)) {
|
||||
if (PTR_ERR(res) == -ENAMETOOLONG)
|
||||
return -ENAMETOOLONG;
|
||||
connected = 0;
|
||||
res = dentry_path_raw(path->dentry, buf, buflen);
|
||||
if (IS_ERR(res)) {
|
||||
error = PTR_ERR(res);
|
||||
*name = buf;
|
||||
goto out;
|
||||
};
|
||||
} else if (!our_mnt(path->mnt))
|
||||
connected = 0;
|
||||
|
||||
*name = res;
|
||||
|
||||
/* Handle two cases:
|
||||
* 1. A deleted dentry && profile is not allowing mediation of deleted
|
||||
* 2. On some filesystems, newly allocated dentries appear to the
|
||||
* security_path hooks as a deleted dentry except without an inode
|
||||
* allocated.
|
||||
*/
|
||||
if (d_unlinked(path->dentry) && path->dentry->d_inode &&
|
||||
!(flags & PATH_MEDIATE_DELETED)) {
|
||||
error = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If the path is not connected to the expected root,
|
||||
* check if it is a sysctl and handle specially else remove any
|
||||
* leading / that __d_path may have returned.
|
||||
* Unless
|
||||
* specifically directed to connect the path,
|
||||
* OR
|
||||
* if in a chroot and doing chroot relative paths and the path
|
||||
* resolves to the namespace root (would be connected outside
|
||||
* of chroot) and specifically directed to connect paths to
|
||||
* namespace root.
|
||||
*/
|
||||
if (!connected) {
|
||||
if (!(flags & PATH_CONNECT_PATH) &&
|
||||
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
|
||||
our_mnt(path->mnt))) {
|
||||
/* disconnected path, don't return pathname starting
|
||||
* with '/'
|
||||
*/
|
||||
error = -EACCES;
|
||||
if (*res == '/')
|
||||
*name = res + 1;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
|
||||
* @path: path to get name for (NOT NULL)
|
||||
* @flags: flags controlling path lookup
|
||||
* @buffer: buffer to put name in (NOT NULL)
|
||||
* @size: size of buffer
|
||||
* @name: Returns - contains position of path name in @buffer (NOT NULL)
|
||||
*
|
||||
* Returns: %0 else error on failure
|
||||
*/
|
||||
static int get_name_to_buffer(struct path *path, int flags, char *buffer,
|
||||
int size, char **name, const char **info)
|
||||
{
|
||||
int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
|
||||
int error = d_namespace_path(path, buffer, size - adjust, name, flags);
|
||||
|
||||
if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0')
|
||||
/*
|
||||
* Append "/" to the pathname. The root directory is a special
|
||||
* case; it already ends in slash.
|
||||
*/
|
||||
strcpy(&buffer[size - 2], "/");
|
||||
|
||||
if (info && error) {
|
||||
if (error == -ENOENT)
|
||||
*info = "Failed name lookup - deleted entry";
|
||||
else if (error == -EACCES)
|
||||
*info = "Failed name lookup - disconnected path";
|
||||
else if (error == -ENAMETOOLONG)
|
||||
*info = "Failed name lookup - name too long";
|
||||
else
|
||||
*info = "Failed name lookup";
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_path_name - compute the pathname of a file
|
||||
* @path: path the file (NOT NULL)
|
||||
* @flags: flags controlling path name generation
|
||||
* @buffer: buffer that aa_get_name() allocated (NOT NULL)
|
||||
* @name: Returns - the generated path name if !error (NOT NULL)
|
||||
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
|
||||
*
|
||||
* @name is a pointer to the beginning of the pathname (which usually differs
|
||||
* from the beginning of the buffer), or NULL. If there is an error @name
|
||||
* may contain a partial or invalid name that can be used for audit purposes,
|
||||
* but it can not be used for mediation.
|
||||
*
|
||||
* We need PATH_IS_DIR to indicate whether the file is a directory or not
|
||||
* because the file may not yet exist, and so we cannot check the inode's
|
||||
* file type.
|
||||
*
|
||||
* Returns: %0 else error code if could retrieve name
|
||||
*/
|
||||
int aa_path_name(struct path *path, int flags, char **buffer, const char **name,
|
||||
const char **info)
|
||||
{
|
||||
char *buf, *str = NULL;
|
||||
int size = 256;
|
||||
int error;
|
||||
|
||||
*name = NULL;
|
||||
*buffer = NULL;
|
||||
for (;;) {
|
||||
/* freed by caller */
|
||||
buf = kmalloc(size, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
error = get_name_to_buffer(path, flags, buf, size, &str, info);
|
||||
if (error != -ENAMETOOLONG)
|
||||
break;
|
||||
|
||||
kfree(buf);
|
||||
size <<= 1;
|
||||
if (size > aa_g_path_max)
|
||||
return -ENAMETOOLONG;
|
||||
*info = NULL;
|
||||
}
|
||||
*buffer = buf;
|
||||
*name = str;
|
||||
|
||||
return error;
|
||||
}
|
1301
security/apparmor/policy.c
Normal file
1301
security/apparmor/policy.c
Normal file
File diff suppressed because it is too large
Load diff
805
security/apparmor/policy_unpack.c
Normal file
805
security/apparmor/policy_unpack.c
Normal file
|
@ -0,0 +1,805 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor functions for unpacking policy loaded from
|
||||
* userspace.
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
* AppArmor uses a serialized binary format for loading policy. To find
|
||||
* policy format documentation look in Documentation/security/apparmor.txt
|
||||
* All policy is validated before it is used.
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/context.h"
|
||||
#include "include/crypto.h"
|
||||
#include "include/match.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/policy_unpack.h"
|
||||
|
||||
/*
|
||||
* The AppArmor interface treats data as a type byte followed by the
|
||||
* actual data. The interface has the notion of a a named entry
|
||||
* which has a name (AA_NAME typecode followed by name string) followed by
|
||||
* the entries typecode and data. Named types allow for optional
|
||||
* elements and extensions to be added and tested for without breaking
|
||||
* backwards compatibility.
|
||||
*/
|
||||
|
||||
enum aa_code {
|
||||
AA_U8,
|
||||
AA_U16,
|
||||
AA_U32,
|
||||
AA_U64,
|
||||
AA_NAME, /* same as string except it is items name */
|
||||
AA_STRING,
|
||||
AA_BLOB,
|
||||
AA_STRUCT,
|
||||
AA_STRUCTEND,
|
||||
AA_LIST,
|
||||
AA_LISTEND,
|
||||
AA_ARRAY,
|
||||
AA_ARRAYEND,
|
||||
};
|
||||
|
||||
/*
|
||||
* aa_ext is the read of the buffer containing the serialized profile. The
|
||||
* data is copied into a kernel buffer in apparmorfs and then handed off to
|
||||
* the unpack routines.
|
||||
*/
|
||||
struct aa_ext {
|
||||
void *start;
|
||||
void *end;
|
||||
void *pos; /* pointer to current position in the buffer */
|
||||
u32 version;
|
||||
};
|
||||
|
||||
/* audit callback for unpack fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
if (sa->aad->iface.target) {
|
||||
struct aa_profile *name = sa->aad->iface.target;
|
||||
audit_log_format(ab, " name=");
|
||||
audit_log_untrustedstring(ab, name->base.hname);
|
||||
}
|
||||
if (sa->aad->iface.pos)
|
||||
audit_log_format(ab, " offset=%ld", sa->aad->iface.pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_iface - do audit message for policy unpacking/load/replace/remove
|
||||
* @new: profile if it has been allocated (MAYBE NULL)
|
||||
* @name: name of the profile being manipulated (MAYBE NULL)
|
||||
* @info: any extra info about the failure (MAYBE NULL)
|
||||
* @e: buffer position info
|
||||
* @error: error code
|
||||
*
|
||||
* Returns: %0 or error
|
||||
*/
|
||||
static int audit_iface(struct aa_profile *new, const char *name,
|
||||
const char *info, struct aa_ext *e, int error)
|
||||
{
|
||||
struct aa_profile *profile = __aa_current_profile();
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
sa.type = LSM_AUDIT_DATA_NONE;
|
||||
sa.aad = &aad;
|
||||
if (e)
|
||||
aad.iface.pos = e->pos - e->start;
|
||||
aad.iface.target = new;
|
||||
aad.name = name;
|
||||
aad.info = info;
|
||||
aad.error = error;
|
||||
|
||||
return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
|
||||
audit_cb);
|
||||
}
|
||||
|
||||
/* test if read will be in packed data bounds */
|
||||
static bool inbounds(struct aa_ext *e, size_t size)
|
||||
{
|
||||
return (size <= e->end - e->pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_u16_chunck - test and do bounds checking for a u16 size based chunk
|
||||
* @e: serialized data read head (NOT NULL)
|
||||
* @chunk: start address for chunk of data (NOT NULL)
|
||||
*
|
||||
* Returns: the size of chunk found with the read head at the end of the chunk.
|
||||
*/
|
||||
static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
|
||||
{
|
||||
size_t size = 0;
|
||||
|
||||
if (!inbounds(e, sizeof(u16)))
|
||||
return 0;
|
||||
size = le16_to_cpu(get_unaligned((u16 *) e->pos));
|
||||
e->pos += sizeof(u16);
|
||||
if (!inbounds(e, size))
|
||||
return 0;
|
||||
*chunk = e->pos;
|
||||
e->pos += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
/* unpack control byte */
|
||||
static bool unpack_X(struct aa_ext *e, enum aa_code code)
|
||||
{
|
||||
if (!inbounds(e, 1))
|
||||
return 0;
|
||||
if (*(u8 *) e->pos != code)
|
||||
return 0;
|
||||
e->pos++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_nameX - check is the next element is of type X with a name of @name
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
* @code: type code
|
||||
* @name: name to match to the serialized element. (MAYBE NULL)
|
||||
*
|
||||
* check that the next serialized data element is of type X and has a tag
|
||||
* name @name. If @name is specified then there must be a matching
|
||||
* name element in the stream. If @name is NULL any name element will be
|
||||
* skipped and only the typecode will be tested.
|
||||
*
|
||||
* Returns 1 on success (both type code and name tests match) and the read
|
||||
* head is advanced past the headers
|
||||
*
|
||||
* Returns: 0 if either match fails, the read head does not move
|
||||
*/
|
||||
static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||
{
|
||||
/*
|
||||
* May need to reset pos if name or type doesn't match
|
||||
*/
|
||||
void *pos = e->pos;
|
||||
/*
|
||||
* Check for presence of a tagname, and if present name size
|
||||
* AA_NAME tag value is a u16.
|
||||
*/
|
||||
if (unpack_X(e, AA_NAME)) {
|
||||
char *tag = NULL;
|
||||
size_t size = unpack_u16_chunk(e, &tag);
|
||||
/* if a name is specified it must match. otherwise skip tag */
|
||||
if (name && (!size || strcmp(name, tag)))
|
||||
goto fail;
|
||||
} else if (name) {
|
||||
/* if a name is specified and there is no name tag fail */
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* now check if type code matches */
|
||||
if (unpack_X(e, code))
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_U32, name)) {
|
||||
if (!inbounds(e, sizeof(u32)))
|
||||
return 0;
|
||||
if (data)
|
||||
*data = le32_to_cpu(get_unaligned((u32 *) e->pos));
|
||||
e->pos += sizeof(u32);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_U64, name)) {
|
||||
if (!inbounds(e, sizeof(u64)))
|
||||
return 0;
|
||||
if (data)
|
||||
*data = le64_to_cpu(get_unaligned((u64 *) e->pos));
|
||||
e->pos += sizeof(u64);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t unpack_array(struct aa_ext *e, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_ARRAY, name)) {
|
||||
int size;
|
||||
if (!inbounds(e, sizeof(u16)))
|
||||
return 0;
|
||||
size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos));
|
||||
e->pos += sizeof(u16);
|
||||
return size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
|
||||
{
|
||||
if (unpack_nameX(e, AA_BLOB, name)) {
|
||||
u32 size;
|
||||
if (!inbounds(e, sizeof(u32)))
|
||||
return 0;
|
||||
size = le32_to_cpu(get_unaligned((u32 *) e->pos));
|
||||
e->pos += sizeof(u32);
|
||||
if (inbounds(e, (size_t) size)) {
|
||||
*blob = e->pos;
|
||||
e->pos += size;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unpack_str(struct aa_ext *e, const char **string, const char *name)
|
||||
{
|
||||
char *src_str;
|
||||
size_t size = 0;
|
||||
void *pos = e->pos;
|
||||
*string = NULL;
|
||||
if (unpack_nameX(e, AA_STRING, name)) {
|
||||
size = unpack_u16_chunk(e, &src_str);
|
||||
if (size) {
|
||||
/* strings are null terminated, length is size - 1 */
|
||||
if (src_str[size - 1] != 0)
|
||||
goto fail;
|
||||
*string = src_str;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
|
||||
{
|
||||
const char *tmp;
|
||||
void *pos = e->pos;
|
||||
int res = unpack_str(e, &tmp, name);
|
||||
*string = NULL;
|
||||
|
||||
if (!res)
|
||||
return 0;
|
||||
|
||||
*string = kmemdup(tmp, res, GFP_KERNEL);
|
||||
if (!*string) {
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#define DFA_VALID_PERM_MASK 0xffffffff
|
||||
#define DFA_VALID_PERM2_MASK 0xffffffff
|
||||
|
||||
/**
|
||||
* verify_accept - verify the accept tables of a dfa
|
||||
* @dfa: dfa to verify accept tables of (NOT NULL)
|
||||
* @flags: flags governing dfa
|
||||
*
|
||||
* Returns: 1 if valid accept tables else 0 if error
|
||||
*/
|
||||
static bool verify_accept(struct aa_dfa *dfa, int flags)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* verify accept permissions */
|
||||
for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
|
||||
int mode = ACCEPT_TABLE(dfa)[i];
|
||||
|
||||
if (mode & ~DFA_VALID_PERM_MASK)
|
||||
return 0;
|
||||
|
||||
if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_dfa - unpack a file rule dfa
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
*
|
||||
* returns dfa or ERR_PTR or NULL if no dfa
|
||||
*/
|
||||
static struct aa_dfa *unpack_dfa(struct aa_ext *e)
|
||||
{
|
||||
char *blob = NULL;
|
||||
size_t size;
|
||||
struct aa_dfa *dfa = NULL;
|
||||
|
||||
size = unpack_blob(e, &blob, "aadfa");
|
||||
if (size) {
|
||||
/*
|
||||
* The dfa is aligned with in the blob to 8 bytes
|
||||
* from the beginning of the stream.
|
||||
* alignment adjust needed by dfa unpack
|
||||
*/
|
||||
size_t sz = blob - (char *) e->start -
|
||||
((e->pos - e->start) & 7);
|
||||
size_t pad = ALIGN(sz, 8) - sz;
|
||||
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
|
||||
TO_ACCEPT2_FLAG(YYTD_DATA32);
|
||||
|
||||
|
||||
if (aa_g_paranoid_load)
|
||||
flags |= DFA_FLAG_VERIFY_STATES;
|
||||
|
||||
dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
|
||||
|
||||
if (IS_ERR(dfa))
|
||||
return dfa;
|
||||
|
||||
if (!verify_accept(dfa, flags))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return dfa;
|
||||
|
||||
fail:
|
||||
aa_put_dfa(dfa);
|
||||
return ERR_PTR(-EPROTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_trans_table - unpack a profile transition table
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
* @profile: profile to add the accept table to (NOT NULL)
|
||||
*
|
||||
* Returns: 1 if table successfully unpacked
|
||||
*/
|
||||
static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
/* exec table is optional */
|
||||
if (unpack_nameX(e, AA_STRUCT, "xtable")) {
|
||||
int i, size;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
/* currently 4 exec bits and entries 0-3 are reserved iupcx */
|
||||
if (size > 16 - 4)
|
||||
goto fail;
|
||||
profile->file.trans.table = kzalloc(sizeof(char *) * size,
|
||||
GFP_KERNEL);
|
||||
if (!profile->file.trans.table)
|
||||
goto fail;
|
||||
|
||||
profile->file.trans.size = size;
|
||||
for (i = 0; i < size; i++) {
|
||||
char *str;
|
||||
int c, j, size2 = unpack_strdup(e, &str, NULL);
|
||||
/* unpack_strdup verifies that the last character is
|
||||
* null termination byte.
|
||||
*/
|
||||
if (!size2)
|
||||
goto fail;
|
||||
profile->file.trans.table[i] = str;
|
||||
/* verify that name doesn't start with space */
|
||||
if (isspace(*str))
|
||||
goto fail;
|
||||
|
||||
/* count internal # of internal \0 */
|
||||
for (c = j = 0; j < size2 - 2; j++) {
|
||||
if (!str[j])
|
||||
c++;
|
||||
}
|
||||
if (*str == ':') {
|
||||
/* beginning with : requires an embedded \0,
|
||||
* verify that exactly 1 internal \0 exists
|
||||
* trailing \0 already verified by unpack_strdup
|
||||
*/
|
||||
if (c != 1)
|
||||
goto fail;
|
||||
/* first character after : must be valid */
|
||||
if (!str[1])
|
||||
goto fail;
|
||||
} else if (c)
|
||||
/* fail - all other cases with embedded \0 */
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
aa_free_domain_entries(&profile->file.trans);
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
/* rlimits are optional */
|
||||
if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
|
||||
int i, size;
|
||||
u32 tmp = 0;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
profile->rlimits.mask = tmp;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
if (size > RLIM_NLIMITS)
|
||||
goto fail;
|
||||
for (i = 0; i < size; i++) {
|
||||
u64 tmp2 = 0;
|
||||
int a = aa_map_resource(i);
|
||||
if (!unpack_u64(e, &tmp2, NULL))
|
||||
goto fail;
|
||||
profile->rlimits.limits[a].rlim_max = tmp2;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_profile - unpack a serialized profile
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
*
|
||||
* NOTE: unpack profile sets audit struct if there is a failure
|
||||
*/
|
||||
static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||
{
|
||||
struct aa_profile *profile = NULL;
|
||||
const char *name = NULL;
|
||||
int i, error = -EPROTO;
|
||||
kernel_cap_t tmpcap;
|
||||
u32 tmp;
|
||||
|
||||
/* check that we have the right struct being passed */
|
||||
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
||||
goto fail;
|
||||
if (!unpack_str(e, &name, NULL))
|
||||
goto fail;
|
||||
|
||||
profile = aa_alloc_profile(name);
|
||||
if (!profile)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/* profile renaming is optional */
|
||||
(void) unpack_str(e, &profile->rename, "rename");
|
||||
|
||||
/* attachment string is optional */
|
||||
(void) unpack_str(e, &profile->attach, "attach");
|
||||
|
||||
/* xmatch is optional and may be NULL */
|
||||
profile->xmatch = unpack_dfa(e);
|
||||
if (IS_ERR(profile->xmatch)) {
|
||||
error = PTR_ERR(profile->xmatch);
|
||||
profile->xmatch = NULL;
|
||||
goto fail;
|
||||
}
|
||||
/* xmatch_len is not optional if xmatch is set */
|
||||
if (profile->xmatch) {
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
profile->xmatch_len = tmp;
|
||||
}
|
||||
|
||||
/* per profile debug flags (complain, audit) */
|
||||
if (!unpack_nameX(e, AA_STRUCT, "flags"))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp & PACKED_FLAG_HAT)
|
||||
profile->flags |= PFLAG_HAT;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp == PACKED_MODE_COMPLAIN)
|
||||
profile->mode = APPARMOR_COMPLAIN;
|
||||
else if (tmp == PACKED_MODE_KILL)
|
||||
profile->mode = APPARMOR_KILL;
|
||||
else if (tmp == PACKED_MODE_UNCONFINED)
|
||||
profile->mode = APPARMOR_UNCONFINED;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp)
|
||||
profile->audit = AUDIT_ALL;
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
|
||||
/* path_flags is optional */
|
||||
if (unpack_u32(e, &profile->path_flags, "path_flags"))
|
||||
profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
|
||||
else
|
||||
/* set a default value if path_flags field is not present */
|
||||
profile->path_flags = PFLAG_MEDIATE_DELETED;
|
||||
|
||||
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &tmpcap.cap[0], NULL))
|
||||
goto fail;
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "caps64")) {
|
||||
/* optional upper half of 64 bit caps */
|
||||
if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(tmpcap.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "capsx")) {
|
||||
/* optional extended caps mediation mask */
|
||||
if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!unpack_rlimits(e, profile))
|
||||
goto fail;
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "policydb")) {
|
||||
/* generic policy dfa - optional and may be NULL */
|
||||
profile->policy.dfa = unpack_dfa(e);
|
||||
if (IS_ERR(profile->policy.dfa)) {
|
||||
error = PTR_ERR(profile->policy.dfa);
|
||||
profile->policy.dfa = NULL;
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_u32(e, &profile->policy.start[0], "start"))
|
||||
/* default start state */
|
||||
profile->policy.start[0] = DFA_START;
|
||||
/* setup class index */
|
||||
for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) {
|
||||
profile->policy.start[i] =
|
||||
aa_dfa_next(profile->policy.dfa,
|
||||
profile->policy.start[0],
|
||||
i);
|
||||
}
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* get file rules */
|
||||
profile->file.dfa = unpack_dfa(e);
|
||||
if (IS_ERR(profile->file.dfa)) {
|
||||
error = PTR_ERR(profile->file.dfa);
|
||||
profile->file.dfa = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
|
||||
/* default start state */
|
||||
profile->file.start = DFA_START;
|
||||
|
||||
if (!unpack_trans_table(e, profile))
|
||||
goto fail;
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
|
||||
return profile;
|
||||
|
||||
fail:
|
||||
if (profile)
|
||||
name = NULL;
|
||||
else if (!name)
|
||||
name = "unknown";
|
||||
audit_iface(profile, name, "failed to unpack profile", e, error);
|
||||
aa_free_profile(profile);
|
||||
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_head - unpack serialized stream header
|
||||
* @e: serialized data read head (NOT NULL)
|
||||
* @required: whether the header is required or optional
|
||||
* @ns: Returns - namespace if one is specified else NULL (NOT NULL)
|
||||
*
|
||||
* Returns: error or 0 if header is good
|
||||
*/
|
||||
static int verify_header(struct aa_ext *e, int required, const char **ns)
|
||||
{
|
||||
int error = -EPROTONOSUPPORT;
|
||||
const char *name = NULL;
|
||||
*ns = NULL;
|
||||
|
||||
/* get the interface version */
|
||||
if (!unpack_u32(e, &e->version, "version")) {
|
||||
if (required) {
|
||||
audit_iface(NULL, NULL, "invalid profile format", e,
|
||||
error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* check that the interface version is currently supported */
|
||||
if (e->version != 5) {
|
||||
audit_iface(NULL, NULL, "unsupported interface version",
|
||||
e, error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* read the namespace if present */
|
||||
if (unpack_str(e, &name, "namespace")) {
|
||||
if (*ns && strcmp(*ns, name))
|
||||
audit_iface(NULL, NULL, "invalid ns change", e, error);
|
||||
else if (!*ns)
|
||||
*ns = name;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool verify_xindex(int xindex, int table_size)
|
||||
{
|
||||
int index, xtype;
|
||||
xtype = xindex & AA_X_TYPE_MASK;
|
||||
index = xindex & AA_X_INDEX_MASK;
|
||||
if (xtype == AA_X_TABLE && index > table_size)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* verify dfa xindexes are in range of transition tables */
|
||||
static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
|
||||
if (!verify_xindex(dfa_user_xindex(dfa, i), table_size))
|
||||
return 0;
|
||||
if (!verify_xindex(dfa_other_xindex(dfa, i), table_size))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_profile - Do post unpack analysis to verify profile consistency
|
||||
* @profile: profile to verify (NOT NULL)
|
||||
*
|
||||
* Returns: 0 if passes verification else error
|
||||
*/
|
||||
static int verify_profile(struct aa_profile *profile)
|
||||
{
|
||||
if (aa_g_paranoid_load) {
|
||||
if (profile->file.dfa &&
|
||||
!verify_dfa_xindex(profile->file.dfa,
|
||||
profile->file.trans.size)) {
|
||||
audit_iface(profile, NULL, "Invalid named transition",
|
||||
NULL, -EPROTO);
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void aa_load_ent_free(struct aa_load_ent *ent)
|
||||
{
|
||||
if (ent) {
|
||||
aa_put_profile(ent->rename);
|
||||
aa_put_profile(ent->old);
|
||||
aa_put_profile(ent->new);
|
||||
kzfree(ent);
|
||||
}
|
||||
}
|
||||
|
||||
struct aa_load_ent *aa_load_ent_alloc(void)
|
||||
{
|
||||
struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL);
|
||||
if (ent)
|
||||
INIT_LIST_HEAD(&ent->list);
|
||||
return ent;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_unpack - unpack packed binary profile(s) data loaded from user space
|
||||
* @udata: user data copied to kmem (NOT NULL)
|
||||
* @size: the size of the user data
|
||||
* @lh: list to place unpacked profiles in a aa_repl_ws
|
||||
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
|
||||
*
|
||||
* Unpack user data and return refcounted allocated profile(s) stored in
|
||||
* @lh in order of discovery, with the list chain stored in base.list
|
||||
* or error
|
||||
*
|
||||
* Returns: profile(s) on @lh else error pointer if fails to unpack
|
||||
*/
|
||||
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
|
||||
{
|
||||
struct aa_load_ent *tmp, *ent;
|
||||
struct aa_profile *profile = NULL;
|
||||
int error;
|
||||
struct aa_ext e = {
|
||||
.start = udata,
|
||||
.end = udata + size,
|
||||
.pos = udata,
|
||||
};
|
||||
|
||||
*ns = NULL;
|
||||
while (e.pos < e.end) {
|
||||
void *start;
|
||||
error = verify_header(&e, e.pos == e.start, ns);
|
||||
if (error)
|
||||
goto fail;
|
||||
|
||||
start = e.pos;
|
||||
profile = unpack_profile(&e);
|
||||
if (IS_ERR(profile)) {
|
||||
error = PTR_ERR(profile);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
error = verify_profile(profile);
|
||||
if (error)
|
||||
goto fail_profile;
|
||||
|
||||
error = aa_calc_profile_hash(profile, e.version, start,
|
||||
e.pos - start);
|
||||
if (error)
|
||||
goto fail_profile;
|
||||
|
||||
ent = aa_load_ent_alloc();
|
||||
if (!ent) {
|
||||
error = -ENOMEM;
|
||||
goto fail_profile;
|
||||
}
|
||||
|
||||
ent->new = profile;
|
||||
list_add_tail(&ent->list, lh);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_profile:
|
||||
aa_put_profile(profile);
|
||||
|
||||
fail:
|
||||
list_for_each_entry_safe(ent, tmp, lh, list) {
|
||||
list_del_init(&ent->list);
|
||||
aa_load_ent_free(ent);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
165
security/apparmor/procattr.c
Normal file
165
security/apparmor/procattr.c
Normal file
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor /proc/<pid>/attr/ interface functions
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/context.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/domain.h"
|
||||
#include "include/procattr.h"
|
||||
|
||||
|
||||
/**
|
||||
* aa_getprocattr - Return the profile information for @profile
|
||||
* @profile: the profile to print profile info about (NOT NULL)
|
||||
* @string: Returns - string containing the profile info (NOT NULL)
|
||||
*
|
||||
* Returns: length of @string on success else error on failure
|
||||
*
|
||||
* Requires: profile != NULL
|
||||
*
|
||||
* Creates a string containing the namespace_name://profile_name for
|
||||
* @profile.
|
||||
*
|
||||
* Returns: size of string placed in @string else error code on failure
|
||||
*/
|
||||
int aa_getprocattr(struct aa_profile *profile, char **string)
|
||||
{
|
||||
char *str;
|
||||
int len = 0, mode_len = 0, ns_len = 0, name_len;
|
||||
const char *mode_str = aa_profile_mode_names[profile->mode];
|
||||
const char *ns_name = NULL;
|
||||
struct aa_namespace *ns = profile->ns;
|
||||
struct aa_namespace *current_ns = __aa_current_profile()->ns;
|
||||
char *s;
|
||||
|
||||
if (!aa_ns_visible(current_ns, ns))
|
||||
return -EACCES;
|
||||
|
||||
ns_name = aa_ns_name(current_ns, ns);
|
||||
ns_len = strlen(ns_name);
|
||||
|
||||
/* if the visible ns_name is > 0 increase size for : :// seperator */
|
||||
if (ns_len)
|
||||
ns_len += 4;
|
||||
|
||||
/* unconfined profiles don't have a mode string appended */
|
||||
if (!unconfined(profile))
|
||||
mode_len = strlen(mode_str) + 3; /* + 3 for _() */
|
||||
|
||||
name_len = strlen(profile->base.hname);
|
||||
len = mode_len + ns_len + name_len + 1; /* + 1 for \n */
|
||||
s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */
|
||||
if (!str)
|
||||
return -ENOMEM;
|
||||
|
||||
if (ns_len) {
|
||||
/* skip over prefix current_ns->base.hname and separating // */
|
||||
sprintf(s, ":%s://", ns_name);
|
||||
s += ns_len;
|
||||
}
|
||||
if (unconfined(profile))
|
||||
/* mode string not being appended */
|
||||
sprintf(s, "%s\n", profile->base.hname);
|
||||
else
|
||||
sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
|
||||
*string = str;
|
||||
|
||||
/* NOTE: len does not include \0 of string, not saved as part of file */
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* split_token_from_name - separate a string of form <token>^<name>
|
||||
* @op: operation being checked
|
||||
* @args: string to parse (NOT NULL)
|
||||
* @token: stores returned parsed token value (NOT NULL)
|
||||
*
|
||||
* Returns: start position of name after token else NULL on failure
|
||||
*/
|
||||
static char *split_token_from_name(int op, char *args, u64 * token)
|
||||
{
|
||||
char *name;
|
||||
|
||||
*token = simple_strtoull(args, &name, 16);
|
||||
if ((name == args) || *name != '^') {
|
||||
AA_ERROR("%s: Invalid input '%s'", op_table[op], args);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
name++; /* skip ^ */
|
||||
if (!*name)
|
||||
name = NULL;
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_setprocattr_chagnehat - handle procattr interface to change_hat
|
||||
* @args: args received from writing to /proc/<pid>/attr/current (NOT NULL)
|
||||
* @size: size of the args
|
||||
* @test: true if this is a test of change_hat permissions
|
||||
*
|
||||
* Returns: %0 or error code if change_hat fails
|
||||
*/
|
||||
int aa_setprocattr_changehat(char *args, size_t size, int test)
|
||||
{
|
||||
char *hat;
|
||||
u64 token;
|
||||
const char *hats[16]; /* current hard limit on # of names */
|
||||
int count = 0;
|
||||
|
||||
hat = split_token_from_name(OP_CHANGE_HAT, args, &token);
|
||||
if (IS_ERR(hat))
|
||||
return PTR_ERR(hat);
|
||||
|
||||
if (!hat && !token) {
|
||||
AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hat) {
|
||||
/* set up hat name vector, args guaranteed null terminated
|
||||
* at args[size] by setprocattr.
|
||||
*
|
||||
* If there are multiple hat names in the buffer each is
|
||||
* separated by a \0. Ie. userspace writes them pre tokenized
|
||||
*/
|
||||
char *end = args + size;
|
||||
for (count = 0; (hat < end) && count < 16; ++count) {
|
||||
char *next = hat + strlen(hat) + 1;
|
||||
hats[count] = hat;
|
||||
hat = next;
|
||||
}
|
||||
}
|
||||
|
||||
AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
|
||||
__func__, token, hat ? hat : NULL);
|
||||
|
||||
return aa_change_hat(hats, count, token, test);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_setprocattr_changeprofile - handle procattr interface to changeprofile
|
||||
* @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL)
|
||||
* @onexec: true if change_profile should be delayed until exec
|
||||
* @test: true if this is a test of change_profile permissions
|
||||
*
|
||||
* Returns: %0 or error code if change_profile fails
|
||||
*/
|
||||
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)
|
||||
{
|
||||
char *name, *ns_name;
|
||||
|
||||
name = aa_split_fqname(fqname, &ns_name);
|
||||
return aa_change_profile(ns_name, name, onexec, test);
|
||||
}
|
154
security/apparmor/resource.c
Normal file
154
security/apparmor/resource.c
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor resource mediation and attachment
|
||||
*
|
||||
* Copyright (C) 1998-2008 Novell/SUSE
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*/
|
||||
|
||||
#include <linux/audit.h>
|
||||
|
||||
#include "include/audit.h"
|
||||
#include "include/context.h"
|
||||
#include "include/resource.h"
|
||||
#include "include/policy.h"
|
||||
|
||||
/*
|
||||
* Table of rlimit names: we generate it from resource.h.
|
||||
*/
|
||||
#include "rlim_names.h"
|
||||
|
||||
struct aa_fs_entry aa_fs_entry_rlimit[] = {
|
||||
AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK),
|
||||
{ }
|
||||
};
|
||||
|
||||
/* audit callback for resource specific fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct common_audit_data *sa = va;
|
||||
|
||||
audit_log_format(ab, " rlimit=%s value=%lu",
|
||||
rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit_resource - audit setting resource limit
|
||||
* @profile: profile being enforced (NOT NULL)
|
||||
* @resoure: rlimit being auditing
|
||||
* @value: value being set
|
||||
* @error: error value
|
||||
*
|
||||
* Returns: 0 or sa->error else other error code on failure
|
||||
*/
|
||||
static int audit_resource(struct aa_profile *profile, unsigned int resource,
|
||||
unsigned long value, int error)
|
||||
{
|
||||
struct common_audit_data sa;
|
||||
struct apparmor_audit_data aad = {0,};
|
||||
|
||||
sa.type = LSM_AUDIT_DATA_NONE;
|
||||
sa.aad = &aad;
|
||||
aad.op = OP_SETRLIMIT,
|
||||
aad.rlim.rlim = resource;
|
||||
aad.rlim.max = value;
|
||||
aad.error = error;
|
||||
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa,
|
||||
audit_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_map_resouce - map compiled policy resource to internal #
|
||||
* @resource: flattened policy resource number
|
||||
*
|
||||
* Returns: resource # for the current architecture.
|
||||
*
|
||||
* rlimit resource can vary based on architecture, map the compiled policy
|
||||
* resource # to the internal representation for the architecture.
|
||||
*/
|
||||
int aa_map_resource(int resource)
|
||||
{
|
||||
return rlim_map[resource];
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_task_setrlimit - test permission to set an rlimit
|
||||
* @profile - profile confining the task (NOT NULL)
|
||||
* @task - task the resource is being set on
|
||||
* @resource - the resource being set
|
||||
* @new_rlim - the new resource limit (NOT NULL)
|
||||
*
|
||||
* Control raising the processes hard limit.
|
||||
*
|
||||
* Returns: 0 or error code if setting resource failed
|
||||
*/
|
||||
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task,
|
||||
unsigned int resource, struct rlimit *new_rlim)
|
||||
{
|
||||
struct aa_profile *task_profile;
|
||||
int error = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
task_profile = aa_get_profile(aa_cred_profile(__task_cred(task)));
|
||||
rcu_read_unlock();
|
||||
|
||||
/* TODO: extend resource control to handle other (non current)
|
||||
* profiles. AppArmor rules currently have the implicit assumption
|
||||
* that the task is setting the resource of a task confined with
|
||||
* the same profile.
|
||||
*/
|
||||
if (profile != task_profile ||
|
||||
(profile->rlimits.mask & (1 << resource) &&
|
||||
new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max))
|
||||
error = -EACCES;
|
||||
|
||||
aa_put_profile(task_profile);
|
||||
|
||||
return audit_resource(profile, resource, new_rlim->rlim_max, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* __aa_transition_rlimits - apply new profile rlimits
|
||||
* @old: old profile on task (NOT NULL)
|
||||
* @new: new profile with rlimits to apply (NOT NULL)
|
||||
*/
|
||||
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
|
||||
{
|
||||
unsigned int mask = 0;
|
||||
struct rlimit *rlim, *initrlim;
|
||||
int i;
|
||||
|
||||
/* for any rlimits the profile controlled reset the soft limit
|
||||
* to the less of the tasks hard limit and the init tasks soft limit
|
||||
*/
|
||||
if (old->rlimits.mask) {
|
||||
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
|
||||
if (old->rlimits.mask & mask) {
|
||||
rlim = current->signal->rlim + i;
|
||||
initrlim = init_task.signal->rlim + i;
|
||||
rlim->rlim_cur = min(rlim->rlim_max,
|
||||
initrlim->rlim_cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* set any new hard limits as dictated by the new profile */
|
||||
if (!new->rlimits.mask)
|
||||
return;
|
||||
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
|
||||
if (!(new->rlimits.mask & mask))
|
||||
continue;
|
||||
|
||||
rlim = current->signal->rlim + i;
|
||||
rlim->rlim_max = min(rlim->rlim_max,
|
||||
new->rlimits.limits[i].rlim_max);
|
||||
/* soft limit should not exceed hard limit */
|
||||
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
|
||||
}
|
||||
}
|
55
security/apparmor/sid.c
Normal file
55
security/apparmor/sid.c
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor security identifier (sid) manipulation fns
|
||||
*
|
||||
* Copyright 2009-2010 Canonical Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, version 2 of the
|
||||
* License.
|
||||
*
|
||||
*
|
||||
* AppArmor allocates a unique sid for every profile loaded. If a profile
|
||||
* is replaced it receives the sid of the profile it is replacing.
|
||||
*
|
||||
* The sid value of 0 is invalid.
|
||||
*/
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include "include/sid.h"
|
||||
|
||||
/* global counter from which sids are allocated */
|
||||
static u32 global_sid;
|
||||
static DEFINE_SPINLOCK(sid_lock);
|
||||
|
||||
/* TODO FIXME: add sid to profile mapping, and sid recycling */
|
||||
|
||||
/**
|
||||
* aa_alloc_sid - allocate a new sid for a profile
|
||||
*/
|
||||
u32 aa_alloc_sid(void)
|
||||
{
|
||||
u32 sid;
|
||||
|
||||
/*
|
||||
* TODO FIXME: sid recycling - part of profile mapping table
|
||||
*/
|
||||
spin_lock(&sid_lock);
|
||||
sid = (++global_sid);
|
||||
spin_unlock(&sid_lock);
|
||||
return sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_free_sid - free a sid
|
||||
* @sid: sid to free
|
||||
*/
|
||||
void aa_free_sid(u32 sid)
|
||||
{
|
||||
; /* NOP ATM */
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue