Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

172
security/Kconfig Normal file
View file

@ -0,0 +1,172 @@
#
# Security configuration
#
menu "Security options"
source security/keys/Kconfig
config SECURITY_DMESG_RESTRICT
bool "Restrict unprivileged access to the kernel syslog"
default n
help
This enforces restrictions on unprivileged users reading the kernel
syslog via dmesg(8).
If this option is not selected, no restrictions will be enforced
unless the dmesg_restrict sysctl is explicitly set to (1).
If you are unsure how to answer this question, answer N.
config SECURITY
bool "Enable different security models"
depends on SYSFS
help
This allows you to choose different security modules to be
configured into your kernel.
If this option is not selected, the default Linux security
model will be used.
If you are unsure how to answer this question, answer N.
config SECURITYFS
bool "Enable the securityfs filesystem"
help
This will build the securityfs filesystem. It is currently used by
the TPM bios character driver and IMA, an integrity provider. It is
not used by SELinux or SMACK.
If you are unsure how to answer this question, answer N.
config SECURITY_NETWORK
bool "Socket and Networking Security Hooks"
depends on SECURITY
help
This enables the socket and networking security hooks.
If enabled, a security module can use these hooks to
implement socket and networking access controls.
If you are unsure how to answer this question, answer N.
config SECURITY_NETWORK_XFRM
bool "XFRM (IPSec) Networking Security Hooks"
depends on XFRM && SECURITY_NETWORK
help
This enables the XFRM (IPSec) networking security hooks.
If enabled, a security module can use these hooks to
implement per-packet access controls based on labels
derived from IPSec policy. Non-IPSec communications are
designated as unlabelled, and only sockets authorized
to communicate unlabelled data can send without using
IPSec.
If you are unsure how to answer this question, answer N.
config SECURITY_PATH
bool "Security hooks for pathname based access control"
depends on SECURITY
help
This enables the security hooks for pathname based access control.
If enabled, a security module can use these hooks to
implement pathname based access controls.
If you are unsure how to answer this question, answer N.
config INTEL_TXT
bool "Enable Intel(R) Trusted Execution Technology (Intel(R) TXT)"
depends on HAVE_INTEL_TXT
help
This option enables support for booting the kernel with the
Trusted Boot (tboot) module. This will utilize
Intel(R) Trusted Execution Technology to perform a measured launch
of the kernel. If the system does not support Intel(R) TXT, this
will have no effect.
Intel TXT will provide higher assurance of system configuration and
initial state as well as data reset protection. This is used to
create a robust initial kernel measurement and verification, which
helps to ensure that kernel security mechanisms are functioning
correctly. This level of protection requires a root of trust outside
of the kernel itself.
Intel TXT also helps solve real end user concerns about having
confidence that their hardware is running the VMM or kernel that
it was configured with, especially since they may be responsible for
providing such assurances to VMs and services running on it.
See <http://www.intel.com/technology/security/> for more information
about Intel(R) TXT.
See <http://tboot.sourceforge.net> for more information about tboot.
See Documentation/intel_txt.txt for a description of how to enable
Intel TXT support in a kernel boot.
If you are unsure as to whether this is required, answer N.
config LSM_MMAP_MIN_ADDR
int "Low address space for LSM to protect from user allocation"
depends on SECURITY && SECURITY_SELINUX
default 32768 if ARM || (ARM64 && COMPAT)
default 65536
help
This is the portion of low virtual memory which should be protected
from userspace allocation. Keeping a user from writing to low pages
can help reduce the impact of kernel NULL pointer bugs.
For most ia64, ppc64 and x86 users with lots of address space
a value of 65536 is reasonable and should cause no problems.
On arm and other archs it should not be higher than 32768.
Programs which use vm86 functionality or have some need to map
this low address space will need the permission specific to the
systems running LSM.
source security/selinux/Kconfig
source security/smack/Kconfig
source security/tomoyo/Kconfig
source security/apparmor/Kconfig
source security/yama/Kconfig
source security/integrity/Kconfig
source security/tz_iccc/Kconfig
choice
prompt "Default security module"
default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_YAMA if SECURITY_YAMA
default DEFAULT_SECURITY_DAC
help
Select the security module that will be used by default if the
kernel parameter security= is not specified.
config DEFAULT_SECURITY_SELINUX
bool "SELinux" if SECURITY_SELINUX=y
config DEFAULT_SECURITY_SMACK
bool "Simplified Mandatory Access Control" if SECURITY_SMACK=y
config DEFAULT_SECURITY_TOMOYO
bool "TOMOYO" if SECURITY_TOMOYO=y
config DEFAULT_SECURITY_APPARMOR
bool "AppArmor" if SECURITY_APPARMOR=y
config DEFAULT_SECURITY_YAMA
bool "Yama" if SECURITY_YAMA=y
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"
endchoice
config DEFAULT_SECURITY
string
default "selinux" if DEFAULT_SECURITY_SELINUX
default "smack" if DEFAULT_SECURITY_SMACK
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "yama" if DEFAULT_SECURITY_YAMA
default "" if DEFAULT_SECURITY_DAC
endmenu

38
security/Makefile Normal file
View file

@ -0,0 +1,38 @@
#
# Makefile for the kernel security code
#
obj-$(CONFIG_KEYS) += keys/
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
# always enable default capabilities
obj-y += commoncap.o
obj-$(CONFIG_MMU) += min_addr.o
# Object file lists
obj-$(CONFIG_SECURITY) += security.o capability.o
obj-$(CONFIG_SECURITYFS) += inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists
subdir-$(CONFIG_INTEGRITY) += integrity
obj-$(CONFIG_INTEGRITY) += integrity/
# TIMA uevent
obj-$(CONFIG_TIMA) += tima_uevent/tima_uevent.o
#ICCC
obj-$(CONFIG_TZ_ICCC) += tz_iccc/
# Knox SDP
obj-$(CONFIG_SDP) += sdp/
obj-$(CONFIG_SDP) += sdp/built-in.o

43
security/apparmor/Kconfig Normal file
View 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.

View 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)

View 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
View 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;
}

View 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
View 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;
}

View 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
View 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
View 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);
}

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

View 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;
}

View 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);
}

View 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
View 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 */
}

1161
security/capability.c Normal file

File diff suppressed because it is too large Load diff

998
security/commoncap.c Normal file
View file

@ -0,0 +1,998 @@
/* Common capabilities, needed by capability.o.
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
*/
#include <linux/capability.h>
#include <linux/audit.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/pagemap.h>
#include <linux/swap.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/ptrace.h>
#include <linux/xattr.h>
#include <linux/hugetlb.h>
#include <linux/mount.h>
#include <linux/sched.h>
#include <linux/prctl.h>
#include <linux/securebits.h>
#include <linux/user_namespace.h>
#include <linux/binfmts.h>
#include <linux/personality.h>
#ifdef CONFIG_ANDROID_PARANOID_NETWORK
#include <linux/android_aid.h>
#endif
/*
* If a non-root user executes a setuid-root binary in
* !secure(SECURE_NOROOT) mode, then we raise capabilities.
* However if fE is also set, then the intent is for only
* the file capabilities to be applied, and the setuid-root
* bit is left on either to change the uid (plausible) or
* to get full privilege on a kernel without file capabilities
* support. So in that case we do not raise capabilities.
*
* Warn if that happens, once per boot.
*/
static void warn_setuid_and_fcaps_mixed(const char *fname)
{
static int warned;
if (!warned) {
printk(KERN_INFO "warning: `%s' has both setuid-root and"
" effective capabilities. Therefore not raising all"
" capabilities.\n", fname);
warned = 1;
}
}
int cap_netlink_send(struct sock *sk, struct sk_buff *skb)
{
return 0;
}
/**
* cap_capable - Determine whether a task has a particular effective capability
* @cred: The credentials to use
* @ns: The user namespace in which we need the capability
* @cap: The capability to check for
* @audit: Whether to write an audit message or not
*
* Determine whether the nominated task has the specified capability amongst
* its effective set, returning 0 if it does, -ve if it does not.
*
* NOTE WELL: cap_has_capability() cannot be used like the kernel's capable()
* and has_capability() functions. That is, it has the reverse semantics:
* cap_has_capability() returns 0 when a task has a capability, but the
* kernel's capable() and has_capability() returns 1 for this case.
*/
int cap_capable(const struct cred *cred, struct user_namespace *targ_ns,
int cap, int audit)
{
struct user_namespace *ns = targ_ns;
#ifdef CONFIG_ANDROID_PARANOID_NETWORK
if (cap == CAP_NET_RAW && in_egroup_p(AID_NET_RAW))
return 0;
if (cap == CAP_NET_ADMIN && in_egroup_p(AID_NET_ADMIN))
return 0;
#endif
/* See if cred has the capability in the target user namespace
* by examining the target user namespace and all of the target
* user namespace's parents.
*/
for (;;) {
/* Do we have the necessary capabilities? */
if (ns == cred->user_ns)
return cap_raised(cred->cap_effective, cap) ? 0 : -EPERM;
/* Have we tried all of the parent namespaces? */
if (ns == &init_user_ns)
return -EPERM;
/*
* The owner of the user namespace in the parent of the
* user namespace has all caps.
*/
if ((ns->parent == cred->user_ns) && uid_eq(ns->owner, cred->euid))
return 0;
/*
* If you have a capability in a parent user ns, then you have
* it over all children user namespaces as well.
*/
ns = ns->parent;
}
/* We never get here */
}
/**
* cap_settime - Determine whether the current process may set the system clock
* @ts: The time to set
* @tz: The timezone to set
*
* Determine whether the current process may set the system clock and timezone
* information, returning 0 if permission granted, -ve if denied.
*/
int cap_settime(const struct timespec *ts, const struct timezone *tz)
{
if (!capable(CAP_SYS_TIME))
return -EPERM;
return 0;
}
/**
* cap_ptrace_access_check - Determine whether the current process may access
* another
* @child: The process to be accessed
* @mode: The mode of attachment.
*
* If we are in the same or an ancestor user_ns and have all the target
* task's capabilities, then ptrace access is allowed.
* If we have the ptrace capability to the target user_ns, then ptrace
* access is allowed.
* Else denied.
*
* Determine whether a process may access another, returning 0 if permission
* granted, -ve if denied.
*/
int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
int ret = 0;
const struct cred *cred, *child_cred;
rcu_read_lock();
cred = current_cred();
child_cred = __task_cred(child);
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, cred->cap_permitted))
goto out;
if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
ret = -EPERM;
out:
rcu_read_unlock();
return ret;
}
/**
* cap_ptrace_traceme - Determine whether another process may trace the current
* @parent: The task proposed to be the tracer
*
* If parent is in the same or an ancestor user_ns and has all current's
* capabilities, then ptrace access is allowed.
* If parent has the ptrace capability to current's user_ns, then ptrace
* access is allowed.
* Else denied.
*
* Determine whether the nominated task is permitted to trace the current
* process, returning 0 if permission is granted, -ve if denied.
*/
int cap_ptrace_traceme(struct task_struct *parent)
{
int ret = 0;
const struct cred *cred, *child_cred;
rcu_read_lock();
cred = __task_cred(parent);
child_cred = current_cred();
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, cred->cap_permitted))
goto out;
if (has_ns_capability(parent, child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
ret = -EPERM;
out:
rcu_read_unlock();
return ret;
}
/**
* cap_capget - Retrieve a task's capability sets
* @target: The task from which to retrieve the capability sets
* @effective: The place to record the effective set
* @inheritable: The place to record the inheritable set
* @permitted: The place to record the permitted set
*
* This function retrieves the capabilities of the nominated task and returns
* them to the caller.
*/
int cap_capget(struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted)
{
const struct cred *cred;
/* Derived from kernel/capability.c:sys_capget. */
rcu_read_lock();
cred = __task_cred(target);
*effective = cred->cap_effective;
*inheritable = cred->cap_inheritable;
*permitted = cred->cap_permitted;
rcu_read_unlock();
return 0;
}
/*
* Determine whether the inheritable capabilities are limited to the old
* permitted set. Returns 1 if they are limited, 0 if they are not.
*/
static inline int cap_inh_is_capped(void)
{
/* they are so limited unless the current task has the CAP_SETPCAP
* capability
*/
if (cap_capable(current_cred(), current_cred()->user_ns,
CAP_SETPCAP, SECURITY_CAP_AUDIT) == 0)
return 0;
return 1;
}
/**
* cap_capset - Validate and apply proposed changes to current's capabilities
* @new: The proposed new credentials; alterations should be made here
* @old: The current task's current credentials
* @effective: A pointer to the proposed new effective capabilities set
* @inheritable: A pointer to the proposed new inheritable capabilities set
* @permitted: A pointer to the proposed new permitted capabilities set
*
* This function validates and applies a proposed mass change to the current
* process's capability sets. The changes are made to the proposed new
* credentials, and assuming no error, will be committed by the caller of LSM.
*/
int cap_capset(struct cred *new,
const struct cred *old,
const kernel_cap_t *effective,
const kernel_cap_t *inheritable,
const kernel_cap_t *permitted)
{
if (cap_inh_is_capped() &&
!cap_issubset(*inheritable,
cap_combine(old->cap_inheritable,
old->cap_permitted)))
/* incapable of using this inheritable set */
return -EPERM;
if (!cap_issubset(*inheritable,
cap_combine(old->cap_inheritable,
old->cap_bset)))
/* no new pI capabilities outside bounding set */
return -EPERM;
/* verify restrictions on target's new Permitted set */
if (!cap_issubset(*permitted, old->cap_permitted))
return -EPERM;
/* verify the _new_Effective_ is a subset of the _new_Permitted_ */
if (!cap_issubset(*effective, *permitted))
return -EPERM;
new->cap_effective = *effective;
new->cap_inheritable = *inheritable;
new->cap_permitted = *permitted;
return 0;
}
/*
* Clear proposed capability sets for execve().
*/
static inline void bprm_clear_caps(struct linux_binprm *bprm)
{
cap_clear(bprm->cred->cap_permitted);
bprm->cap_effective = false;
}
/**
* cap_inode_need_killpriv - Determine if inode change affects privileges
* @dentry: The inode/dentry in being changed with change marked ATTR_KILL_PRIV
*
* Determine if an inode having a change applied that's marked ATTR_KILL_PRIV
* affects the security markings on that inode, and if it is, should
* inode_killpriv() be invoked or the change rejected?
*
* Returns 0 if granted; +ve if granted, but inode_killpriv() is required; and
* -ve to deny the change.
*/
int cap_inode_need_killpriv(struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
int error;
if (!inode->i_op->getxattr)
return 0;
error = inode->i_op->getxattr(dentry, XATTR_NAME_CAPS, NULL, 0);
if (error <= 0)
return 0;
return 1;
}
/**
* cap_inode_killpriv - Erase the security markings on an inode
* @dentry: The inode/dentry to alter
*
* Erase the privilege-enhancing security markings on an inode.
*
* Returns 0 if successful, -ve on error.
*/
int cap_inode_killpriv(struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
if (!inode->i_op->removexattr)
return 0;
return inode->i_op->removexattr(dentry, XATTR_NAME_CAPS);
}
/*
* Calculate the new process capability sets from the capability sets attached
* to a file.
*/
static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,
struct linux_binprm *bprm,
bool *effective,
bool *has_cap)
{
struct cred *new = bprm->cred;
unsigned i;
int ret = 0;
if (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE)
*effective = true;
if (caps->magic_etc & VFS_CAP_REVISION_MASK)
*has_cap = true;
CAP_FOR_EACH_U32(i) {
__u32 permitted = caps->permitted.cap[i];
__u32 inheritable = caps->inheritable.cap[i];
/*
* pP' = (X & fP) | (pI & fI)
*/
new->cap_permitted.cap[i] =
(new->cap_bset.cap[i] & permitted) |
(new->cap_inheritable.cap[i] & inheritable);
if (permitted & ~new->cap_permitted.cap[i])
/* insufficient to execute correctly */
ret = -EPERM;
}
/*
* For legacy apps, with no internal support for recognizing they
* do not have enough capabilities, we return an error if they are
* missing some "forced" (aka file-permitted) capabilities.
*/
return *effective ? ret : 0;
}
/*
* Extract the on-exec-apply capability sets for an executable file.
*/
int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps)
{
struct inode *inode = dentry->d_inode;
__u32 magic_etc;
unsigned tocopy, i;
int size;
struct vfs_cap_data caps;
memset(cpu_caps, 0, sizeof(struct cpu_vfs_cap_data));
if (!inode || !inode->i_op->getxattr)
return -ENODATA;
size = inode->i_op->getxattr((struct dentry *)dentry, XATTR_NAME_CAPS, &caps,
XATTR_CAPS_SZ);
if (size == -ENODATA || size == -EOPNOTSUPP)
/* no data, that's ok */
return -ENODATA;
if (size < 0)
return size;
if (size < sizeof(magic_etc))
return -EINVAL;
cpu_caps->magic_etc = magic_etc = le32_to_cpu(caps.magic_etc);
switch (magic_etc & VFS_CAP_REVISION_MASK) {
case VFS_CAP_REVISION_1:
if (size != XATTR_CAPS_SZ_1)
return -EINVAL;
tocopy = VFS_CAP_U32_1;
break;
case VFS_CAP_REVISION_2:
if (size != XATTR_CAPS_SZ_2)
return -EINVAL;
tocopy = VFS_CAP_U32_2;
break;
default:
return -EINVAL;
}
CAP_FOR_EACH_U32(i) {
if (i >= tocopy)
break;
cpu_caps->permitted.cap[i] = le32_to_cpu(caps.data[i].permitted);
cpu_caps->inheritable.cap[i] = le32_to_cpu(caps.data[i].inheritable);
}
cpu_caps->permitted.cap[CAP_LAST_U32] &= CAP_LAST_U32_VALID_MASK;
cpu_caps->inheritable.cap[CAP_LAST_U32] &= CAP_LAST_U32_VALID_MASK;
return 0;
}
/*
* Attempt to get the on-exec apply capability sets for an executable file from
* its xattrs and, if present, apply them to the proposed credentials being
* constructed by execve().
*/
static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_cap)
{
struct dentry *dentry;
int rc = 0;
struct cpu_vfs_cap_data vcaps;
bprm_clear_caps(bprm);
if (!file_caps_enabled)
return 0;
if (bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)
return 0;
dentry = dget(bprm->file->f_dentry);
rc = get_vfs_caps_from_disk(dentry, &vcaps);
if (rc < 0) {
if (rc == -EINVAL)
printk(KERN_NOTICE "%s: get_vfs_caps_from_disk returned %d for %s\n",
__func__, rc, bprm->filename);
else if (rc == -ENODATA)
rc = 0;
goto out;
}
rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective, has_cap);
if (rc == -EINVAL)
printk(KERN_NOTICE "%s: cap_from_disk returned %d for %s\n",
__func__, rc, bprm->filename);
out:
dput(dentry);
if (rc)
bprm_clear_caps(bprm);
return rc;
}
/**
* cap_bprm_set_creds - Set up the proposed credentials for execve().
* @bprm: The execution parameters, including the proposed creds
*
* Set up the proposed credentials for a new execution context being
* constructed by execve(). The proposed creds in @bprm->cred is altered,
* which won't take effect immediately. Returns 0 if successful, -ve on error.
*/
int cap_bprm_set_creds(struct linux_binprm *bprm)
{
const struct cred *old = current_cred();
struct cred *new = bprm->cred;
bool effective, has_cap = false;
int ret;
kuid_t root_uid;
effective = false;
ret = get_file_caps(bprm, &effective, &has_cap);
if (ret < 0)
return ret;
root_uid = make_kuid(new->user_ns, 0);
if (!issecure(SECURE_NOROOT)) {
/*
* If the legacy file capability is set, then don't set privs
* for a setuid root binary run by a non-root user. Do set it
* for a root user just to cause least surprise to an admin.
*/
if (has_cap && !uid_eq(new->uid, root_uid) && uid_eq(new->euid, root_uid)) {
warn_setuid_and_fcaps_mixed(bprm->filename);
goto skip;
}
/*
* To support inheritance of root-permissions and suid-root
* executables under compatibility mode, we override the
* capability sets for the file.
*
* If only the real uid is 0, we do not set the effective bit.
*/
if (uid_eq(new->euid, root_uid) || uid_eq(new->uid, root_uid)) {
/* pP' = (cap_bset & ~0) | (pI & ~0) */
new->cap_permitted = cap_combine(old->cap_bset,
old->cap_inheritable);
}
if (uid_eq(new->euid, root_uid))
effective = true;
}
skip:
/* if we have fs caps, clear dangerous personality flags */
if (!cap_issubset(new->cap_permitted, old->cap_permitted))
bprm->per_clear |= PER_CLEAR_ON_SETID;
/* Don't let someone trace a set[ug]id/setpcap binary with the revised
* credentials unless they have the appropriate permit.
*
* In addition, if NO_NEW_PRIVS, then ensure we get no new privs.
*/
if ((!uid_eq(new->euid, old->uid) ||
!gid_eq(new->egid, old->gid) ||
!cap_issubset(new->cap_permitted, old->cap_permitted)) &&
bprm->unsafe & ~LSM_UNSAFE_PTRACE_CAP) {
/* downgrade; they get no more than they had, and maybe less */
if (!capable(CAP_SETUID) ||
(bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS)) {
new->euid = new->uid;
new->egid = new->gid;
}
new->cap_permitted = cap_intersect(new->cap_permitted,
old->cap_permitted);
}
new->suid = new->fsuid = new->euid;
new->sgid = new->fsgid = new->egid;
if (effective)
new->cap_effective = new->cap_permitted;
else
cap_clear(new->cap_effective);
bprm->cap_effective = effective;
/*
* Audit candidate if current->cap_effective is set
*
* We do not bother to audit if 3 things are true:
* 1) cap_effective has all caps
* 2) we are root
* 3) root is supposed to have all caps (SECURE_NOROOT)
* Since this is just a normal root execing a process.
*
* Number 1 above might fail if you don't have a full bset, but I think
* that is interesting information to audit.
*/
if (!cap_isclear(new->cap_effective)) {
if (!cap_issubset(CAP_FULL_SET, new->cap_effective) ||
!uid_eq(new->euid, root_uid) || !uid_eq(new->uid, root_uid) ||
issecure(SECURE_NOROOT)) {
ret = audit_log_bprm_fcaps(bprm, new, old);
if (ret < 0)
return ret;
}
}
new->securebits &= ~issecure_mask(SECURE_KEEP_CAPS);
return 0;
}
/**
* cap_bprm_secureexec - Determine whether a secure execution is required
* @bprm: The execution parameters
*
* Determine whether a secure execution is required, return 1 if it is, and 0
* if it is not.
*
* The credentials have been committed by this point, and so are no longer
* available through @bprm->cred.
*/
int cap_bprm_secureexec(struct linux_binprm *bprm)
{
const struct cred *cred = current_cred();
kuid_t root_uid = make_kuid(cred->user_ns, 0);
if (!uid_eq(cred->uid, root_uid)) {
if (bprm->cap_effective)
return 1;
if (!cap_isclear(cred->cap_permitted))
return 1;
}
return (!uid_eq(cred->euid, cred->uid) ||
!gid_eq(cred->egid, cred->gid));
}
/**
* cap_inode_setxattr - Determine whether an xattr may be altered
* @dentry: The inode/dentry being altered
* @name: The name of the xattr to be changed
* @value: The value that the xattr will be changed to
* @size: The size of value
* @flags: The replacement flag
*
* Determine whether an xattr may be altered or set on an inode, returning 0 if
* permission is granted, -ve if denied.
*
* This is used to make sure security xattrs don't get updated or set by those
* who aren't privileged to do so.
*/
int cap_inode_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
if (!strcmp(name, XATTR_NAME_CAPS)) {
if (!capable(CAP_SETFCAP))
return -EPERM;
return 0;
}
if (!strncmp(name, XATTR_SECURITY_PREFIX,
sizeof(XATTR_SECURITY_PREFIX) - 1) &&
!capable(CAP_SYS_ADMIN))
return -EPERM;
return 0;
}
/**
* cap_inode_removexattr - Determine whether an xattr may be removed
* @dentry: The inode/dentry being altered
* @name: The name of the xattr to be changed
*
* Determine whether an xattr may be removed from an inode, returning 0 if
* permission is granted, -ve if denied.
*
* This is used to make sure security xattrs don't get removed by those who
* aren't privileged to remove them.
*/
int cap_inode_removexattr(struct dentry *dentry, const char *name)
{
if (!strcmp(name, XATTR_NAME_CAPS)) {
if (!capable(CAP_SETFCAP))
return -EPERM;
return 0;
}
if (!strncmp(name, XATTR_SECURITY_PREFIX,
sizeof(XATTR_SECURITY_PREFIX) - 1) &&
!capable(CAP_SYS_ADMIN))
return -EPERM;
return 0;
}
/*
* cap_emulate_setxuid() fixes the effective / permitted capabilities of
* a process after a call to setuid, setreuid, or setresuid.
*
* 1) When set*uiding _from_ one of {r,e,s}uid == 0 _to_ all of
* {r,e,s}uid != 0, the permitted and effective capabilities are
* cleared.
*
* 2) When set*uiding _from_ euid == 0 _to_ euid != 0, the effective
* capabilities of the process are cleared.
*
* 3) When set*uiding _from_ euid != 0 _to_ euid == 0, the effective
* capabilities are set to the permitted capabilities.
*
* fsuid is handled elsewhere. fsuid == 0 and {r,e,s}uid!= 0 should
* never happen.
*
* -astor
*
* cevans - New behaviour, Oct '99
* A process may, via prctl(), elect to keep its capabilities when it
* calls setuid() and switches away from uid==0. Both permitted and
* effective sets will be retained.
* Without this change, it was impossible for a daemon to drop only some
* of its privilege. The call to setuid(!=0) would drop all privileges!
* Keeping uid 0 is not an option because uid 0 owns too many vital
* files..
* Thanks to Olaf Kirch and Peter Benie for spotting this.
*/
static inline void cap_emulate_setxuid(struct cred *new, const struct cred *old)
{
kuid_t root_uid = make_kuid(old->user_ns, 0);
if ((uid_eq(old->uid, root_uid) ||
uid_eq(old->euid, root_uid) ||
uid_eq(old->suid, root_uid)) &&
(!uid_eq(new->uid, root_uid) &&
!uid_eq(new->euid, root_uid) &&
!uid_eq(new->suid, root_uid)) &&
!issecure(SECURE_KEEP_CAPS)) {
cap_clear(new->cap_permitted);
cap_clear(new->cap_effective);
}
if (uid_eq(old->euid, root_uid) && !uid_eq(new->euid, root_uid))
cap_clear(new->cap_effective);
if (!uid_eq(old->euid, root_uid) && uid_eq(new->euid, root_uid))
new->cap_effective = new->cap_permitted;
}
/**
* cap_task_fix_setuid - Fix up the results of setuid() call
* @new: The proposed credentials
* @old: The current task's current credentials
* @flags: Indications of what has changed
*
* Fix up the results of setuid() call before the credential changes are
* actually applied, returning 0 to grant the changes, -ve to deny them.
*/
int cap_task_fix_setuid(struct cred *new, const struct cred *old, int flags)
{
switch (flags) {
case LSM_SETID_RE:
case LSM_SETID_ID:
case LSM_SETID_RES:
/* juggle the capabilities to follow [RES]UID changes unless
* otherwise suppressed */
if (!issecure(SECURE_NO_SETUID_FIXUP))
cap_emulate_setxuid(new, old);
break;
case LSM_SETID_FS:
/* juggle the capabilties to follow FSUID changes, unless
* otherwise suppressed
*
* FIXME - is fsuser used for all CAP_FS_MASK capabilities?
* if not, we might be a bit too harsh here.
*/
if (!issecure(SECURE_NO_SETUID_FIXUP)) {
kuid_t root_uid = make_kuid(old->user_ns, 0);
if (uid_eq(old->fsuid, root_uid) && !uid_eq(new->fsuid, root_uid))
new->cap_effective =
cap_drop_fs_set(new->cap_effective);
if (!uid_eq(old->fsuid, root_uid) && uid_eq(new->fsuid, root_uid))
new->cap_effective =
cap_raise_fs_set(new->cap_effective,
new->cap_permitted);
}
break;
default:
return -EINVAL;
}
return 0;
}
/*
* Rationale: code calling task_setscheduler, task_setioprio, and
* task_setnice, assumes that
* . if capable(cap_sys_nice), then those actions should be allowed
* . if not capable(cap_sys_nice), but acting on your own processes,
* then those actions should be allowed
* This is insufficient now since you can call code without suid, but
* yet with increased caps.
* So we check for increased caps on the target process.
*/
static int cap_safe_nice(struct task_struct *p)
{
int is_subset, ret = 0;
rcu_read_lock();
is_subset = cap_issubset(__task_cred(p)->cap_permitted,
current_cred()->cap_permitted);
if (!is_subset && !ns_capable(__task_cred(p)->user_ns, CAP_SYS_NICE))
ret = -EPERM;
rcu_read_unlock();
return ret;
}
/**
* cap_task_setscheduler - Detemine if scheduler policy change is permitted
* @p: The task to affect
*
* Detemine if the requested scheduler policy change is permitted for the
* specified task, returning 0 if permission is granted, -ve if denied.
*/
int cap_task_setscheduler(struct task_struct *p)
{
return cap_safe_nice(p);
}
/**
* cap_task_ioprio - Detemine if I/O priority change is permitted
* @p: The task to affect
* @ioprio: The I/O priority to set
*
* Detemine if the requested I/O priority change is permitted for the specified
* task, returning 0 if permission is granted, -ve if denied.
*/
int cap_task_setioprio(struct task_struct *p, int ioprio)
{
return cap_safe_nice(p);
}
/**
* cap_task_ioprio - Detemine if task priority change is permitted
* @p: The task to affect
* @nice: The nice value to set
*
* Detemine if the requested task priority change is permitted for the
* specified task, returning 0 if permission is granted, -ve if denied.
*/
int cap_task_setnice(struct task_struct *p, int nice)
{
return cap_safe_nice(p);
}
/*
* Implement PR_CAPBSET_DROP. Attempt to remove the specified capability from
* the current task's bounding set. Returns 0 on success, -ve on error.
*/
static int cap_prctl_drop(unsigned long cap)
{
struct cred *new;
if (!ns_capable(current_user_ns(), CAP_SETPCAP))
return -EPERM;
if (!cap_valid(cap))
return -EINVAL;
new = prepare_creds();
if (!new)
return -ENOMEM;
cap_lower(new->cap_bset, cap);
return commit_creds(new);
}
/**
* cap_task_prctl - Implement process control functions for this security module
* @option: The process control function requested
* @arg2, @arg3, @arg4, @arg5: The argument data for this function
*
* Allow process control functions (sys_prctl()) to alter capabilities; may
* also deny access to other functions not otherwise implemented here.
*
* Returns 0 or +ve on success, -ENOSYS if this function is not implemented
* here, other -ve on error. If -ENOSYS is returned, sys_prctl() and other LSM
* modules will consider performing the function.
*/
int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
const struct cred *old = current_cred();
struct cred *new;
switch (option) {
case PR_CAPBSET_READ:
if (!cap_valid(arg2))
return -EINVAL;
return !!cap_raised(old->cap_bset, arg2);
case PR_CAPBSET_DROP:
return cap_prctl_drop(arg2);
/*
* The next four prctl's remain to assist with transitioning a
* system from legacy UID=0 based privilege (when filesystem
* capabilities are not in use) to a system using filesystem
* capabilities only - as the POSIX.1e draft intended.
*
* Note:
*
* PR_SET_SECUREBITS =
* issecure_mask(SECURE_KEEP_CAPS_LOCKED)
* | issecure_mask(SECURE_NOROOT)
* | issecure_mask(SECURE_NOROOT_LOCKED)
* | issecure_mask(SECURE_NO_SETUID_FIXUP)
* | issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED)
*
* will ensure that the current process and all of its
* children will be locked into a pure
* capability-based-privilege environment.
*/
case PR_SET_SECUREBITS:
if ((((old->securebits & SECURE_ALL_LOCKS) >> 1)
& (old->securebits ^ arg2)) /*[1]*/
|| ((old->securebits & SECURE_ALL_LOCKS & ~arg2)) /*[2]*/
|| (arg2 & ~(SECURE_ALL_LOCKS | SECURE_ALL_BITS)) /*[3]*/
|| (cap_capable(current_cred(),
current_cred()->user_ns, CAP_SETPCAP,
SECURITY_CAP_AUDIT) != 0) /*[4]*/
/*
* [1] no changing of bits that are locked
* [2] no unlocking of locks
* [3] no setting of unsupported bits
* [4] doing anything requires privilege (go read about
* the "sendmail capabilities bug")
*/
)
/* cannot change a locked bit */
return -EPERM;
new = prepare_creds();
if (!new)
return -ENOMEM;
new->securebits = arg2;
return commit_creds(new);
case PR_GET_SECUREBITS:
return old->securebits;
case PR_GET_KEEPCAPS:
return !!issecure(SECURE_KEEP_CAPS);
case PR_SET_KEEPCAPS:
if (arg2 > 1) /* Note, we rely on arg2 being unsigned here */
return -EINVAL;
if (issecure(SECURE_KEEP_CAPS_LOCKED))
return -EPERM;
new = prepare_creds();
if (!new)
return -ENOMEM;
if (arg2)
new->securebits |= issecure_mask(SECURE_KEEP_CAPS);
else
new->securebits &= ~issecure_mask(SECURE_KEEP_CAPS);
return commit_creds(new);
default:
/* No functionality available - continue with default */
return -ENOSYS;
}
}
/**
* cap_vm_enough_memory - Determine whether a new virtual mapping is permitted
* @mm: The VM space in which the new mapping is to be made
* @pages: The size of the mapping
*
* Determine whether the allocation of a new virtual mapping by the current
* task is permitted, returning 0 if permission is granted, -ve if not.
*/
int cap_vm_enough_memory(struct mm_struct *mm, long pages)
{
int cap_sys_admin = 0;
if (cap_capable(current_cred(), &init_user_ns, CAP_SYS_ADMIN,
SECURITY_CAP_NOAUDIT) == 0)
cap_sys_admin = 1;
return __vm_enough_memory(mm, pages, cap_sys_admin);
}
/*
* cap_mmap_addr - check if able to map given addr
* @addr: address attempting to be mapped
*
* If the process is attempting to map memory below dac_mmap_min_addr they need
* CAP_SYS_RAWIO. The other parameters to this function are unused by the
* capability security module. Returns 0 if this mapping should be allowed
* -EPERM if not.
*/
int cap_mmap_addr(unsigned long addr)
{
int ret = 0;
if (addr < dac_mmap_min_addr) {
ret = cap_capable(current_cred(), &init_user_ns, CAP_SYS_RAWIO,
SECURITY_CAP_AUDIT);
/* set PF_SUPERPRIV if it turns out we allow the low mmap */
if (ret == 0)
current->flags |= PF_SUPERPRIV;
}
return ret;
}
int cap_mmap_file(struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags)
{
return 0;
}

868
security/device_cgroup.c Normal file
View file

@ -0,0 +1,868 @@
/*
* device_cgroup.c - device cgroup subsystem
*
* Copyright 2007 IBM Corp
*/
#include <linux/device_cgroup.h>
#include <linux/cgroup.h>
#include <linux/ctype.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/rcupdate.h>
#include <linux/mutex.h>
#define ACC_MKNOD 1
#define ACC_READ 2
#define ACC_WRITE 4
#define ACC_MASK (ACC_MKNOD | ACC_READ | ACC_WRITE)
#define DEV_BLOCK 1
#define DEV_CHAR 2
#define DEV_ALL 4 /* this represents all devices */
static DEFINE_MUTEX(devcgroup_mutex);
enum devcg_behavior {
DEVCG_DEFAULT_NONE,
DEVCG_DEFAULT_ALLOW,
DEVCG_DEFAULT_DENY,
};
/*
* exception list locking rules:
* hold devcgroup_mutex for update/read.
* hold rcu_read_lock() for read.
*/
struct dev_exception_item {
u32 major, minor;
short type;
short access;
struct list_head list;
struct rcu_head rcu;
};
struct dev_cgroup {
struct cgroup_subsys_state css;
struct list_head exceptions;
enum devcg_behavior behavior;
};
static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s)
{
return s ? container_of(s, struct dev_cgroup, css) : NULL;
}
static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
{
return css_to_devcgroup(task_css(task, devices_cgrp_id));
}
/*
* called under devcgroup_mutex
*/
static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig)
{
struct dev_exception_item *ex, *tmp, *new;
lockdep_assert_held(&devcgroup_mutex);
list_for_each_entry(ex, orig, list) {
new = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
if (!new)
goto free_and_exit;
list_add_tail(&new->list, dest);
}
return 0;
free_and_exit:
list_for_each_entry_safe(ex, tmp, dest, list) {
list_del(&ex->list);
kfree(ex);
}
return -ENOMEM;
}
/*
* called under devcgroup_mutex
*/
static int dev_exception_add(struct dev_cgroup *dev_cgroup,
struct dev_exception_item *ex)
{
struct dev_exception_item *excopy, *walk;
lockdep_assert_held(&devcgroup_mutex);
excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL);
if (!excopy)
return -ENOMEM;
list_for_each_entry(walk, &dev_cgroup->exceptions, list) {
if (walk->type != ex->type)
continue;
if (walk->major != ex->major)
continue;
if (walk->minor != ex->minor)
continue;
walk->access |= ex->access;
kfree(excopy);
excopy = NULL;
}
if (excopy != NULL)
list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions);
return 0;
}
/*
* called under devcgroup_mutex
*/
static void dev_exception_rm(struct dev_cgroup *dev_cgroup,
struct dev_exception_item *ex)
{
struct dev_exception_item *walk, *tmp;
lockdep_assert_held(&devcgroup_mutex);
list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) {
if (walk->type != ex->type)
continue;
if (walk->major != ex->major)
continue;
if (walk->minor != ex->minor)
continue;
walk->access &= ~ex->access;
if (!walk->access) {
list_del_rcu(&walk->list);
kfree_rcu(walk, rcu);
}
}
}
static void __dev_exception_clean(struct dev_cgroup *dev_cgroup)
{
struct dev_exception_item *ex, *tmp;
list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) {
list_del_rcu(&ex->list);
kfree_rcu(ex, rcu);
}
}
/**
* dev_exception_clean - frees all entries of the exception list
* @dev_cgroup: dev_cgroup with the exception list to be cleaned
*
* called under devcgroup_mutex
*/
static void dev_exception_clean(struct dev_cgroup *dev_cgroup)
{
lockdep_assert_held(&devcgroup_mutex);
__dev_exception_clean(dev_cgroup);
}
static inline bool is_devcg_online(const struct dev_cgroup *devcg)
{
return (devcg->behavior != DEVCG_DEFAULT_NONE);
}
/**
* devcgroup_online - initializes devcgroup's behavior and exceptions based on
* parent's
* @css: css getting online
* returns 0 in case of success, error code otherwise
*/
static int devcgroup_online(struct cgroup_subsys_state *css)
{
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
struct dev_cgroup *parent_dev_cgroup = css_to_devcgroup(css->parent);
int ret = 0;
mutex_lock(&devcgroup_mutex);
if (parent_dev_cgroup == NULL)
dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW;
else {
ret = dev_exceptions_copy(&dev_cgroup->exceptions,
&parent_dev_cgroup->exceptions);
if (!ret)
dev_cgroup->behavior = parent_dev_cgroup->behavior;
}
mutex_unlock(&devcgroup_mutex);
return ret;
}
static void devcgroup_offline(struct cgroup_subsys_state *css)
{
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
mutex_lock(&devcgroup_mutex);
dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
mutex_unlock(&devcgroup_mutex);
}
/*
* called from kernel/cgroup.c with cgroup_lock() held.
*/
static struct cgroup_subsys_state *
devcgroup_css_alloc(struct cgroup_subsys_state *parent_css)
{
struct dev_cgroup *dev_cgroup;
dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL);
if (!dev_cgroup)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&dev_cgroup->exceptions);
dev_cgroup->behavior = DEVCG_DEFAULT_NONE;
return &dev_cgroup->css;
}
static void devcgroup_css_free(struct cgroup_subsys_state *css)
{
struct dev_cgroup *dev_cgroup = css_to_devcgroup(css);
__dev_exception_clean(dev_cgroup);
kfree(dev_cgroup);
}
#define DEVCG_ALLOW 1
#define DEVCG_DENY 2
#define DEVCG_LIST 3
#define MAJMINLEN 13
#define ACCLEN 4
static void set_access(char *acc, short access)
{
int idx = 0;
memset(acc, 0, ACCLEN);
if (access & ACC_READ)
acc[idx++] = 'r';
if (access & ACC_WRITE)
acc[idx++] = 'w';
if (access & ACC_MKNOD)
acc[idx++] = 'm';
}
static char type_to_char(short type)
{
if (type == DEV_ALL)
return 'a';
if (type == DEV_CHAR)
return 'c';
if (type == DEV_BLOCK)
return 'b';
return 'X';
}
static void set_majmin(char *str, unsigned m)
{
if (m == ~0)
strcpy(str, "*");
else
sprintf(str, "%u", m);
}
static int devcgroup_seq_show(struct seq_file *m, void *v)
{
struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m));
struct dev_exception_item *ex;
char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN];
rcu_read_lock();
/*
* To preserve the compatibility:
* - Only show the "all devices" when the default policy is to allow
* - List the exceptions in case the default policy is to deny
* This way, the file remains as a "whitelist of devices"
*/
if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
set_access(acc, ACC_MASK);
set_majmin(maj, ~0);
set_majmin(min, ~0);
seq_printf(m, "%c %s:%s %s\n", type_to_char(DEV_ALL),
maj, min, acc);
} else {
list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) {
set_access(acc, ex->access);
set_majmin(maj, ex->major);
set_majmin(min, ex->minor);
seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type),
maj, min, acc);
}
}
rcu_read_unlock();
return 0;
}
/**
* match_exception - iterates the exception list trying to find a complete match
* @exceptions: list of exceptions
* @type: device type (DEV_BLOCK or DEV_CHAR)
* @major: device file major number, ~0 to match all
* @minor: device file minor number, ~0 to match all
* @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD)
*
* It is considered a complete match if an exception is found that will
* contain the entire range of provided parameters.
*
* Return: true in case it matches an exception completely
*/
static bool match_exception(struct list_head *exceptions, short type,
u32 major, u32 minor, short access)
{
struct dev_exception_item *ex;
list_for_each_entry_rcu(ex, exceptions, list) {
if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK))
continue;
if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR))
continue;
if (ex->major != ~0 && ex->major != major)
continue;
if (ex->minor != ~0 && ex->minor != minor)
continue;
/* provided access cannot have more than the exception rule */
if (access & (~ex->access))
continue;
return true;
}
return false;
}
/**
* match_exception_partial - iterates the exception list trying to find a partial match
* @exceptions: list of exceptions
* @type: device type (DEV_BLOCK or DEV_CHAR)
* @major: device file major number, ~0 to match all
* @minor: device file minor number, ~0 to match all
* @access: permission mask (ACC_READ, ACC_WRITE, ACC_MKNOD)
*
* It is considered a partial match if an exception's range is found to
* contain *any* of the devices specified by provided parameters. This is
* used to make sure no extra access is being granted that is forbidden by
* any of the exception list.
*
* Return: true in case the provided range mat matches an exception completely
*/
static bool match_exception_partial(struct list_head *exceptions, short type,
u32 major, u32 minor, short access)
{
struct dev_exception_item *ex;
list_for_each_entry_rcu(ex, exceptions, list) {
if ((type & DEV_BLOCK) && !(ex->type & DEV_BLOCK))
continue;
if ((type & DEV_CHAR) && !(ex->type & DEV_CHAR))
continue;
/*
* We must be sure that both the exception and the provided
* range aren't masking all devices
*/
if (ex->major != ~0 && major != ~0 && ex->major != major)
continue;
if (ex->minor != ~0 && minor != ~0 && ex->minor != minor)
continue;
/*
* In order to make sure the provided range isn't matching
* an exception, all its access bits shouldn't match the
* exception's access bits
*/
if (!(access & ex->access))
continue;
return true;
}
return false;
}
/**
* verify_new_ex - verifies if a new exception is allowed by parent cgroup's permissions
* @dev_cgroup: dev cgroup to be tested against
* @refex: new exception
* @behavior: behavior of the exception's dev_cgroup
*
* This is used to make sure a child cgroup won't have more privileges
* than its parent
*/
static bool verify_new_ex(struct dev_cgroup *dev_cgroup,
struct dev_exception_item *refex,
enum devcg_behavior behavior)
{
bool match = false;
rcu_lockdep_assert(rcu_read_lock_held() ||
lockdep_is_held(&devcgroup_mutex),
"device_cgroup:verify_new_ex called without proper synchronization");
if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW) {
if (behavior == DEVCG_DEFAULT_ALLOW) {
/*
* new exception in the child doesn't matter, only
* adding extra restrictions
*/
return true;
} else {
/*
* new exception in the child will add more devices
* that can be acessed, so it can't match any of
* parent's exceptions, even slightly
*/
match = match_exception_partial(&dev_cgroup->exceptions,
refex->type,
refex->major,
refex->minor,
refex->access);
if (match)
return false;
return true;
}
} else {
/*
* Only behavior == DEVCG_DEFAULT_DENY allowed here, therefore
* the new exception will add access to more devices and must
* be contained completely in an parent's exception to be
* allowed
*/
match = match_exception(&dev_cgroup->exceptions, refex->type,
refex->major, refex->minor,
refex->access);
if (match)
/* parent has an exception that matches the proposed */
return true;
else
return false;
}
return false;
}
/*
* parent_has_perm:
* when adding a new allow rule to a device exception list, the rule
* must be allowed in the parent device
*/
static int parent_has_perm(struct dev_cgroup *childcg,
struct dev_exception_item *ex)
{
struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);
if (!parent)
return 1;
return verify_new_ex(parent, ex, childcg->behavior);
}
/**
* parent_allows_removal - verify if it's ok to remove an exception
* @childcg: child cgroup from where the exception will be removed
* @ex: exception being removed
*
* When removing an exception in cgroups with default ALLOW policy, it must
* be checked if removing it will give the child cgroup more access than the
* parent.
*
* Return: true if it's ok to remove exception, false otherwise
*/
static bool parent_allows_removal(struct dev_cgroup *childcg,
struct dev_exception_item *ex)
{
struct dev_cgroup *parent = css_to_devcgroup(childcg->css.parent);
if (!parent)
return true;
/* It's always allowed to remove access to devices */
if (childcg->behavior == DEVCG_DEFAULT_DENY)
return true;
/*
* Make sure you're not removing part or a whole exception existing in
* the parent cgroup
*/
return !match_exception_partial(&parent->exceptions, ex->type,
ex->major, ex->minor, ex->access);
}
/**
* may_allow_all - checks if it's possible to change the behavior to
* allow based on parent's rules.
* @parent: device cgroup's parent
* returns: != 0 in case it's allowed, 0 otherwise
*/
static inline int may_allow_all(struct dev_cgroup *parent)
{
if (!parent)
return 1;
return parent->behavior == DEVCG_DEFAULT_ALLOW;
}
/**
* revalidate_active_exceptions - walks through the active exception list and
* revalidates the exceptions based on parent's
* behavior and exceptions. The exceptions that
* are no longer valid will be removed.
* Called with devcgroup_mutex held.
* @devcg: cgroup which exceptions will be checked
*
* This is one of the three key functions for hierarchy implementation.
* This function is responsible for re-evaluating all the cgroup's active
* exceptions due to a parent's exception change.
* Refer to Documentation/cgroups/devices.txt for more details.
*/
static void revalidate_active_exceptions(struct dev_cgroup *devcg)
{
struct dev_exception_item *ex;
struct list_head *this, *tmp;
list_for_each_safe(this, tmp, &devcg->exceptions) {
ex = container_of(this, struct dev_exception_item, list);
if (!parent_has_perm(devcg, ex))
dev_exception_rm(devcg, ex);
}
}
/**
* propagate_exception - propagates a new exception to the children
* @devcg_root: device cgroup that added a new exception
* @ex: new exception to be propagated
*
* returns: 0 in case of success, != 0 in case of error
*/
static int propagate_exception(struct dev_cgroup *devcg_root,
struct dev_exception_item *ex)
{
struct cgroup_subsys_state *pos;
int rc = 0;
rcu_read_lock();
css_for_each_descendant_pre(pos, &devcg_root->css) {
struct dev_cgroup *devcg = css_to_devcgroup(pos);
/*
* Because devcgroup_mutex is held, no devcg will become
* online or offline during the tree walk (see on/offline
* methods), and online ones are safe to access outside RCU
* read lock without bumping refcnt.
*/
if (pos == &devcg_root->css || !is_devcg_online(devcg))
continue;
rcu_read_unlock();
/*
* in case both root's behavior and devcg is allow, a new
* restriction means adding to the exception list
*/
if (devcg_root->behavior == DEVCG_DEFAULT_ALLOW &&
devcg->behavior == DEVCG_DEFAULT_ALLOW) {
rc = dev_exception_add(devcg, ex);
if (rc)
break;
} else {
/*
* in the other possible cases:
* root's behavior: allow, devcg's: deny
* root's behavior: deny, devcg's: deny
* the exception will be removed
*/
dev_exception_rm(devcg, ex);
}
revalidate_active_exceptions(devcg);
rcu_read_lock();
}
rcu_read_unlock();
return rc;
}
/*
* Modify the exception list using allow/deny rules.
* CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD
* so we can give a container CAP_MKNOD to let it create devices but not
* modify the exception list.
* It seems likely we'll want to add a CAP_CONTAINER capability to allow
* us to also grant CAP_SYS_ADMIN to containers without giving away the
* device exception list controls, but for now we'll stick with CAP_SYS_ADMIN
*
* Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting
* new access is only allowed if you're in the top-level cgroup, or your
* parent cgroup has the access you're asking for.
*/
static int devcgroup_update_access(struct dev_cgroup *devcgroup,
int filetype, char *buffer)
{
const char *b;
char temp[12]; /* 11 + 1 characters needed for a u32 */
int count, rc = 0;
struct dev_exception_item ex;
struct dev_cgroup *parent = css_to_devcgroup(devcgroup->css.parent);
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
memset(&ex, 0, sizeof(ex));
b = buffer;
switch (*b) {
case 'a':
switch (filetype) {
case DEVCG_ALLOW:
if (css_has_online_children(&devcgroup->css))
return -EINVAL;
if (!may_allow_all(parent))
return -EPERM;
dev_exception_clean(devcgroup);
devcgroup->behavior = DEVCG_DEFAULT_ALLOW;
if (!parent)
break;
rc = dev_exceptions_copy(&devcgroup->exceptions,
&parent->exceptions);
if (rc)
return rc;
break;
case DEVCG_DENY:
if (css_has_online_children(&devcgroup->css))
return -EINVAL;
dev_exception_clean(devcgroup);
devcgroup->behavior = DEVCG_DEFAULT_DENY;
break;
default:
return -EINVAL;
}
return 0;
case 'b':
ex.type = DEV_BLOCK;
break;
case 'c':
ex.type = DEV_CHAR;
break;
default:
return -EINVAL;
}
b++;
if (!isspace(*b))
return -EINVAL;
b++;
if (*b == '*') {
ex.major = ~0;
b++;
} else if (isdigit(*b)) {
memset(temp, 0, sizeof(temp));
for (count = 0; count < sizeof(temp) - 1; count++) {
temp[count] = *b;
b++;
if (!isdigit(*b))
break;
}
rc = kstrtou32(temp, 10, &ex.major);
if (rc)
return -EINVAL;
} else {
return -EINVAL;
}
if (*b != ':')
return -EINVAL;
b++;
/* read minor */
if (*b == '*') {
ex.minor = ~0;
b++;
} else if (isdigit(*b)) {
memset(temp, 0, sizeof(temp));
for (count = 0; count < sizeof(temp) - 1; count++) {
temp[count] = *b;
b++;
if (!isdigit(*b))
break;
}
rc = kstrtou32(temp, 10, &ex.minor);
if (rc)
return -EINVAL;
} else {
return -EINVAL;
}
if (!isspace(*b))
return -EINVAL;
for (b++, count = 0; count < 3; count++, b++) {
switch (*b) {
case 'r':
ex.access |= ACC_READ;
break;
case 'w':
ex.access |= ACC_WRITE;
break;
case 'm':
ex.access |= ACC_MKNOD;
break;
case '\n':
case '\0':
count = 3;
break;
default:
return -EINVAL;
}
}
switch (filetype) {
case DEVCG_ALLOW:
/*
* If the default policy is to allow by default, try to remove
* an matching exception instead. And be silent about it: we
* don't want to break compatibility
*/
if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) {
/* Check if the parent allows removing it first */
if (!parent_allows_removal(devcgroup, &ex))
return -EPERM;
dev_exception_rm(devcgroup, &ex);
break;
}
if (!parent_has_perm(devcgroup, &ex))
return -EPERM;
rc = dev_exception_add(devcgroup, &ex);
break;
case DEVCG_DENY:
/*
* If the default policy is to deny by default, try to remove
* an matching exception instead. And be silent about it: we
* don't want to break compatibility
*/
if (devcgroup->behavior == DEVCG_DEFAULT_DENY)
dev_exception_rm(devcgroup, &ex);
else
rc = dev_exception_add(devcgroup, &ex);
if (rc)
break;
/* we only propagate new restrictions */
rc = propagate_exception(devcgroup, &ex);
break;
default:
rc = -EINVAL;
}
return rc;
}
static ssize_t devcgroup_access_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
int retval;
mutex_lock(&devcgroup_mutex);
retval = devcgroup_update_access(css_to_devcgroup(of_css(of)),
of_cft(of)->private, strstrip(buf));
mutex_unlock(&devcgroup_mutex);
return retval ?: nbytes;
}
static struct cftype dev_cgroup_files[] = {
{
.name = "allow",
.write = devcgroup_access_write,
.private = DEVCG_ALLOW,
},
{
.name = "deny",
.write = devcgroup_access_write,
.private = DEVCG_DENY,
},
{
.name = "list",
.seq_show = devcgroup_seq_show,
.private = DEVCG_LIST,
},
{ } /* terminate */
};
struct cgroup_subsys devices_cgrp_subsys = {
.css_alloc = devcgroup_css_alloc,
.css_free = devcgroup_css_free,
.css_online = devcgroup_online,
.css_offline = devcgroup_offline,
.legacy_cftypes = dev_cgroup_files,
};
/**
* __devcgroup_check_permission - checks if an inode operation is permitted
* @dev_cgroup: the dev cgroup to be tested against
* @type: device type
* @major: device major number
* @minor: device minor number
* @access: combination of ACC_WRITE, ACC_READ and ACC_MKNOD
*
* returns 0 on success, -EPERM case the operation is not permitted
*/
static int __devcgroup_check_permission(short type, u32 major, u32 minor,
short access)
{
struct dev_cgroup *dev_cgroup;
bool rc;
rcu_read_lock();
dev_cgroup = task_devcgroup(current);
if (dev_cgroup->behavior == DEVCG_DEFAULT_ALLOW)
/* Can't match any of the exceptions, even partially */
rc = !match_exception_partial(&dev_cgroup->exceptions,
type, major, minor, access);
else
/* Need to match completely one exception to be allowed */
rc = match_exception(&dev_cgroup->exceptions, type, major,
minor, access);
rcu_read_unlock();
if (!rc)
return -EPERM;
return 0;
}
int __devcgroup_inode_permission(struct inode *inode, int mask)
{
short type, access = 0;
if (S_ISBLK(inode->i_mode))
type = DEV_BLOCK;
if (S_ISCHR(inode->i_mode))
type = DEV_CHAR;
if (mask & MAY_WRITE)
access |= ACC_WRITE;
if (mask & MAY_READ)
access |= ACC_READ;
return __devcgroup_check_permission(type, imajor(inode), iminor(inode),
access);
}
int devcgroup_inode_mknod(int mode, dev_t dev)
{
short type;
if (!S_ISBLK(mode) && !S_ISCHR(mode))
return 0;
if (S_ISBLK(mode))
type = DEV_BLOCK;
else
type = DEV_CHAR;
return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev),
ACC_MKNOD);
}

236
security/inode.c Normal file
View file

@ -0,0 +1,236 @@
/*
* inode.c - securityfs
*
* Copyright (C) 2005 Greg Kroah-Hartman <gregkh@suse.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* Based on fs/debugfs/inode.c which had the following copyright notice:
* Copyright (C) 2004 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2004 IBM Inc.
*/
/* #define DEBUG */
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/pagemap.h>
#include <linux/init.h>
#include <linux/namei.h>
#include <linux/security.h>
#include <linux/magic.h>
static struct vfsmount *mount;
static int mount_count;
static inline int positive(struct dentry *dentry)
{
return dentry->d_inode && !d_unhashed(dentry);
}
static int fill_super(struct super_block *sb, void *data, int silent)
{
static struct tree_descr files[] = {{""}};
return simple_fill_super(sb, SECURITYFS_MAGIC, files);
}
static struct dentry *get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name,
void *data)
{
return mount_single(fs_type, flags, data, fill_super);
}
static struct file_system_type fs_type = {
.owner = THIS_MODULE,
.name = "securityfs",
.mount = get_sb,
.kill_sb = kill_litter_super,
};
/**
* securityfs_create_file - create a file in the securityfs filesystem
*
* @name: a pointer to a string containing the name of the file to create.
* @mode: the permission that the file should have
* @parent: a pointer to the parent dentry for this file. This should be a
* directory dentry if set. If this parameter is %NULL, then the
* file will be created in the root of the securityfs filesystem.
* @data: a pointer to something that the caller will want to get to later
* on. The inode.i_private pointer will point to this value on
* the open() call.
* @fops: a pointer to a struct file_operations that should be used for
* this file.
*
* This is the basic "create a file" function for securityfs. It allows for a
* wide range of flexibility in creating a file, or a directory (if you
* want to create a directory, the securityfs_create_dir() function is
* recommended to be used instead).
*
* This function returns a pointer to a dentry if it succeeds. This
* pointer must be passed to the securityfs_remove() function when the file is
* to be removed (no automatic cleanup happens if your module is unloaded,
* you are responsible here). If an error occurs, the function will return
* the error value (via ERR_PTR).
*
* If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned.
*/
struct dentry *securityfs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops)
{
struct dentry *dentry;
int is_dir = S_ISDIR(mode);
struct inode *dir, *inode;
int error;
if (!is_dir) {
BUG_ON(!fops);
mode = (mode & S_IALLUGO) | S_IFREG;
}
pr_debug("securityfs: creating file '%s'\n",name);
error = simple_pin_fs(&fs_type, &mount, &mount_count);
if (error)
return ERR_PTR(error);
if (!parent)
parent = mount->mnt_root;
dir = parent->d_inode;
mutex_lock(&dir->i_mutex);
dentry = lookup_one_len(name, parent, strlen(name));
if (IS_ERR(dentry))
goto out;
if (dentry->d_inode) {
error = -EEXIST;
goto out1;
}
inode = new_inode(dir->i_sb);
if (!inode) {
error = -ENOMEM;
goto out1;
}
inode->i_ino = get_next_ino();
inode->i_mode = mode;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
inode->i_private = data;
if (is_dir) {
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inc_nlink(inode);
inc_nlink(dir);
} else {
inode->i_fop = fops;
}
d_instantiate(dentry, inode);
dget(dentry);
mutex_unlock(&dir->i_mutex);
return dentry;
out1:
dput(dentry);
dentry = ERR_PTR(error);
out:
mutex_unlock(&dir->i_mutex);
simple_release_fs(&mount, &mount_count);
return dentry;
}
EXPORT_SYMBOL_GPL(securityfs_create_file);
/**
* securityfs_create_dir - create a directory in the securityfs filesystem
*
* @name: a pointer to a string containing the name of the directory to
* create.
* @parent: a pointer to the parent dentry for this file. This should be a
* directory dentry if set. If this parameter is %NULL, then the
* directory will be created in the root of the securityfs filesystem.
*
* This function creates a directory in securityfs with the given @name.
*
* This function returns a pointer to a dentry if it succeeds. This
* pointer must be passed to the securityfs_remove() function when the file is
* to be removed (no automatic cleanup happens if your module is unloaded,
* you are responsible here). If an error occurs, %NULL will be returned.
*
* If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned. It is not wise to check for this value, but rather, check for
* %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling
* code.
*/
struct dentry *securityfs_create_dir(const char *name, struct dentry *parent)
{
return securityfs_create_file(name,
S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
parent, NULL, NULL);
}
EXPORT_SYMBOL_GPL(securityfs_create_dir);
/**
* securityfs_remove - removes a file or directory from the securityfs filesystem
*
* @dentry: a pointer to a the dentry of the file or directory to be removed.
*
* This function removes a file or directory in securityfs that was previously
* created with a call to another securityfs function (like
* securityfs_create_file() or variants thereof.)
*
* This function is required to be called in order for the file to be
* removed. No automatic cleanup of files will happen when a module is
* removed; you are responsible here.
*/
void securityfs_remove(struct dentry *dentry)
{
struct dentry *parent;
if (!dentry || IS_ERR(dentry))
return;
parent = dentry->d_parent;
if (!parent || !parent->d_inode)
return;
mutex_lock(&parent->d_inode->i_mutex);
if (positive(dentry)) {
if (dentry->d_inode) {
if (S_ISDIR(dentry->d_inode->i_mode))
simple_rmdir(parent->d_inode, dentry);
else
simple_unlink(parent->d_inode, dentry);
dput(dentry);
}
}
mutex_unlock(&parent->d_inode->i_mutex);
simple_release_fs(&mount, &mount_count);
}
EXPORT_SYMBOL_GPL(securityfs_remove);
static struct kobject *security_kobj;
static int __init securityfs_init(void)
{
int retval;
security_kobj = kobject_create_and_add("security", kernel_kobj);
if (!security_kobj)
return -EINVAL;
retval = register_filesystem(&fs_type);
if (retval)
kobject_put(security_kobj);
return retval;
}
core_initcall(securityfs_init);
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,62 @@
#
config INTEGRITY
bool "Integrity subsystem"
depends on SECURITY
default y
help
This option enables the integrity subsystem, which is comprised
of a number of different components including the Integrity
Measurement Architecture (IMA), Extended Verification Module
(EVM), IMA-appraisal extension, digital signature verification
extension and audit measurement log support.
Each of these components can be enabled/disabled separately.
Refer to the individual components for additional details.
if INTEGRITY
config INTEGRITY_SIGNATURE
boolean "Digital signature verification using multiple keyrings"
depends on KEYS
default n
select SIGNATURE
help
This option enables digital signature verification support
using multiple keyrings. It defines separate keyrings for each
of the different use cases - evm, ima, and modules.
Different keyrings improves search performance, but also allow
to "lock" certain keyring to prevent adding new keys.
This is useful for evm and module keyrings, when keys are
usually only added from initramfs.
config INTEGRITY_ASYMMETRIC_KEYS
boolean "Enable asymmetric keys support"
depends on INTEGRITY_SIGNATURE
default n
select ASYMMETRIC_KEY_TYPE
select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
select PUBLIC_KEY_ALGO_RSA
select X509_CERTIFICATE_PARSER
help
This option enables digital signature verification using
asymmetric keys.
config INTEGRITY_AUDIT
bool "Enables integrity auditing support "
depends on AUDIT
default y
help
In addition to enabling integrity auditing support, this
option adds a kernel parameter 'integrity_audit', which
controls the level of integrity auditing messages.
0 - basic integrity auditing messages (default)
1 - additional integrity auditing messages
Additional informational integrity auditing messages would
be enabled by specifying 'integrity_audit=1' on the kernel
command line.
source security/integrity/ima/Kconfig
source security/integrity/evm/Kconfig
endif # if INTEGRITY

View file

@ -0,0 +1,15 @@
#
# Makefile for caching inode integrity data (iint)
#
obj-$(CONFIG_INTEGRITY) += integrity.o
integrity-y := iint.o
integrity-$(CONFIG_INTEGRITY_AUDIT) += integrity_audit.o
integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o
integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o
subdir-$(CONFIG_IMA) += ima
obj-$(CONFIG_IMA) += ima/
subdir-$(CONFIG_EVM) += evm
obj-$(CONFIG_EVM) += evm/

View file

@ -0,0 +1,86 @@
/*
* Copyright (C) 2011 Intel Corporation
*
* Author:
* Dmitry Kasatkin <dmitry.kasatkin@intel.com>
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/err.h>
#include <linux/sched.h>
#include <linux/rbtree.h>
#include <linux/cred.h>
#include <linux/key-type.h>
#include <linux/digsig.h>
#include "integrity.h"
static struct key *keyring[INTEGRITY_KEYRING_MAX];
static const char *keyring_name[INTEGRITY_KEYRING_MAX] = {
"_evm",
"_module",
#ifndef CONFIG_IMA_TRUSTED_KEYRING
"_ima",
#else
".ima",
#endif
};
int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen,
const char *digest, int digestlen)
{
if (id >= INTEGRITY_KEYRING_MAX)
return -EINVAL;
if (!keyring[id]) {
keyring[id] =
request_key(&key_type_keyring, keyring_name[id], NULL);
if (IS_ERR(keyring[id])) {
int err = PTR_ERR(keyring[id]);
pr_err("no %s keyring: %d\n", keyring_name[id], err);
keyring[id] = NULL;
return err;
}
}
switch (sig[1]) {
case 1:
/* v1 API expect signature without xattr type */
return digsig_verify(keyring[id], sig + 1, siglen - 1,
digest, digestlen);
case 2:
return asymmetric_verify(keyring[id], sig, siglen,
digest, digestlen);
}
return -EOPNOTSUPP;
}
int integrity_init_keyring(const unsigned int id)
{
const struct cred *cred = current_cred();
int err = 0;
keyring[id] = keyring_alloc(keyring_name[id], KUIDT_INIT(0),
KGIDT_INIT(0), cred,
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
KEY_USR_VIEW | KEY_USR_READ |
KEY_USR_WRITE | KEY_USR_SEARCH),
KEY_ALLOC_NOT_IN_QUOTA, NULL);
if (!IS_ERR(keyring[id]))
set_bit(KEY_FLAG_TRUSTED_ONLY, &keyring[id]->flags);
else {
err = PTR_ERR(keyring[id]);
pr_info("Can't allocate %s keyring (%d)\n",
keyring_name[id], err);
keyring[id] = NULL;
}
return err;
}

View file

@ -0,0 +1,105 @@
/*
* Copyright (C) 2013 Intel Corporation
*
* Author:
* Dmitry Kasatkin <dmitry.kasatkin@intel.com>
*
* 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.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/err.h>
#include <linux/ratelimit.h>
#include <linux/key-type.h>
#include <crypto/public_key.h>
#include <keys/asymmetric-type.h>
#include "integrity.h"
/*
* Request an asymmetric key.
*/
static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
{
struct key *key;
char name[12];
sprintf(name, "id:%08x", keyid);
pr_debug("key search: \"%s\"\n", name);
if (keyring) {
/* search in specific keyring */
key_ref_t kref;
kref = keyring_search(make_key_ref(keyring, 1),
&key_type_asymmetric, name);
if (IS_ERR(kref))
key = ERR_CAST(kref);
else
key = key_ref_to_ptr(kref);
} else {
key = request_key(&key_type_asymmetric, name, NULL);
}
if (IS_ERR(key)) {
pr_err_ratelimited("Request for unknown key '%s' err %ld\n",
name, PTR_ERR(key));
switch (PTR_ERR(key)) {
/* Hide some search errors */
case -EACCES:
case -ENOTDIR:
case -EAGAIN:
return ERR_PTR(-ENOKEY);
default:
return key;
}
}
pr_debug("%s() = 0 [%x]\n", __func__, key_serial(key));
return key;
}
int asymmetric_verify(struct key *keyring, const char *sig,
int siglen, const char *data, int datalen)
{
struct public_key_signature pks;
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
struct key *key;
int ret = -ENOMEM;
if (siglen <= sizeof(*hdr))
return -EBADMSG;
siglen -= sizeof(*hdr);
if (siglen != __be16_to_cpu(hdr->sig_size))
return -EBADMSG;
if (hdr->hash_algo >= PKEY_HASH__LAST)
return -ENOPKG;
key = request_asymmetric_key(keyring, __be32_to_cpu(hdr->keyid));
if (IS_ERR(key))
return PTR_ERR(key);
memset(&pks, 0, sizeof(pks));
pks.pkey_hash_algo = hdr->hash_algo;
pks.digest = (u8 *)data;
pks.digest_size = datalen;
pks.nr_mpi = 1;
pks.rsa.s = mpi_read_raw_data(hdr->sig, siglen);
if (pks.rsa.s)
ret = verify_signature(key, &pks);
mpi_free(pks.rsa.s);
key_put(key);
pr_debug("%s() = %d\n", __func__, ret);
return ret;
}

View file

@ -0,0 +1,44 @@
config EVM
boolean "EVM support"
select KEYS
select ENCRYPTED_KEYS
select CRYPTO_HMAC
select CRYPTO_SHA1
default n
help
EVM protects a file's security extended attributes against
integrity attacks.
If you are unsure how to answer this question, answer N.
config EVM_ATTR_FSUUID
bool "FSUUID (version 2)"
default y
depends on EVM
help
Include filesystem UUID for HMAC calculation.
Default value is 'selected', which is former version 2.
if 'not selected', it is former version 1
WARNING: changing the HMAC calculation method or adding
additional info to the calculation, requires existing EVM
labeled file systems to be relabeled.
config EVM_EXTRA_SMACK_XATTRS
bool "Additional SMACK xattrs"
depends on EVM && SECURITY_SMACK
default n
help
Include additional SMACK xattrs for HMAC calculation.
In addition to the original security xattrs (eg. security.selinux,
security.SMACK64, security.capability, and security.ima) included
in the HMAC calculation, enabling this option includes newly defined
Smack xattrs: security.SMACK64EXEC, security.SMACK64TRANSMUTE and
security.SMACK64MMAP.
WARNING: changing the HMAC calculation method or adding
additional info to the calculation, requires existing EVM
labeled file systems to be relabeled.

View file

@ -0,0 +1,7 @@
#
# Makefile for building the Extended Verification Module(EVM)
#
obj-$(CONFIG_EVM) += evm.o
evm-y := evm_main.o evm_crypto.o evm_secfs.o
evm-$(CONFIG_FS_POSIX_ACL) += evm_posix_acl.o

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2010 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
* Kylene Hall <kjhall@us.ibm.com>
*
* 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.
*
* File: evm.h
*
*/
#ifndef __INTEGRITY_EVM_H
#define __INTEGRITY_EVM_H
#include <linux/xattr.h>
#include <linux/security.h>
#include "../integrity.h"
extern int evm_initialized;
extern char *evm_hmac;
extern char *evm_hash;
#define EVM_ATTR_FSUUID 0x0001
extern int evm_hmac_attrs;
extern struct crypto_shash *hmac_tfm;
extern struct crypto_shash *hash_tfm;
/* List of EVM protected security xattrs */
extern char *evm_config_xattrnames[];
int evm_init_key(void);
int evm_update_evmxattr(struct dentry *dentry,
const char *req_xattr_name,
const char *req_xattr_value,
size_t req_xattr_value_len);
int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name,
const char *req_xattr_value,
size_t req_xattr_value_len, char *digest);
int evm_calc_hash(struct dentry *dentry, const char *req_xattr_name,
const char *req_xattr_value,
size_t req_xattr_value_len, char *digest);
int evm_init_hmac(struct inode *inode, const struct xattr *xattr,
char *hmac_val);
int evm_init_secfs(void);
#endif

View file

@ -0,0 +1,262 @@
/*
* Copyright (C) 2005-2010 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
* Kylene Hall <kjhall@us.ibm.com>
*
* 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.
*
* File: evm_crypto.c
* Using root's kernel master key (kmk), calculate the HMAC
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/crypto.h>
#include <linux/xattr.h>
#include <keys/encrypted-type.h>
#include <crypto/hash.h>
#include "evm.h"
#define EVMKEY "evm-key"
#define MAX_KEY_SIZE 128
static unsigned char evmkey[MAX_KEY_SIZE];
static int evmkey_len = MAX_KEY_SIZE;
struct crypto_shash *hmac_tfm;
struct crypto_shash *hash_tfm;
static DEFINE_MUTEX(mutex);
static struct shash_desc *init_desc(char type)
{
long rc;
char *algo;
struct crypto_shash **tfm;
struct shash_desc *desc;
if (type == EVM_XATTR_HMAC) {
tfm = &hmac_tfm;
algo = evm_hmac;
} else {
tfm = &hash_tfm;
algo = evm_hash;
}
if (*tfm == NULL) {
mutex_lock(&mutex);
if (*tfm)
goto out;
*tfm = crypto_alloc_shash(algo, 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(*tfm)) {
rc = PTR_ERR(*tfm);
pr_err("Can not allocate %s (reason: %ld)\n", algo, rc);
*tfm = NULL;
mutex_unlock(&mutex);
return ERR_PTR(rc);
}
if (type == EVM_XATTR_HMAC) {
rc = crypto_shash_setkey(*tfm, evmkey, evmkey_len);
if (rc) {
crypto_free_shash(*tfm);
*tfm = NULL;
mutex_unlock(&mutex);
return ERR_PTR(rc);
}
}
out:
mutex_unlock(&mutex);
}
desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(*tfm),
GFP_KERNEL);
if (!desc)
return ERR_PTR(-ENOMEM);
desc->tfm = *tfm;
desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
rc = crypto_shash_init(desc);
if (rc) {
kfree(desc);
return ERR_PTR(rc);
}
return desc;
}
/* Protect against 'cutting & pasting' security.evm xattr, include inode
* specific info.
*
* (Additional directory/file metadata needs to be added for more complete
* protection.)
*/
static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
char *digest)
{
struct h_misc {
unsigned long ino;
__u32 generation;
uid_t uid;
gid_t gid;
umode_t mode;
} hmac_misc;
memset(&hmac_misc, 0, sizeof(hmac_misc));
hmac_misc.ino = inode->i_ino;
hmac_misc.generation = inode->i_generation;
hmac_misc.uid = from_kuid(&init_user_ns, inode->i_uid);
hmac_misc.gid = from_kgid(&init_user_ns, inode->i_gid);
hmac_misc.mode = inode->i_mode;
crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof(hmac_misc));
if (evm_hmac_attrs & EVM_ATTR_FSUUID)
crypto_shash_update(desc, inode->i_sb->s_uuid,
sizeof(inode->i_sb->s_uuid));
crypto_shash_final(desc, digest);
}
/*
* Calculate the HMAC value across the set of protected security xattrs.
*
* Instead of retrieving the requested xattr, for performance, calculate
* the hmac using the requested xattr value. Don't alloc/free memory for
* each xattr, but attempt to re-use the previously allocated memory.
*/
static int evm_calc_hmac_or_hash(struct dentry *dentry,
const char *req_xattr_name,
const char *req_xattr_value,
size_t req_xattr_value_len,
char type, char *digest)
{
struct inode *inode = dentry->d_inode;
struct shash_desc *desc;
char **xattrname;
size_t xattr_size = 0;
char *xattr_value = NULL;
int error;
int size;
if (!inode->i_op->getxattr)
return -EOPNOTSUPP;
desc = init_desc(type);
if (IS_ERR(desc))
return PTR_ERR(desc);
error = -ENODATA;
for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) {
if ((req_xattr_name && req_xattr_value)
&& !strcmp(*xattrname, req_xattr_name)) {
error = 0;
crypto_shash_update(desc, (const u8 *)req_xattr_value,
req_xattr_value_len);
continue;
}
size = vfs_getxattr_alloc(dentry, *xattrname,
&xattr_value, xattr_size, GFP_NOFS);
if (size == -ENOMEM) {
error = -ENOMEM;
goto out;
}
if (size < 0)
continue;
error = 0;
xattr_size = size;
crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size);
}
hmac_add_misc(desc, inode, digest);
out:
kfree(xattr_value);
kfree(desc);
return error;
}
int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name,
const char *req_xattr_value, size_t req_xattr_value_len,
char *digest)
{
return evm_calc_hmac_or_hash(dentry, req_xattr_name, req_xattr_value,
req_xattr_value_len, EVM_XATTR_HMAC, digest);
}
int evm_calc_hash(struct dentry *dentry, const char *req_xattr_name,
const char *req_xattr_value, size_t req_xattr_value_len,
char *digest)
{
return evm_calc_hmac_or_hash(dentry, req_xattr_name, req_xattr_value,
req_xattr_value_len, IMA_XATTR_DIGEST, digest);
}
/*
* Calculate the hmac and update security.evm xattr
*
* Expects to be called with i_mutex locked.
*/
int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name,
const char *xattr_value, size_t xattr_value_len)
{
struct inode *inode = dentry->d_inode;
struct evm_ima_xattr_data xattr_data;
int rc = 0;
rc = evm_calc_hmac(dentry, xattr_name, xattr_value,
xattr_value_len, xattr_data.digest);
if (rc == 0) {
xattr_data.type = EVM_XATTR_HMAC;
rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM,
&xattr_data,
sizeof(xattr_data), 0);
} else if (rc == -ENODATA && inode->i_op->removexattr) {
rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM);
}
return rc;
}
int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr,
char *hmac_val)
{
struct shash_desc *desc;
desc = init_desc(EVM_XATTR_HMAC);
if (IS_ERR(desc)) {
pr_info("init_desc failed\n");
return PTR_ERR(desc);
}
crypto_shash_update(desc, lsm_xattr->value, lsm_xattr->value_len);
hmac_add_misc(desc, inode, hmac_val);
kfree(desc);
return 0;
}
/*
* Get the key from the TPM for the SHA1-HMAC
*/
int evm_init_key(void)
{
struct key *evm_key;
struct encrypted_key_payload *ekp;
int rc = 0;
evm_key = request_key(&key_type_encrypted, EVMKEY, NULL);
if (IS_ERR(evm_key))
return -ENOENT;
down_read(&evm_key->sem);
ekp = evm_key->payload.data;
if (ekp->decrypted_datalen > MAX_KEY_SIZE) {
rc = -EINVAL;
goto out;
}
memcpy(evmkey, ekp->decrypted_data, ekp->decrypted_datalen);
out:
/* burn the original key contents */
memset(ekp->decrypted_data, 0, ekp->decrypted_datalen);
up_read(&evm_key->sem);
key_put(evm_key);
return rc;
}

View file

@ -0,0 +1,494 @@
/*
* Copyright (C) 2005-2010 IBM Corporation
*
* Author:
* Mimi Zohar <zohar@us.ibm.com>
* Kylene Hall <kjhall@us.ibm.com>
*
* 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.
*
* File: evm_main.c
* implements evm_inode_setxattr, evm_inode_post_setxattr,
* evm_inode_removexattr, and evm_verifyxattr
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/crypto.h>
#include <linux/audit.h>
#include <linux/xattr.h>
#include <linux/integrity.h>
#include <linux/evm.h>
#include <crypto/hash.h>
#include <crypto/algapi.h>
#include "evm.h"
int evm_initialized;
static char *integrity_status_msg[] = {
"pass", "fail", "no_label", "no_xattrs", "unknown"
};
char *evm_hmac = "hmac(sha1)";
char *evm_hash = "sha1";
int evm_hmac_attrs;
char *evm_config_xattrnames[] = {
#ifdef CONFIG_SECURITY_SELINUX
XATTR_NAME_SELINUX,
#endif
#ifdef CONFIG_SECURITY_SMACK
XATTR_NAME_SMACK,
#ifdef CONFIG_EVM_EXTRA_SMACK_XATTRS
XATTR_NAME_SMACKEXEC,
XATTR_NAME_SMACKTRANSMUTE,
XATTR_NAME_SMACKMMAP,
#endif
#endif
#ifdef CONFIG_IMA_APPRAISE
XATTR_NAME_IMA,
#endif
XATTR_NAME_CAPS,
NULL
};
static int evm_fixmode;
static int __init evm_set_fixmode(char *str)
{
if (strncmp(str, "fix", 3) == 0)
evm_fixmode = 1;
return 0;
}
__setup("evm=", evm_set_fixmode);
static void __init evm_init_config(void)
{
#ifdef CONFIG_EVM_ATTR_FSUUID
evm_hmac_attrs |= EVM_ATTR_FSUUID;
#endif
pr_info("HMAC attrs: 0x%x\n", evm_hmac_attrs);
}
static int evm_find_protected_xattrs(struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
char **xattr;
int error;
int count = 0;
if (!inode->i_op->getxattr)
return -EOPNOTSUPP;
for (xattr = evm_config_xattrnames; *xattr != NULL; xattr++) {
error = inode->i_op->getxattr(dentry, *xattr, NULL, 0);
if (error < 0) {
if (error == -ENODATA)
continue;
return error;
}
count++;
}
return count;
}
/*
* evm_verify_hmac - calculate and compare the HMAC with the EVM xattr
*
* Compute the HMAC on the dentry's protected set of extended attributes
* and compare it against the stored security.evm xattr.
*
* For performance:
* - use the previoulsy retrieved xattr value and length to calculate the
* HMAC.)
* - cache the verification result in the iint, when available.
*
* Returns integrity status
*/
static enum integrity_status evm_verify_hmac(struct dentry *dentry,
const char *xattr_name,
char *xattr_value,
size_t xattr_value_len,
struct integrity_iint_cache *iint)
{
struct evm_ima_xattr_data *xattr_data = NULL;
struct evm_ima_xattr_data calc;
enum integrity_status evm_status = INTEGRITY_PASS;
int rc, xattr_len;
if (iint && iint->evm_status == INTEGRITY_PASS)
return iint->evm_status;
/* if status is not PASS, try to check again - against -ENOMEM */
/* first need to know the sig type */
rc = vfs_getxattr_alloc(dentry, XATTR_NAME_EVM, (char **)&xattr_data, 0,
GFP_NOFS);
if (rc <= 0) {
evm_status = INTEGRITY_FAIL;
if (rc == -ENODATA) {
rc = evm_find_protected_xattrs(dentry);
if (rc > 0)
evm_status = INTEGRITY_NOLABEL;
else if (rc == 0)
evm_status = INTEGRITY_NOXATTRS; /* new file */
} else if (rc == -EOPNOTSUPP) {
evm_status = INTEGRITY_UNKNOWN;
}
goto out;
}
xattr_len = rc;
/* check value type */
switch (xattr_data->type) {
case EVM_XATTR_HMAC:
rc = evm_calc_hmac(dentry, xattr_name, xattr_value,
xattr_value_len, calc.digest);
if (rc)
break;
rc = crypto_memneq(xattr_data->digest, calc.digest,
sizeof(calc.digest));
if (rc)
rc = -EINVAL;
break;
case EVM_IMA_XATTR_DIGSIG:
rc = evm_calc_hash(dentry, xattr_name, xattr_value,
xattr_value_len, calc.digest);
if (rc)
break;
rc = integrity_digsig_verify(INTEGRITY_KEYRING_EVM,
(const char *)xattr_data, xattr_len,
calc.digest, sizeof(calc.digest));
if (!rc) {
/* we probably want to replace rsa with hmac here */
evm_update_evmxattr(dentry, xattr_name, xattr_value,
xattr_value_len);
}
break;
default:
rc = -EINVAL;
break;
}
if (rc)
evm_status = (rc == -ENODATA) ?
INTEGRITY_NOXATTRS : INTEGRITY_FAIL;
out:
if (iint)
iint->evm_status = evm_status;
kfree(xattr_data);
return evm_status;
}
static int evm_protected_xattr(const char *req_xattr_name)
{
char **xattrname;
int namelen;
int found = 0;
namelen = strlen(req_xattr_name);
for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) {
if ((strlen(*xattrname) == namelen)
&& (strncmp(req_xattr_name, *xattrname, namelen) == 0)) {
found = 1;
break;
}
if (strncmp(req_xattr_name,
*xattrname + XATTR_SECURITY_PREFIX_LEN,
strlen(req_xattr_name)) == 0) {
found = 1;
break;
}
}
return found;
}
/**
* evm_verifyxattr - verify the integrity of the requested xattr
* @dentry: object of the verify xattr
* @xattr_name: requested xattr
* @xattr_value: requested xattr value
* @xattr_value_len: requested xattr value length
*
* Calculate the HMAC for the given dentry and verify it against the stored
* security.evm xattr. For performance, use the xattr value and length
* previously retrieved to calculate the HMAC.
*
* Returns the xattr integrity status.
*
* This function requires the caller to lock the inode's i_mutex before it
* is executed.
*/
enum integrity_status evm_verifyxattr(struct dentry *dentry,
const char *xattr_name,
void *xattr_value, size_t xattr_value_len,
struct integrity_iint_cache *iint)
{
if (!evm_initialized || !evm_protected_xattr(xattr_name))
return INTEGRITY_UNKNOWN;
if (!iint) {
iint = integrity_iint_find(dentry->d_inode);
if (!iint)
return INTEGRITY_UNKNOWN;
}
return evm_verify_hmac(dentry, xattr_name, xattr_value,
xattr_value_len, iint);
}
EXPORT_SYMBOL_GPL(evm_verifyxattr);
/*
* evm_verify_current_integrity - verify the dentry's metadata integrity
* @dentry: pointer to the affected dentry
*
* Verify and return the dentry's metadata integrity. The exceptions are
* before EVM is initialized or in 'fix' mode.
*/
static enum integrity_status evm_verify_current_integrity(struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
if (!evm_initialized || !S_ISREG(inode->i_mode) || evm_fixmode)
return 0;
return evm_verify_hmac(dentry, NULL, NULL, 0, NULL);
}
/*
* evm_protect_xattr - protect the EVM extended attribute
*
* Prevent security.evm from being modified or removed without the
* necessary permissions or when the existing value is invalid.
*
* The posix xattr acls are 'system' prefixed, which normally would not
* affect security.evm. An interesting side affect of writing posix xattr
* acls is their modifying of the i_mode, which is included in security.evm.
* For posix xattr acls only, permit security.evm, even if it currently
* doesn't exist, to be updated.
*/
static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name,
const void *xattr_value, size_t xattr_value_len)
{
enum integrity_status evm_status;
if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) {
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
} else if (!evm_protected_xattr(xattr_name)) {
if (!posix_xattr_acl(xattr_name))
return 0;
evm_status = evm_verify_current_integrity(dentry);
if ((evm_status == INTEGRITY_PASS) ||
(evm_status == INTEGRITY_NOXATTRS))
return 0;
goto out;
}
evm_status = evm_verify_current_integrity(dentry);
if (evm_status == INTEGRITY_NOXATTRS) {
struct integrity_iint_cache *iint;
iint = integrity_iint_find(dentry->d_inode);
if (iint && (iint->flags & IMA_NEW_FILE))
return 0;
}
out:
if (evm_status != INTEGRITY_PASS)
integrity_audit_msg(AUDIT_INTEGRITY_METADATA, dentry->d_inode,
dentry->d_name.name, "appraise_metadata",
integrity_status_msg[evm_status],
-EPERM, 0);
return evm_status == INTEGRITY_PASS ? 0 : -EPERM;
}
/**
* evm_inode_setxattr - protect the EVM extended attribute
* @dentry: pointer to the affected dentry
* @xattr_name: pointer to the affected extended attribute name
* @xattr_value: pointer to the new extended attribute value
* @xattr_value_len: pointer to the new extended attribute value length
*
* Before allowing the 'security.evm' protected xattr to be updated,
* verify the existing value is valid. As only the kernel should have
* access to the EVM encrypted key needed to calculate the HMAC, prevent
* userspace from writing HMAC value. Writing 'security.evm' requires
* requires CAP_SYS_ADMIN privileges.
*/
int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name,
const void *xattr_value, size_t xattr_value_len)
{
const struct evm_ima_xattr_data *xattr_data = xattr_value;
if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) {
if (!xattr_value_len)
return -EINVAL;
if (xattr_data->type != EVM_IMA_XATTR_DIGSIG)
return -EPERM;
}
return evm_protect_xattr(dentry, xattr_name, xattr_value,
xattr_value_len);
}
/**
* evm_inode_removexattr - protect the EVM extended attribute
* @dentry: pointer to the affected dentry
* @xattr_name: pointer to the affected extended attribute name
*
* Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that
* the current value is valid.
*/
int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name)
{
return evm_protect_xattr(dentry, xattr_name, NULL, 0);
}
/**
* evm_inode_post_setxattr - update 'security.evm' to reflect the changes
* @dentry: pointer to the affected dentry
* @xattr_name: pointer to the affected extended attribute name
* @xattr_value: pointer to the new extended attribute value
* @xattr_value_len: pointer to the new extended attribute value length
*
* Update the HMAC stored in 'security.evm' to reflect the change.
*
* No need to take the i_mutex lock here, as this function is called from
* __vfs_setxattr_noperm(). The caller of which has taken the inode's
* i_mutex lock.
*/
void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name,
const void *xattr_value, size_t xattr_value_len)
{
if (!evm_initialized || (!evm_protected_xattr(xattr_name)
&& !posix_xattr_acl(xattr_name)))
return;
evm_update_evmxattr(dentry, xattr_name, xattr_value, xattr_value_len);
}
/**
* evm_inode_post_removexattr - update 'security.evm' after removing the xattr
* @dentry: pointer to the affected dentry
* @xattr_name: pointer to the affected extended attribute name
*
* Update the HMAC stored in 'security.evm' to reflect removal of the xattr.
*/
void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name)
{
struct inode *inode = dentry->d_inode;
if (!evm_initialized || !evm_protected_xattr(xattr_name))
return;
mutex_lock(&inode->i_mutex);
evm_update_evmxattr(dentry, xattr_name, NULL, 0);
mutex_unlock(&inode->i_mutex);
}
/**
* evm_inode_setattr - prevent updating an invalid EVM extended attribute
* @dentry: pointer to the affected dentry
*/
int evm_inode_setattr(struct dentry *dentry, struct iattr *attr)
{
unsigned int ia_valid = attr->ia_valid;
enum integrity_status evm_status;
if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)))
return 0;
evm_status = evm_verify_current_integrity(dentry);
if ((evm_status == INTEGRITY_PASS) ||
(evm_status == INTEGRITY_NOXATTRS))
return 0;
integrity_audit_msg(AUDIT_INTEGRITY_METADATA, dentry->d_inode,
dentry->d_name.name, "appraise_metadata",
integrity_status_msg[evm_status], -EPERM, 0);
return -EPERM;
}
/**
* evm_inode_post_setattr - update 'security.evm' after modifying metadata
* @dentry: pointer to the affected dentry
* @ia_valid: for the UID and GID status
*
* For now, update the HMAC stored in 'security.evm' to reflect UID/GID
* changes.
*
* This function is called from notify_change(), which expects the caller
* to lock the inode's i_mutex.
*/
void evm_inode_post_setattr(struct dentry *dentry, int ia_valid)
{
if (!evm_initialized)
return;
if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))
evm_update_evmxattr(dentry, NULL, NULL, 0);
}
/*
* evm_inode_init_security - initializes security.evm
*/
int evm_inode_init_security(struct inode *inode,
const struct xattr *lsm_xattr,
struct xattr *evm_xattr)
{
struct evm_ima_xattr_data *xattr_data;
int rc;
if (!evm_initialized || !evm_protected_xattr(lsm_xattr->name))
return 0;
xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS);
if (!xattr_data)
return -ENOMEM;
xattr_data->type = EVM_XATTR_HMAC;
rc = evm_init_hmac(inode, lsm_xattr, xattr_data->digest);
if (rc < 0)
goto out;
evm_xattr->value = xattr_data;
evm_xattr->value_len = sizeof(*xattr_data);
evm_xattr->name = XATTR_EVM_SUFFIX;
return 0;
out:
kfree(xattr_data);
return rc;
}
EXPORT_SYMBOL_GPL(evm_inode_init_security);
static int __init init_evm(void)
{
int error;
evm_init_config();
error = evm_init_secfs();
if (error < 0) {
pr_info("Error registering secfs\n");
goto err;
}
return 0;
err:
return error;
}
/*
* evm_display_config - list the EVM protected security extended attributes
*/
static int __init evm_display_config(void)
{
char **xattrname;
for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++)
pr_info("%s\n", *xattrname);
return 0;
}
pure_initcall(evm_display_config);
late_initcall(init_evm);
MODULE_DESCRIPTION("Extended Verification Module");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2011 IBM Corporation
*
* Author:
* Mimi Zohar <zohar@us.ibm.com>
*
* 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/module.h>
#include <linux/xattr.h>
#include <linux/evm.h>
int posix_xattr_acl(const char *xattr)
{
int xattr_len = strlen(xattr);
if ((strlen(XATTR_NAME_POSIX_ACL_ACCESS) == xattr_len)
&& (strncmp(XATTR_NAME_POSIX_ACL_ACCESS, xattr, xattr_len) == 0))
return 1;
if ((strlen(XATTR_NAME_POSIX_ACL_DEFAULT) == xattr_len)
&& (strncmp(XATTR_NAME_POSIX_ACL_DEFAULT, xattr, xattr_len) == 0))
return 1;
return 0;
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2010 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: evm_secfs.c
* - Used to signal when key is on keyring
* - Get the key and enable EVM
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/uaccess.h>
#include <linux/module.h>
#include "evm.h"
static struct dentry *evm_init_tpm;
/**
* evm_read_key - read() for <securityfs>/evm
*
* @filp: file pointer, not actually used
* @buf: where to put the result
* @count: maximum to send along
* @ppos: where to start
*
* Returns number of bytes read or error code, as appropriate
*/
static ssize_t evm_read_key(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char temp[80];
ssize_t rc;
if (*ppos != 0)
return 0;
sprintf(temp, "%d", evm_initialized);
rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
return rc;
}
/**
* evm_write_key - write() for <securityfs>/evm
* @file: file pointer, not actually used
* @buf: where to get the data from
* @count: bytes sent
* @ppos: where to start
*
* Used to signal that key is on the kernel key ring.
* - get the integrity hmac key from the kernel key ring
* - create list of hmac protected extended attributes
* Returns number of bytes written or error code, as appropriate
*/
static ssize_t evm_write_key(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char temp[80];
int i, error;
if (!capable(CAP_SYS_ADMIN) || evm_initialized)
return -EPERM;
if (count >= sizeof(temp) || count == 0)
return -EINVAL;
if (copy_from_user(temp, buf, count) != 0)
return -EFAULT;
temp[count] = '\0';
if ((sscanf(temp, "%d", &i) != 1) || (i != 1))
return -EINVAL;
error = evm_init_key();
if (!error) {
evm_initialized = 1;
pr_info("initialized\n");
} else
pr_err("initialization failed\n");
return count;
}
static const struct file_operations evm_key_ops = {
.read = evm_read_key,
.write = evm_write_key,
};
int __init evm_init_secfs(void)
{
int error = 0;
evm_init_tpm = securityfs_create_file("evm", S_IRUSR | S_IRGRP,
NULL, NULL, &evm_key_ops);
if (!evm_init_tpm || IS_ERR(evm_init_tpm))
error = -EFAULT;
return error;
}

172
security/integrity/iint.c Normal file
View file

@ -0,0 +1,172 @@
/*
* Copyright (C) 2008 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: integrity_iint.c
* - implements the integrity hooks: integrity_inode_alloc,
* integrity_inode_free
* - cache integrity information associated with an inode
* using a rbtree tree.
*/
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/rbtree.h>
#include "integrity.h"
static struct rb_root integrity_iint_tree = RB_ROOT;
static DEFINE_RWLOCK(integrity_iint_lock);
static struct kmem_cache *iint_cache __read_mostly;
int iint_initialized;
/*
* __integrity_iint_find - return the iint associated with an inode
*/
static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode)
{
struct integrity_iint_cache *iint;
struct rb_node *n = integrity_iint_tree.rb_node;
while (n) {
iint = rb_entry(n, struct integrity_iint_cache, rb_node);
if (inode < iint->inode)
n = n->rb_left;
else if (inode > iint->inode)
n = n->rb_right;
else
break;
}
if (!n)
return NULL;
return iint;
}
/*
* integrity_iint_find - return the iint associated with an inode
*/
struct integrity_iint_cache *integrity_iint_find(struct inode *inode)
{
struct integrity_iint_cache *iint;
if (!IS_IMA(inode))
return NULL;
read_lock(&integrity_iint_lock);
iint = __integrity_iint_find(inode);
read_unlock(&integrity_iint_lock);
return iint;
}
static void iint_free(struct integrity_iint_cache *iint)
{
kfree(iint->ima_hash);
iint->ima_hash = NULL;
iint->version = 0;
iint->flags = 0UL;
iint->ima_file_status = INTEGRITY_UNKNOWN;
iint->ima_mmap_status = INTEGRITY_UNKNOWN;
iint->ima_bprm_status = INTEGRITY_UNKNOWN;
iint->ima_module_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN;
kmem_cache_free(iint_cache, iint);
}
/**
* integrity_inode_get - find or allocate an iint associated with an inode
* @inode: pointer to the inode
* @return: allocated iint
*
* Caller must lock i_mutex
*/
struct integrity_iint_cache *integrity_inode_get(struct inode *inode)
{
struct rb_node **p;
struct rb_node *node, *parent = NULL;
struct integrity_iint_cache *iint, *test_iint;
iint = integrity_iint_find(inode);
if (iint)
return iint;
iint = kmem_cache_alloc(iint_cache, GFP_NOFS);
if (!iint)
return NULL;
write_lock(&integrity_iint_lock);
p = &integrity_iint_tree.rb_node;
while (*p) {
parent = *p;
test_iint = rb_entry(parent, struct integrity_iint_cache,
rb_node);
if (inode < test_iint->inode)
p = &(*p)->rb_left;
else
p = &(*p)->rb_right;
}
iint->inode = inode;
node = &iint->rb_node;
inode->i_flags |= S_IMA;
rb_link_node(node, parent, p);
rb_insert_color(node, &integrity_iint_tree);
write_unlock(&integrity_iint_lock);
return iint;
}
/**
* integrity_inode_free - called on security_inode_free
* @inode: pointer to the inode
*
* Free the integrity information(iint) associated with an inode.
*/
void integrity_inode_free(struct inode *inode)
{
struct integrity_iint_cache *iint;
if (!IS_IMA(inode))
return;
write_lock(&integrity_iint_lock);
iint = __integrity_iint_find(inode);
rb_erase(&iint->rb_node, &integrity_iint_tree);
write_unlock(&integrity_iint_lock);
iint_free(iint);
}
static void init_once(void *foo)
{
struct integrity_iint_cache *iint = foo;
memset(iint, 0, sizeof(*iint));
iint->version = 0;
iint->flags = 0UL;
iint->ima_file_status = INTEGRITY_UNKNOWN;
iint->ima_mmap_status = INTEGRITY_UNKNOWN;
iint->ima_bprm_status = INTEGRITY_UNKNOWN;
iint->ima_module_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN;
}
static int __init integrity_iintcache_init(void)
{
iint_cache =
kmem_cache_create("iint_cache", sizeof(struct integrity_iint_cache),
0, SLAB_PANIC, init_once);
iint_initialized = 1;
return 0;
}
security_initcall(integrity_iintcache_init);

View file

@ -0,0 +1,133 @@
# IBM Integrity Measurement Architecture
#
config IMA
bool "Integrity Measurement Architecture(IMA)"
select SECURITYFS
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_MD5
select CRYPTO_SHA1
select CRYPTO_HASH_INFO
select TCG_TPM if HAS_IOMEM && !UML
select TCG_TIS if TCG_TPM && X86
select TCG_IBMVTPM if TCG_TPM && PPC64
help
The Trusted Computing Group(TCG) runtime Integrity
Measurement Architecture(IMA) maintains a list of hash
values of executables and other sensitive system files,
as they are read or executed. If an attacker manages
to change the contents of an important system file
being measured, we can tell.
If your system has a TPM chip, then IMA also maintains
an aggregate integrity value over this list inside the
TPM hardware, so that the TPM can prove to a third party
whether or not critical system files have been modified.
Read <http://www.usenix.org/events/sec04/tech/sailer.html>
to learn more about IMA.
If unsure, say N.
config IMA_MEASURE_PCR_IDX
int
depends on IMA
range 8 14
default 10
help
IMA_MEASURE_PCR_IDX determines the TPM PCR register index
that IMA uses to maintain the integrity aggregate of the
measurement list. If unsure, use the default 10.
config IMA_LSM_RULES
bool
depends on IMA && AUDIT && (SECURITY_SELINUX || SECURITY_SMACK)
default y
help
Disabling this option will disregard LSM based policy rules.
choice
prompt "Default template"
default IMA_NG_TEMPLATE
depends on IMA
help
Select the default IMA measurement template.
The original 'ima' measurement list template contains a
hash, defined as 20 bytes, and a null terminated pathname,
limited to 255 characters. The 'ima-ng' measurement list
template permits both larger hash digests and longer
pathnames.
config IMA_TEMPLATE
bool "ima"
config IMA_NG_TEMPLATE
bool "ima-ng (default)"
config IMA_SIG_TEMPLATE
bool "ima-sig"
endchoice
config IMA_DEFAULT_TEMPLATE
string
depends on IMA
default "ima" if IMA_TEMPLATE
default "ima-ng" if IMA_NG_TEMPLATE
default "ima-sig" if IMA_SIG_TEMPLATE
choice
prompt "Default integrity hash algorithm"
default IMA_DEFAULT_HASH_SHA1
depends on IMA
help
Select the default hash algorithm used for the measurement
list, integrity appraisal and audit log. The compiled default
hash algorithm can be overwritten using the kernel command
line 'ima_hash=' option.
config IMA_DEFAULT_HASH_SHA1
bool "SHA1 (default)"
depends on CRYPTO_SHA1
config IMA_DEFAULT_HASH_SHA256
bool "SHA256"
depends on CRYPTO_SHA256 && !IMA_TEMPLATE
config IMA_DEFAULT_HASH_SHA512
bool "SHA512"
depends on CRYPTO_SHA512 && !IMA_TEMPLATE
config IMA_DEFAULT_HASH_WP512
bool "WP512"
depends on CRYPTO_WP512 && !IMA_TEMPLATE
endchoice
config IMA_DEFAULT_HASH
string
depends on IMA
default "sha1" if IMA_DEFAULT_HASH_SHA1
default "sha256" if IMA_DEFAULT_HASH_SHA256
default "sha512" if IMA_DEFAULT_HASH_SHA512
default "wp512" if IMA_DEFAULT_HASH_WP512
config IMA_APPRAISE
bool "Appraise integrity measurements"
depends on IMA
default n
help
This option enables local measurement integrity appraisal.
It requires the system to be labeled with a security extended
attribute containing the file hash measurement. To protect
the security extended attributes from offline attack, enable
and configure EVM.
For more information on integrity appraisal refer to:
<http://linux-ima.sourceforge.net>
If unsure, say N.
config IMA_TRUSTED_KEYRING
bool "Require all keys on the .ima keyring be signed"
depends on IMA_APPRAISE && SYSTEM_TRUSTED_KEYRING
depends on INTEGRITY_ASYMMETRIC_KEYS
select KEYS_DEBUG_PROC_KEYS
default y
help
This option requires that all keys added to the .ima
keyring be signed by a key on the system trusted keyring.

View file

@ -0,0 +1,10 @@
#
# Makefile for building Trusted Computing Group's(TCG) runtime Integrity
# Measurement Architecture(IMA).
#
obj-$(CONFIG_IMA) += ima.o
ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \
ima_policy.o ima_template.o ima_template_lib.o
ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o

View file

@ -0,0 +1,259 @@
/*
* Copyright (C) 2005,2006,2007,2008 IBM Corporation
*
* Authors:
* Reiner Sailer <sailer@watson.ibm.com>
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: ima.h
* internal Integrity Measurement Architecture (IMA) definitions
*/
#ifndef __LINUX_IMA_H
#define __LINUX_IMA_H
#include <linux/types.h>
#include <linux/crypto.h>
#include <linux/security.h>
#include <linux/hash.h>
#include <linux/tpm.h>
#include <linux/audit.h>
#include "../integrity.h"
enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_BINARY_NO_FIELD_LEN,
IMA_SHOW_BINARY_OLD_STRING_FMT, IMA_SHOW_ASCII };
enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 };
/* digest size for IMA, fits SHA1 or MD5 */
#define IMA_DIGEST_SIZE SHA1_DIGEST_SIZE
#define IMA_EVENT_NAME_LEN_MAX 255
#define IMA_HASH_BITS 9
#define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS)
#define IMA_TEMPLATE_FIELD_ID_MAX_LEN 16
#define IMA_TEMPLATE_NUM_FIELDS_MAX 15
#define IMA_TEMPLATE_IMA_NAME "ima"
#define IMA_TEMPLATE_IMA_FMT "d|n"
/* current content of the policy */
extern int ima_policy_flag;
/* set during initialization */
extern int ima_initialized;
extern int ima_used_chip;
extern int ima_hash_algo;
extern int ima_appraise;
/* IMA template field data definition */
struct ima_field_data {
u8 *data;
u32 len;
};
/* IMA template field definition */
struct ima_template_field {
const char field_id[IMA_TEMPLATE_FIELD_ID_MAX_LEN];
int (*field_init) (struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, struct ima_field_data *field_data);
void (*field_show) (struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
};
/* IMA template descriptor definition */
struct ima_template_desc {
char *name;
char *fmt;
int num_fields;
struct ima_template_field **fields;
};
struct ima_template_entry {
u8 digest[TPM_DIGEST_SIZE]; /* sha1 or md5 measurement hash */
struct ima_template_desc *template_desc; /* template descriptor */
u32 template_data_len;
struct ima_field_data template_data[0]; /* template related data */
};
struct ima_queue_entry {
struct hlist_node hnext; /* place in hash collision list */
struct list_head later; /* place in ima_measurements list */
struct ima_template_entry *entry;
};
extern struct list_head ima_measurements; /* list of all measurements */
/* Internal IMA function definitions */
int ima_init(void);
int ima_fs_init(void);
int ima_add_template_entry(struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
const unsigned char *filename);
int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash);
int ima_calc_field_array_hash(struct ima_field_data *field_data,
struct ima_template_desc *desc, int num_fields,
struct ima_digest_data *hash);
int __init ima_calc_boot_aggregate(struct ima_digest_data *hash);
void ima_add_violation(struct file *file, const unsigned char *filename,
const char *op, const char *cause);
int ima_init_crypto(void);
void ima_putc(struct seq_file *m, void *data, int datalen);
void ima_print_digest(struct seq_file *m, u8 *digest, int size);
struct ima_template_desc *ima_template_desc_current(void);
int ima_init_template(void);
/*
* used to protect h_table and sha_table
*/
extern spinlock_t ima_queue_lock;
struct ima_h_table {
atomic_long_t len; /* number of stored measurements in the list */
atomic_long_t violations;
struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
};
extern struct ima_h_table ima_htable;
static inline unsigned long ima_hash_key(u8 *digest)
{
return hash_long(*digest, IMA_HASH_BITS);
}
/* LIM API function definitions */
int ima_get_action(struct inode *inode, int mask, int function);
int ima_must_measure(struct inode *inode, int mask, int function);
int ima_collect_measurement(struct integrity_iint_cache *iint,
struct file *file,
struct evm_ima_xattr_data **xattr_value,
int *xattr_len);
void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len);
void ima_audit_measurement(struct integrity_iint_cache *iint,
const unsigned char *filename);
int ima_alloc_init_template(struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, struct ima_template_entry **entry);
int ima_store_template(struct ima_template_entry *entry, int violation,
struct inode *inode, const unsigned char *filename);
void ima_free_template_entry(struct ima_template_entry *entry);
const char *ima_d_path(struct path *path, char **pathbuf);
/* IMA policy related functions */
enum ima_hooks { FILE_CHECK = 1, MMAP_CHECK, BPRM_CHECK, MODULE_CHECK, FIRMWARE_CHECK, POST_SETATTR };
int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask,
int flags);
void ima_init_policy(void);
void ima_update_policy(void);
void ima_update_policy_flag(void);
ssize_t ima_parse_add_rule(char *);
void ima_delete_rules(void);
/* Appraise integrity measurements */
#define IMA_APPRAISE_ENFORCE 0x01
#define IMA_APPRAISE_FIX 0x02
#define IMA_APPRAISE_LOG 0x04
#define IMA_APPRAISE_MODULES 0x08
#define IMA_APPRAISE_FIRMWARE 0x10
#ifdef CONFIG_IMA_APPRAISE
int ima_appraise_measurement(int func, struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, int opened);
int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func);
void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file);
enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint,
int func);
void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_digest_data *hash);
int ima_read_xattr(struct dentry *dentry,
struct evm_ima_xattr_data **xattr_value);
#else
static inline int ima_appraise_measurement(int func,
struct integrity_iint_cache *iint,
struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, int opened)
{
return INTEGRITY_UNKNOWN;
}
static inline int ima_must_appraise(struct inode *inode, int mask,
enum ima_hooks func)
{
return 0;
}
static inline void ima_update_xattr(struct integrity_iint_cache *iint,
struct file *file)
{
}
static inline enum integrity_status ima_get_cache_status(struct integrity_iint_cache
*iint, int func)
{
return INTEGRITY_UNKNOWN;
}
static inline void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value,
int xattr_len,
struct ima_digest_data *hash)
{
}
static inline int ima_read_xattr(struct dentry *dentry,
struct evm_ima_xattr_data **xattr_value)
{
return 0;
}
#endif
/* LSM based policy rules require audit */
#ifdef CONFIG_IMA_LSM_RULES
#define security_filter_rule_init security_audit_rule_init
#define security_filter_rule_match security_audit_rule_match
#else
static inline int security_filter_rule_init(u32 field, u32 op, char *rulestr,
void **lsmrule)
{
return -EINVAL;
}
static inline int security_filter_rule_match(u32 secid, u32 field, u32 op,
void *lsmrule,
struct audit_context *actx)
{
return -EINVAL;
}
#endif /* CONFIG_IMA_LSM_RULES */
#ifdef CONFIG_IMA_TRUSTED_KEYRING
static inline int ima_init_keyring(const unsigned int id)
{
return integrity_init_keyring(id);
}
#else
static inline int ima_init_keyring(const unsigned int id)
{
return 0;
}
#endif /* CONFIG_IMA_TRUSTED_KEYRING */
#endif

View file

@ -0,0 +1,338 @@
/*
* Copyright (C) 2008 IBM Corporation
*
* Author: Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: ima_api.c
* Implements must_appraise_or_measure, collect_measurement,
* appraise_measurement, store_measurement and store_template.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/xattr.h>
#include <linux/evm.h>
#include <crypto/hash_info.h>
#include "ima.h"
/*
* ima_free_template_entry - free an existing template entry
*/
void ima_free_template_entry(struct ima_template_entry *entry)
{
int i;
for (i = 0; i < entry->template_desc->num_fields; i++)
kfree(entry->template_data[i].data);
kfree(entry);
}
/*
* ima_alloc_init_template - create and initialize a new template entry
*/
int ima_alloc_init_template(struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, struct ima_template_entry **entry)
{
struct ima_template_desc *template_desc = ima_template_desc_current();
int i, result = 0;
*entry = kzalloc(sizeof(**entry) + template_desc->num_fields *
sizeof(struct ima_field_data), GFP_NOFS);
if (!*entry)
return -ENOMEM;
(*entry)->template_desc = template_desc;
for (i = 0; i < template_desc->num_fields; i++) {
struct ima_template_field *field = template_desc->fields[i];
u32 len;
result = field->field_init(iint, file, filename,
xattr_value, xattr_len,
&((*entry)->template_data[i]));
if (result != 0)
goto out;
len = (*entry)->template_data[i].len;
(*entry)->template_data_len += sizeof(len);
(*entry)->template_data_len += len;
}
return 0;
out:
ima_free_template_entry(*entry);
*entry = NULL;
return result;
}
/*
* ima_store_template - store ima template measurements
*
* Calculate the hash of a template entry, add the template entry
* to an ordered list of measurement entries maintained inside the kernel,
* and also update the aggregate integrity value (maintained inside the
* configured TPM PCR) over the hashes of the current list of measurement
* entries.
*
* Applications retrieve the current kernel-held measurement list through
* the securityfs entries in /sys/kernel/security/ima. The signed aggregate
* TPM PCR (called quote) can be retrieved using a TPM user space library
* and is used to validate the measurement list.
*
* Returns 0 on success, error code otherwise
*/
int ima_store_template(struct ima_template_entry *entry,
int violation, struct inode *inode,
const unsigned char *filename)
{
static const char op[] = "add_template_measure";
static const char audit_cause[] = "hashing_error";
char *template_name = entry->template_desc->name;
int result;
struct {
struct ima_digest_data hdr;
char digest[TPM_DIGEST_SIZE];
} hash;
if (!violation) {
int num_fields = entry->template_desc->num_fields;
/* this function uses default algo */
hash.hdr.algo = HASH_ALGO_SHA1;
result = ima_calc_field_array_hash(&entry->template_data[0],
entry->template_desc,
num_fields, &hash.hdr);
if (result < 0) {
integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode,
template_name, op,
audit_cause, result, 0);
return result;
}
memcpy(entry->digest, hash.hdr.digest, hash.hdr.length);
}
result = ima_add_template_entry(entry, violation, op, inode, filename);
return result;
}
/*
* ima_add_violation - add violation to measurement list.
*
* Violations are flagged in the measurement list with zero hash values.
* By extending the PCR with 0xFF's instead of with zeroes, the PCR
* value is invalidated.
*/
void ima_add_violation(struct file *file, const unsigned char *filename,
const char *op, const char *cause)
{
struct ima_template_entry *entry;
struct inode *inode = file_inode(file);
int violation = 1;
int result;
/* can overflow, only indicator */
atomic_long_inc(&ima_htable.violations);
result = ima_alloc_init_template(NULL, file, filename,
NULL, 0, &entry);
if (result < 0) {
result = -ENOMEM;
goto err_out;
}
result = ima_store_template(entry, violation, inode, filename);
if (result < 0)
ima_free_template_entry(entry);
err_out:
integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
op, cause, result, 0);
}
/**
* ima_get_action - appraise & measure decision based on policy.
* @inode: pointer to inode to measure
* @mask: contains the permission mask (MAY_READ, MAY_WRITE, MAY_EXECUTE)
* @function: calling function (FILE_CHECK, BPRM_CHECK, MMAP_CHECK, MODULE_CHECK)
*
* The policy is defined in terms of keypairs:
* subj=, obj=, type=, func=, mask=, fsmagic=
* subj,obj, and type: are LSM specific.
* func: FILE_CHECK | BPRM_CHECK | MMAP_CHECK | MODULE_CHECK
* mask: contains the permission mask
* fsmagic: hex value
*
* Returns IMA_MEASURE, IMA_APPRAISE mask.
*
*/
int ima_get_action(struct inode *inode, int mask, int function)
{
int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE;
if (!ima_appraise)
flags &= ~IMA_APPRAISE;
return ima_match_policy(inode, function, mask, flags);
}
/*
* ima_collect_measurement - collect file measurement
*
* Calculate the file hash, if it doesn't already exist,
* storing the measurement and i_version in the iint.
*
* Must be called with iint->mutex held.
*
* Return 0 on success, error code otherwise
*/
int ima_collect_measurement(struct integrity_iint_cache *iint,
struct file *file,
struct evm_ima_xattr_data **xattr_value,
int *xattr_len)
{
const char *audit_cause = "failed";
struct inode *inode = file_inode(file);
const char *filename = file->f_dentry->d_name.name;
int result = 0;
struct {
struct ima_digest_data hdr;
char digest[IMA_MAX_DIGEST_SIZE];
} hash;
if (xattr_value)
*xattr_len = ima_read_xattr(file->f_dentry, xattr_value);
if (!(iint->flags & IMA_COLLECTED)) {
u64 i_version = file_inode(file)->i_version;
if (file->f_flags & O_DIRECT) {
audit_cause = "failed(directio)";
result = -EACCES;
goto out;
}
/* use default hash algorithm */
hash.hdr.algo = ima_hash_algo;
if (xattr_value)
ima_get_hash_algo(*xattr_value, *xattr_len, &hash.hdr);
result = ima_calc_file_hash(file, &hash.hdr);
if (!result) {
int length = sizeof(hash.hdr) + hash.hdr.length;
void *tmpbuf = krealloc(iint->ima_hash, length,
GFP_NOFS);
if (tmpbuf) {
iint->ima_hash = tmpbuf;
memcpy(iint->ima_hash, &hash, length);
iint->version = i_version;
iint->flags |= IMA_COLLECTED;
} else
result = -ENOMEM;
}
}
out:
if (result)
integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode,
filename, "collect_data", audit_cause,
result, 0);
return result;
}
/*
* ima_store_measurement - store file measurement
*
* Create an "ima" template and then store the template by calling
* ima_store_template.
*
* We only get here if the inode has not already been measured,
* but the measurement could already exist:
* - multiple copies of the same file on either the same or
* different filesystems.
* - the inode was previously flushed as well as the iint info,
* containing the hashing info.
*
* Must be called with iint->mutex held.
*/
void ima_store_measurement(struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len)
{
static const char op[] = "add_template_measure";
static const char audit_cause[] = "ENOMEM";
int result = -ENOMEM;
struct inode *inode = file_inode(file);
struct ima_template_entry *entry;
int violation = 0;
if (iint->flags & IMA_MEASURED)
return;
result = ima_alloc_init_template(iint, file, filename,
xattr_value, xattr_len, &entry);
if (result < 0) {
integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
op, audit_cause, result, 0);
return;
}
result = ima_store_template(entry, violation, inode, filename);
if (!result || result == -EEXIST)
iint->flags |= IMA_MEASURED;
if (result < 0)
ima_free_template_entry(entry);
}
void ima_audit_measurement(struct integrity_iint_cache *iint,
const unsigned char *filename)
{
struct audit_buffer *ab;
char hash[(iint->ima_hash->length * 2) + 1];
const char *algo_name = hash_algo_name[iint->ima_hash->algo];
char algo_hash[sizeof(hash) + strlen(algo_name) + 2];
int i;
if (iint->flags & IMA_AUDITED)
return;
for (i = 0; i < iint->ima_hash->length; i++)
hex_byte_pack(hash + (i * 2), iint->ima_hash->digest[i]);
hash[i * 2] = '\0';
ab = audit_log_start(current->audit_context, GFP_KERNEL,
AUDIT_INTEGRITY_RULE);
if (!ab)
return;
audit_log_format(ab, "file=");
audit_log_untrustedstring(ab, filename);
audit_log_format(ab, " hash=");
snprintf(algo_hash, sizeof(algo_hash), "%s:%s", algo_name, hash);
audit_log_untrustedstring(ab, algo_hash);
audit_log_task_info(ab, current);
audit_log_end(ab);
iint->flags |= IMA_AUDITED;
}
const char *ima_d_path(struct path *path, char **pathbuf)
{
char *pathname = NULL;
*pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
if (*pathbuf) {
pathname = d_absolute_path(path, *pathbuf, PATH_MAX);
if (IS_ERR(pathname)) {
kfree(*pathbuf);
*pathbuf = NULL;
pathname = NULL;
}
}
return pathname ?: (const char *)path->dentry->d_name.name;
}

View file

@ -0,0 +1,400 @@
/*
* Copyright (C) 2011 IBM Corporation
*
* Author:
* Mimi Zohar <zohar@us.ibm.com>
*
* 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/module.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/xattr.h>
#include <linux/magic.h>
#include <linux/ima.h>
#include <linux/evm.h>
#include <crypto/hash_info.h>
#include "ima.h"
static int __init default_appraise_setup(char *str)
{
if (strncmp(str, "off", 3) == 0)
ima_appraise = 0;
else if (strncmp(str, "log", 3) == 0)
ima_appraise = IMA_APPRAISE_LOG;
else if (strncmp(str, "fix", 3) == 0)
ima_appraise = IMA_APPRAISE_FIX;
return 1;
}
__setup("ima_appraise=", default_appraise_setup);
/*
* ima_must_appraise - set appraise flag
*
* Return 1 to appraise
*/
int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func)
{
if (!ima_appraise)
return 0;
return ima_match_policy(inode, func, mask, IMA_APPRAISE);
}
static int ima_fix_xattr(struct dentry *dentry,
struct integrity_iint_cache *iint)
{
int rc, offset;
u8 algo = iint->ima_hash->algo;
if (algo <= HASH_ALGO_SHA1) {
offset = 1;
iint->ima_hash->xattr.sha1.type = IMA_XATTR_DIGEST;
} else {
offset = 0;
iint->ima_hash->xattr.ng.type = IMA_XATTR_DIGEST_NG;
iint->ima_hash->xattr.ng.algo = algo;
}
rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA,
&iint->ima_hash->xattr.data[offset],
(sizeof(iint->ima_hash->xattr) - offset) +
iint->ima_hash->length, 0);
return rc;
}
/* Return specific func appraised cached result */
enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint,
int func)
{
switch (func) {
case MMAP_CHECK:
return iint->ima_mmap_status;
case BPRM_CHECK:
return iint->ima_bprm_status;
case MODULE_CHECK:
return iint->ima_module_status;
case FIRMWARE_CHECK:
return iint->ima_firmware_status;
case FILE_CHECK:
default:
return iint->ima_file_status;
}
}
static void ima_set_cache_status(struct integrity_iint_cache *iint,
int func, enum integrity_status status)
{
switch (func) {
case MMAP_CHECK:
iint->ima_mmap_status = status;
break;
case BPRM_CHECK:
iint->ima_bprm_status = status;
break;
case MODULE_CHECK:
iint->ima_module_status = status;
break;
case FIRMWARE_CHECK:
iint->ima_firmware_status = status;
break;
case FILE_CHECK:
default:
iint->ima_file_status = status;
break;
}
}
static void ima_cache_flags(struct integrity_iint_cache *iint, int func)
{
switch (func) {
case MMAP_CHECK:
iint->flags |= (IMA_MMAP_APPRAISED | IMA_APPRAISED);
break;
case BPRM_CHECK:
iint->flags |= (IMA_BPRM_APPRAISED | IMA_APPRAISED);
break;
case MODULE_CHECK:
iint->flags |= (IMA_MODULE_APPRAISED | IMA_APPRAISED);
break;
case FIRMWARE_CHECK:
iint->flags |= (IMA_FIRMWARE_APPRAISED | IMA_APPRAISED);
break;
case FILE_CHECK:
default:
iint->flags |= (IMA_FILE_APPRAISED | IMA_APPRAISED);
break;
}
}
void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_digest_data *hash)
{
struct signature_v2_hdr *sig;
if (!xattr_value || xattr_len < 2)
return;
switch (xattr_value->type) {
case EVM_IMA_XATTR_DIGSIG:
sig = (typeof(sig))xattr_value;
if (sig->version != 2 || xattr_len <= sizeof(*sig))
return;
hash->algo = sig->hash_algo;
break;
case IMA_XATTR_DIGEST_NG:
hash->algo = xattr_value->digest[0];
break;
case IMA_XATTR_DIGEST:
/* this is for backward compatibility */
if (xattr_len == 21) {
unsigned int zero = 0;
if (!memcmp(&xattr_value->digest[16], &zero, 4))
hash->algo = HASH_ALGO_MD5;
else
hash->algo = HASH_ALGO_SHA1;
} else if (xattr_len == 17)
hash->algo = HASH_ALGO_MD5;
break;
}
}
int ima_read_xattr(struct dentry *dentry,
struct evm_ima_xattr_data **xattr_value)
{
struct inode *inode = dentry->d_inode;
if (!inode->i_op->getxattr)
return 0;
return vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)xattr_value,
0, GFP_NOFS);
}
/*
* ima_appraise_measurement - appraise file measurement
*
* Call evm_verifyxattr() to verify the integrity of 'security.ima'.
* Assuming success, compare the xattr hash with the collected measurement.
*
* Return 0 on success, error code otherwise
*/
int ima_appraise_measurement(int func, struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, int opened)
{
static const char op[] = "appraise_data";
char *cause = "unknown";
struct dentry *dentry = file->f_dentry;
struct inode *inode = dentry->d_inode;
enum integrity_status status = INTEGRITY_UNKNOWN;
int rc = xattr_len, hash_start = 0;
if (!inode->i_op->getxattr)
return INTEGRITY_UNKNOWN;
if (rc <= 0) {
if (rc && rc != -ENODATA)
goto out;
cause = "missing-hash";
status = INTEGRITY_NOLABEL;
if (opened & FILE_CREATED) {
iint->flags |= IMA_NEW_FILE;
status = INTEGRITY_PASS;
}
goto out;
}
status = evm_verifyxattr(dentry, XATTR_NAME_IMA, xattr_value, rc, iint);
if ((status != INTEGRITY_PASS) && (status != INTEGRITY_UNKNOWN)) {
if ((status == INTEGRITY_NOLABEL)
|| (status == INTEGRITY_NOXATTRS))
cause = "missing-HMAC";
else if (status == INTEGRITY_FAIL)
cause = "invalid-HMAC";
goto out;
}
switch (xattr_value->type) {
case IMA_XATTR_DIGEST_NG:
/* first byte contains algorithm id */
hash_start = 1;
case IMA_XATTR_DIGEST:
if (iint->flags & IMA_DIGSIG_REQUIRED) {
cause = "IMA-signature-required";
status = INTEGRITY_FAIL;
break;
}
if (xattr_len - sizeof(xattr_value->type) - hash_start >=
iint->ima_hash->length)
/* xattr length may be longer. md5 hash in previous
version occupied 20 bytes in xattr, instead of 16
*/
rc = memcmp(&xattr_value->digest[hash_start],
iint->ima_hash->digest,
iint->ima_hash->length);
else
rc = -EINVAL;
if (rc) {
cause = "invalid-hash";
status = INTEGRITY_FAIL;
break;
}
status = INTEGRITY_PASS;
break;
case EVM_IMA_XATTR_DIGSIG:
iint->flags |= IMA_DIGSIG;
rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA,
(const char *)xattr_value, rc,
iint->ima_hash->digest,
iint->ima_hash->length);
if (rc == -EOPNOTSUPP) {
status = INTEGRITY_UNKNOWN;
} else if (rc) {
cause = "invalid-signature";
status = INTEGRITY_FAIL;
} else {
status = INTEGRITY_PASS;
}
break;
default:
status = INTEGRITY_UNKNOWN;
cause = "unknown-ima-data";
break;
}
out:
if (status != INTEGRITY_PASS) {
if ((ima_appraise & IMA_APPRAISE_FIX) &&
(!xattr_value ||
xattr_value->type != EVM_IMA_XATTR_DIGSIG)) {
if (!ima_fix_xattr(dentry, iint))
status = INTEGRITY_PASS;
}
integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename,
op, cause, rc, 0);
} else {
ima_cache_flags(iint, func);
}
ima_set_cache_status(iint, func, status);
return status;
}
/*
* ima_update_xattr - update 'security.ima' hash value
*/
void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file)
{
struct dentry *dentry = file->f_dentry;
int rc = 0;
/* do not collect and update hash for digital signatures */
if (iint->flags & IMA_DIGSIG)
return;
rc = ima_collect_measurement(iint, file, NULL, NULL);
if (rc < 0)
return;
ima_fix_xattr(dentry, iint);
}
/**
* ima_inode_post_setattr - reflect file metadata changes
* @dentry: pointer to the affected dentry
*
* Changes to a dentry's metadata might result in needing to appraise.
*
* This function is called from notify_change(), which expects the caller
* to lock the inode's i_mutex.
*/
void ima_inode_post_setattr(struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
struct integrity_iint_cache *iint;
int must_appraise, rc;
if (!(ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode)
|| !inode->i_op->removexattr)
return;
must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR);
iint = integrity_iint_find(inode);
if (iint) {
iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED |
IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK |
IMA_ACTION_FLAGS);
if (must_appraise)
iint->flags |= IMA_APPRAISE;
}
if (!must_appraise)
rc = inode->i_op->removexattr(dentry, XATTR_NAME_IMA);
return;
}
/*
* ima_protect_xattr - protect 'security.ima'
*
* Ensure that not just anyone can modify or remove 'security.ima'.
*/
static int ima_protect_xattr(struct dentry *dentry, const char *xattr_name,
const void *xattr_value, size_t xattr_value_len)
{
if (strcmp(xattr_name, XATTR_NAME_IMA) == 0) {
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
return 1;
}
return 0;
}
static void ima_reset_appraise_flags(struct inode *inode, int digsig)
{
struct integrity_iint_cache *iint;
if (!(ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode))
return;
iint = integrity_iint_find(inode);
if (!iint)
return;
iint->flags &= ~IMA_DONE_MASK;
if (digsig)
iint->flags |= IMA_DIGSIG;
return;
}
int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
const void *xattr_value, size_t xattr_value_len)
{
const struct evm_ima_xattr_data *xvalue = xattr_value;
int result;
result = ima_protect_xattr(dentry, xattr_name, xattr_value,
xattr_value_len);
if (result == 1) {
if (!xattr_value_len || (xvalue->type >= IMA_XATTR_LAST))
return -EINVAL;
ima_reset_appraise_flags(dentry->d_inode,
(xvalue->type == EVM_IMA_XATTR_DIGSIG) ? 1 : 0);
result = 0;
}
return result;
}
int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name)
{
int result;
result = ima_protect_xattr(dentry, xattr_name, NULL, 0);
if (result == 1) {
ima_reset_appraise_flags(dentry->d_inode, 0);
result = 0;
}
return result;
}

View file

@ -0,0 +1,603 @@
/*
* Copyright (C) 2005,2006,2007,2008 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
* Kylene Hall <kjhall@us.ibm.com>
*
* 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.
*
* File: ima_crypto.c
* Calculates md5/sha1 file hash, template hash, boot-aggreate hash
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/ratelimit.h>
#include <linux/file.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <crypto/hash.h>
#include <crypto/hash_info.h>
#include "ima.h"
struct ahash_completion {
struct completion completion;
int err;
};
/* minimum file size for ahash use */
static unsigned long ima_ahash_minsize;
module_param_named(ahash_minsize, ima_ahash_minsize, ulong, 0644);
MODULE_PARM_DESC(ahash_minsize, "Minimum file size for ahash use");
/* default is 0 - 1 page. */
static int ima_maxorder;
static unsigned int ima_bufsize = PAGE_SIZE;
static int param_set_bufsize(const char *val, const struct kernel_param *kp)
{
unsigned long long size;
int order;
size = memparse(val, NULL);
order = get_order(size);
if (order >= MAX_ORDER)
return -EINVAL;
ima_maxorder = order;
ima_bufsize = PAGE_SIZE << order;
return 0;
}
static struct kernel_param_ops param_ops_bufsize = {
.set = param_set_bufsize,
.get = param_get_uint,
};
#define param_check_bufsize(name, p) __param_check(name, p, unsigned int)
module_param_named(ahash_bufsize, ima_bufsize, bufsize, 0644);
MODULE_PARM_DESC(ahash_bufsize, "Maximum ahash buffer size");
static struct crypto_shash *ima_shash_tfm;
static struct crypto_ahash *ima_ahash_tfm;
/**
* ima_kernel_read - read file content
*
* This is a function for reading file content instead of kernel_read().
* It does not perform locking checks to ensure it cannot be blocked.
* It does not perform security checks because it is irrelevant for IMA.
*
*/
static int ima_kernel_read(struct file *file, loff_t offset,
char *addr, unsigned long count)
{
mm_segment_t old_fs;
char __user *buf = addr;
ssize_t ret = -EINVAL;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
old_fs = get_fs();
set_fs(get_ds());
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, &offset);
else if (file->f_op->aio_read)
ret = do_sync_read(file, buf, count, &offset);
else if (file->f_op->read_iter)
ret = new_sync_read(file, buf, count, &offset);
set_fs(old_fs);
return ret;
}
int __init ima_init_crypto(void)
{
long rc;
ima_shash_tfm = crypto_alloc_shash(hash_algo_name[ima_hash_algo], 0, 0);
if (IS_ERR(ima_shash_tfm)) {
rc = PTR_ERR(ima_shash_tfm);
pr_err("Can not allocate %s (reason: %ld)\n",
hash_algo_name[ima_hash_algo], rc);
return rc;
}
return 0;
}
static struct crypto_shash *ima_alloc_tfm(enum hash_algo algo)
{
struct crypto_shash *tfm = ima_shash_tfm;
int rc;
if (algo < 0 || algo >= HASH_ALGO__LAST)
algo = ima_hash_algo;
if (algo != ima_hash_algo) {
tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0);
if (IS_ERR(tfm)) {
rc = PTR_ERR(tfm);
pr_err("Can not allocate %s (reason: %d)\n",
hash_algo_name[algo], rc);
}
}
return tfm;
}
static void ima_free_tfm(struct crypto_shash *tfm)
{
if (tfm != ima_shash_tfm)
crypto_free_shash(tfm);
}
/**
* ima_alloc_pages() - Allocate contiguous pages.
* @max_size: Maximum amount of memory to allocate.
* @allocated_size: Returned size of actual allocation.
* @last_warn: Should the min_size allocation warn or not.
*
* Tries to do opportunistic allocation for memory first trying to allocate
* max_size amount of memory and then splitting that until zero order is
* reached. Allocation is tried without generating allocation warnings unless
* last_warn is set. Last_warn set affects only last allocation of zero order.
*
* By default, ima_maxorder is 0 and it is equivalent to kmalloc(GFP_KERNEL)
*
* Return pointer to allocated memory, or NULL on failure.
*/
static void *ima_alloc_pages(loff_t max_size, size_t *allocated_size,
int last_warn)
{
void *ptr;
int order = ima_maxorder;
gfp_t gfp_mask = __GFP_WAIT | __GFP_NOWARN | __GFP_NORETRY;
if (order)
order = min(get_order(max_size), order);
for (; order; order--) {
ptr = (void *)__get_free_pages(gfp_mask, order);
if (ptr) {
*allocated_size = PAGE_SIZE << order;
return ptr;
}
}
/* order is zero - one page */
gfp_mask = GFP_KERNEL;
if (!last_warn)
gfp_mask |= __GFP_NOWARN;
ptr = (void *)__get_free_pages(gfp_mask, 0);
if (ptr) {
*allocated_size = PAGE_SIZE;
return ptr;
}
*allocated_size = 0;
return NULL;
}
/**
* ima_free_pages() - Free pages allocated by ima_alloc_pages().
* @ptr: Pointer to allocated pages.
* @size: Size of allocated buffer.
*/
static void ima_free_pages(void *ptr, size_t size)
{
if (!ptr)
return;
free_pages((unsigned long)ptr, get_order(size));
}
static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo)
{
struct crypto_ahash *tfm = ima_ahash_tfm;
int rc;
if (algo < 0 || algo >= HASH_ALGO__LAST)
algo = ima_hash_algo;
if (algo != ima_hash_algo || !tfm) {
tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0);
if (!IS_ERR(tfm)) {
if (algo == ima_hash_algo)
ima_ahash_tfm = tfm;
} else {
rc = PTR_ERR(tfm);
pr_err("Can not allocate %s (reason: %d)\n",
hash_algo_name[algo], rc);
}
}
return tfm;
}
static void ima_free_atfm(struct crypto_ahash *tfm)
{
if (tfm != ima_ahash_tfm)
crypto_free_ahash(tfm);
}
static void ahash_complete(struct crypto_async_request *req, int err)
{
struct ahash_completion *res = req->data;
if (err == -EINPROGRESS)
return;
res->err = err;
complete(&res->completion);
}
static int ahash_wait(int err, struct ahash_completion *res)
{
switch (err) {
case 0:
break;
case -EINPROGRESS:
case -EBUSY:
wait_for_completion(&res->completion);
reinit_completion(&res->completion);
err = res->err;
/* fall through */
default:
pr_crit_ratelimited("ahash calculation failed: err: %d\n", err);
}
return err;
}
static int ima_calc_file_hash_atfm(struct file *file,
struct ima_digest_data *hash,
struct crypto_ahash *tfm)
{
loff_t i_size, offset;
char *rbuf[2] = { NULL, };
int rc, read = 0, rbuf_len, active = 0, ahash_rc = 0;
struct ahash_request *req;
struct scatterlist sg[1];
struct ahash_completion res;
size_t rbuf_size[2];
hash->length = crypto_ahash_digestsize(tfm);
req = ahash_request_alloc(tfm, GFP_KERNEL);
if (!req)
return -ENOMEM;
init_completion(&res.completion);
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
ahash_complete, &res);
rc = ahash_wait(crypto_ahash_init(req), &res);
if (rc)
goto out1;
i_size = i_size_read(file_inode(file));
if (i_size == 0)
goto out2;
/*
* Try to allocate maximum size of memory.
* Fail if even a single page cannot be allocated.
*/
rbuf[0] = ima_alloc_pages(i_size, &rbuf_size[0], 1);
if (!rbuf[0]) {
rc = -ENOMEM;
goto out1;
}
/* Only allocate one buffer if that is enough. */
if (i_size > rbuf_size[0]) {
/*
* Try to allocate secondary buffer. If that fails fallback to
* using single buffering. Use previous memory allocation size
* as baseline for possible allocation size.
*/
rbuf[1] = ima_alloc_pages(i_size - rbuf_size[0],
&rbuf_size[1], 0);
}
if (!(file->f_mode & FMODE_READ)) {
file->f_mode |= FMODE_READ;
read = 1;
}
for (offset = 0; offset < i_size; offset += rbuf_len) {
if (!rbuf[1] && offset) {
/* Not using two buffers, and it is not the first
* read/request, wait for the completion of the
* previous ahash_update() request.
*/
rc = ahash_wait(ahash_rc, &res);
if (rc)
goto out3;
}
/* read buffer */
rbuf_len = min_t(loff_t, i_size - offset, rbuf_size[active]);
rc = ima_kernel_read(file, offset, rbuf[active], rbuf_len);
if (rc != rbuf_len)
goto out3;
if (rbuf[1] && offset) {
/* Using two buffers, and it is not the first
* read/request, wait for the completion of the
* previous ahash_update() request.
*/
rc = ahash_wait(ahash_rc, &res);
if (rc)
goto out3;
}
sg_init_one(&sg[0], rbuf[active], rbuf_len);
ahash_request_set_crypt(req, sg, NULL, rbuf_len);
ahash_rc = crypto_ahash_update(req);
if (rbuf[1])
active = !active; /* swap buffers, if we use two */
}
/* wait for the last update request to complete */
rc = ahash_wait(ahash_rc, &res);
out3:
if (read)
file->f_mode &= ~FMODE_READ;
ima_free_pages(rbuf[0], rbuf_size[0]);
ima_free_pages(rbuf[1], rbuf_size[1]);
out2:
if (!rc) {
ahash_request_set_crypt(req, NULL, hash->digest, 0);
rc = ahash_wait(crypto_ahash_final(req), &res);
}
out1:
ahash_request_free(req);
return rc;
}
static int ima_calc_file_ahash(struct file *file, struct ima_digest_data *hash)
{
struct crypto_ahash *tfm;
int rc;
tfm = ima_alloc_atfm(hash->algo);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
rc = ima_calc_file_hash_atfm(file, hash, tfm);
ima_free_atfm(tfm);
return rc;
}
static int ima_calc_file_hash_tfm(struct file *file,
struct ima_digest_data *hash,
struct crypto_shash *tfm)
{
loff_t i_size, offset = 0;
char *rbuf;
int rc, read = 0;
SHASH_DESC_ON_STACK(shash, tfm);
shash->tfm = tfm;
shash->flags = 0;
hash->length = crypto_shash_digestsize(tfm);
rc = crypto_shash_init(shash);
if (rc != 0)
return rc;
i_size = i_size_read(file_inode(file));
if (i_size == 0)
goto out;
rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!rbuf)
return -ENOMEM;
if (!(file->f_mode & FMODE_READ)) {
file->f_mode |= FMODE_READ;
read = 1;
}
while (offset < i_size) {
int rbuf_len;
rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE);
if (rbuf_len < 0) {
rc = rbuf_len;
break;
}
if (rbuf_len == 0)
break;
offset += rbuf_len;
rc = crypto_shash_update(shash, rbuf, rbuf_len);
if (rc)
break;
}
if (read)
file->f_mode &= ~FMODE_READ;
kfree(rbuf);
out:
if (!rc)
rc = crypto_shash_final(shash, hash->digest);
return rc;
}
static int ima_calc_file_shash(struct file *file, struct ima_digest_data *hash)
{
struct crypto_shash *tfm;
int rc;
tfm = ima_alloc_tfm(hash->algo);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
rc = ima_calc_file_hash_tfm(file, hash, tfm);
ima_free_tfm(tfm);
return rc;
}
/*
* ima_calc_file_hash - calculate file hash
*
* Asynchronous hash (ahash) allows using HW acceleration for calculating
* a hash. ahash performance varies for different data sizes on different
* crypto accelerators. shash performance might be better for smaller files.
* The 'ima.ahash_minsize' module parameter allows specifying the best
* minimum file size for using ahash on the system.
*
* If the ima.ahash_minsize parameter is not specified, this function uses
* shash for the hash calculation. If ahash fails, it falls back to using
* shash.
*/
int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash)
{
loff_t i_size;
int rc;
i_size = i_size_read(file_inode(file));
if (ima_ahash_minsize && i_size >= ima_ahash_minsize) {
rc = ima_calc_file_ahash(file, hash);
if (!rc)
return 0;
}
return ima_calc_file_shash(file, hash);
}
/*
* Calculate the hash of template data
*/
static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data,
struct ima_template_desc *td,
int num_fields,
struct ima_digest_data *hash,
struct crypto_shash *tfm)
{
SHASH_DESC_ON_STACK(shash, tfm);
int rc, i;
shash->tfm = tfm;
shash->flags = 0;
hash->length = crypto_shash_digestsize(tfm);
rc = crypto_shash_init(shash);
if (rc != 0)
return rc;
for (i = 0; i < num_fields; i++) {
u8 buffer[IMA_EVENT_NAME_LEN_MAX + 1] = { 0 };
u8 *data_to_hash = field_data[i].data;
u32 datalen = field_data[i].len;
if (strcmp(td->name, IMA_TEMPLATE_IMA_NAME) != 0) {
rc = crypto_shash_update(shash,
(const u8 *) &field_data[i].len,
sizeof(field_data[i].len));
if (rc)
break;
} else if (strcmp(td->fields[i]->field_id, "n") == 0) {
memcpy(buffer, data_to_hash, datalen);
data_to_hash = buffer;
datalen = IMA_EVENT_NAME_LEN_MAX + 1;
}
rc = crypto_shash_update(shash, data_to_hash, datalen);
if (rc)
break;
}
if (!rc)
rc = crypto_shash_final(shash, hash->digest);
return rc;
}
int ima_calc_field_array_hash(struct ima_field_data *field_data,
struct ima_template_desc *desc, int num_fields,
struct ima_digest_data *hash)
{
struct crypto_shash *tfm;
int rc;
tfm = ima_alloc_tfm(hash->algo);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
rc = ima_calc_field_array_hash_tfm(field_data, desc, num_fields,
hash, tfm);
ima_free_tfm(tfm);
return rc;
}
static void __init ima_pcrread(int idx, u8 *pcr)
{
if (!ima_used_chip)
return;
if (tpm_pcr_read(TPM_ANY_NUM, idx, pcr) != 0)
pr_err("Error Communicating to TPM chip\n");
}
/*
* Calculate the boot aggregate hash
*/
static int __init ima_calc_boot_aggregate_tfm(char *digest,
struct crypto_shash *tfm)
{
u8 pcr_i[TPM_DIGEST_SIZE];
int rc, i;
SHASH_DESC_ON_STACK(shash, tfm);
shash->tfm = tfm;
shash->flags = 0;
rc = crypto_shash_init(shash);
if (rc != 0)
return rc;
/* cumulative sha1 over tpm registers 0-7 */
for (i = TPM_PCR0; i < TPM_PCR8; i++) {
ima_pcrread(i, pcr_i);
/* now accumulate with current aggregate */
rc = crypto_shash_update(shash, pcr_i, TPM_DIGEST_SIZE);
}
if (!rc)
crypto_shash_final(shash, digest);
return rc;
}
int __init ima_calc_boot_aggregate(struct ima_digest_data *hash)
{
struct crypto_shash *tfm;
int rc;
tfm = ima_alloc_tfm(hash->algo);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
hash->length = crypto_shash_digestsize(tfm);
rc = ima_calc_boot_aggregate_tfm(hash->digest, tfm);
ima_free_tfm(tfm);
return rc;
}

View file

@ -0,0 +1,382 @@
/*
* Copyright (C) 2005,2006,2007,2008 IBM Corporation
*
* Authors:
* Kylene Hall <kjhall@us.ibm.com>
* Reiner Sailer <sailer@us.ibm.com>
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: ima_fs.c
* implemenents security file system for reporting
* current measurement list and IMA statistics
*/
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/rculist.h>
#include <linux/rcupdate.h>
#include <linux/parser.h>
#include "ima.h"
static int valid_policy = 1;
#define TMPBUFLEN 12
static ssize_t ima_show_htable_value(char __user *buf, size_t count,
loff_t *ppos, atomic_long_t *val)
{
char tmpbuf[TMPBUFLEN];
ssize_t len;
len = scnprintf(tmpbuf, TMPBUFLEN, "%li\n", atomic_long_read(val));
return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}
static ssize_t ima_show_htable_violations(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
return ima_show_htable_value(buf, count, ppos, &ima_htable.violations);
}
static const struct file_operations ima_htable_violations_ops = {
.read = ima_show_htable_violations,
.llseek = generic_file_llseek,
};
static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
}
static const struct file_operations ima_measurements_count_ops = {
.read = ima_show_measurements_count,
.llseek = generic_file_llseek,
};
/* returns pointer to hlist_node */
static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
{
loff_t l = *pos;
struct ima_queue_entry *qe;
/* we need a lock since pos could point beyond last element */
rcu_read_lock();
list_for_each_entry_rcu(qe, &ima_measurements, later) {
if (!l--) {
rcu_read_unlock();
return qe;
}
}
rcu_read_unlock();
return NULL;
}
static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
{
struct ima_queue_entry *qe = v;
/* lock protects when reading beyond last element
* against concurrent list-extension
*/
rcu_read_lock();
qe = list_entry_rcu(qe->later.next, struct ima_queue_entry, later);
rcu_read_unlock();
(*pos)++;
return (&qe->later == &ima_measurements) ? NULL : qe;
}
static void ima_measurements_stop(struct seq_file *m, void *v)
{
}
void ima_putc(struct seq_file *m, void *data, int datalen)
{
while (datalen--)
seq_putc(m, *(char *)data++);
}
/* print format:
* 32bit-le=pcr#
* char[20]=template digest
* 32bit-le=template name size
* char[n]=template name
* [eventdata length]
* eventdata[n]=template specific data
*/
static int ima_measurements_show(struct seq_file *m, void *v)
{
/* the list never shrinks, so we don't need a lock here */
struct ima_queue_entry *qe = v;
struct ima_template_entry *e;
int namelen;
u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX;
bool is_ima_template = false;
int i;
/* get entry */
e = qe->entry;
if (e == NULL)
return -1;
/*
* 1st: PCRIndex
* PCR used is always the same (config option) in
* little-endian format
*/
ima_putc(m, &pcr, sizeof(pcr));
/* 2nd: template digest */
ima_putc(m, e->digest, TPM_DIGEST_SIZE);
/* 3rd: template name size */
namelen = strlen(e->template_desc->name);
ima_putc(m, &namelen, sizeof(namelen));
/* 4th: template name */
ima_putc(m, e->template_desc->name, namelen);
/* 5th: template length (except for 'ima' template) */
if (strcmp(e->template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0)
is_ima_template = true;
if (!is_ima_template)
ima_putc(m, &e->template_data_len,
sizeof(e->template_data_len));
/* 6th: template specific data */
for (i = 0; i < e->template_desc->num_fields; i++) {
enum ima_show_type show = IMA_SHOW_BINARY;
struct ima_template_field *field = e->template_desc->fields[i];
if (is_ima_template && strcmp(field->field_id, "d") == 0)
show = IMA_SHOW_BINARY_NO_FIELD_LEN;
if (is_ima_template && strcmp(field->field_id, "n") == 0)
show = IMA_SHOW_BINARY_OLD_STRING_FMT;
field->field_show(m, show, &e->template_data[i]);
}
return 0;
}
static const struct seq_operations ima_measurments_seqops = {
.start = ima_measurements_start,
.next = ima_measurements_next,
.stop = ima_measurements_stop,
.show = ima_measurements_show
};
static int ima_measurements_open(struct inode *inode, struct file *file)
{
return seq_open(file, &ima_measurments_seqops);
}
static const struct file_operations ima_measurements_ops = {
.open = ima_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
void ima_print_digest(struct seq_file *m, u8 *digest, int size)
{
int i;
for (i = 0; i < size; i++)
seq_printf(m, "%02x", *(digest + i));
}
/* print in ascii */
static int ima_ascii_measurements_show(struct seq_file *m, void *v)
{
/* the list never shrinks, so we don't need a lock here */
struct ima_queue_entry *qe = v;
struct ima_template_entry *e;
int i;
/* get entry */
e = qe->entry;
if (e == NULL)
return -1;
/* 1st: PCR used (config option) */
seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX);
/* 2nd: SHA1 template hash */
ima_print_digest(m, e->digest, TPM_DIGEST_SIZE);
/* 3th: template name */
seq_printf(m, " %s", e->template_desc->name);
/* 4th: template specific data */
for (i = 0; i < e->template_desc->num_fields; i++) {
seq_puts(m, " ");
if (e->template_data[i].len == 0)
continue;
e->template_desc->fields[i]->field_show(m, IMA_SHOW_ASCII,
&e->template_data[i]);
}
seq_puts(m, "\n");
return 0;
}
static const struct seq_operations ima_ascii_measurements_seqops = {
.start = ima_measurements_start,
.next = ima_measurements_next,
.stop = ima_measurements_stop,
.show = ima_ascii_measurements_show
};
static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
{
return seq_open(file, &ima_ascii_measurements_seqops);
}
static const struct file_operations ima_ascii_measurements_ops = {
.open = ima_ascii_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static ssize_t ima_write_policy(struct file *file, const char __user *buf,
size_t datalen, loff_t *ppos)
{
char *data = NULL;
ssize_t result;
if (datalen >= PAGE_SIZE)
datalen = PAGE_SIZE - 1;
/* No partial writes. */
result = -EINVAL;
if (*ppos != 0)
goto out;
result = -ENOMEM;
data = kmalloc(datalen + 1, GFP_KERNEL);
if (!data)
goto out;
*(data + datalen) = '\0';
result = -EFAULT;
if (copy_from_user(data, buf, datalen))
goto out;
result = ima_parse_add_rule(data);
out:
if (result < 0)
valid_policy = 0;
kfree(data);
return result;
}
static struct dentry *ima_dir;
static struct dentry *binary_runtime_measurements;
static struct dentry *ascii_runtime_measurements;
static struct dentry *runtime_measurements_count;
static struct dentry *violations;
static struct dentry *ima_policy;
static atomic_t policy_opencount = ATOMIC_INIT(1);
/*
* ima_open_policy: sequentialize access to the policy file
*/
static int ima_open_policy(struct inode *inode, struct file *filp)
{
/* No point in being allowed to open it if you aren't going to write */
if (!(filp->f_flags & O_WRONLY))
return -EACCES;
if (atomic_dec_and_test(&policy_opencount))
return 0;
return -EBUSY;
}
/*
* ima_release_policy - start using the new measure policy rules.
*
* Initially, ima_measure points to the default policy rules, now
* point to the new policy rules, and remove the securityfs policy file,
* assuming a valid policy.
*/
static int ima_release_policy(struct inode *inode, struct file *file)
{
if (!valid_policy) {
ima_delete_rules();
valid_policy = 1;
atomic_set(&policy_opencount, 1);
return 0;
}
ima_update_policy();
securityfs_remove(ima_policy);
ima_policy = NULL;
return 0;
}
static const struct file_operations ima_measure_policy_ops = {
.open = ima_open_policy,
.write = ima_write_policy,
.release = ima_release_policy,
.llseek = generic_file_llseek,
};
int __init ima_fs_init(void)
{
ima_dir = securityfs_create_dir("ima", NULL);
if (IS_ERR(ima_dir))
return -1;
binary_runtime_measurements =
securityfs_create_file("binary_runtime_measurements",
S_IRUSR | S_IRGRP, ima_dir, NULL,
&ima_measurements_ops);
if (IS_ERR(binary_runtime_measurements))
goto out;
ascii_runtime_measurements =
securityfs_create_file("ascii_runtime_measurements",
S_IRUSR | S_IRGRP, ima_dir, NULL,
&ima_ascii_measurements_ops);
if (IS_ERR(ascii_runtime_measurements))
goto out;
runtime_measurements_count =
securityfs_create_file("runtime_measurements_count",
S_IRUSR | S_IRGRP, ima_dir, NULL,
&ima_measurements_count_ops);
if (IS_ERR(runtime_measurements_count))
goto out;
violations =
securityfs_create_file("violations", S_IRUSR | S_IRGRP,
ima_dir, NULL, &ima_htable_violations_ops);
if (IS_ERR(violations))
goto out;
ima_policy = securityfs_create_file("policy",
S_IWUSR,
ima_dir, NULL,
&ima_measure_policy_ops);
if (IS_ERR(ima_policy))
goto out;
return 0;
out:
securityfs_remove(violations);
securityfs_remove(runtime_measurements_count);
securityfs_remove(ascii_runtime_measurements);
securityfs_remove(binary_runtime_measurements);
securityfs_remove(ima_dir);
securityfs_remove(ima_policy);
return -1;
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (C) 2005,2006,2007,2008 IBM Corporation
*
* Authors:
* Reiner Sailer <sailer@watson.ibm.com>
* Leendert van Doorn <leendert@watson.ibm.com>
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: ima_init.c
* initialization and cleanup functions
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <crypto/hash_info.h>
#include "ima.h"
/* name for boot aggregate entry */
static const char *boot_aggregate_name = "boot_aggregate";
int ima_used_chip;
/* Add the boot aggregate to the IMA measurement list and extend
* the PCR register.
*
* Calculate the boot aggregate, a SHA1 over tpm registers 0-7,
* assuming a TPM chip exists, and zeroes if the TPM chip does not
* exist. Add the boot aggregate measurement to the measurement
* list and extend the PCR register.
*
* If a tpm chip does not exist, indicate the core root of trust is
* not hardware based by invalidating the aggregate PCR value.
* (The aggregate PCR value is invalidated by adding one value to
* the measurement list and extending the aggregate PCR value with
* a different value.) Violations add a zero entry to the measurement
* list and extend the aggregate PCR value with ff...ff's.
*/
static int __init ima_add_boot_aggregate(void)
{
static const char op[] = "add_boot_aggregate";
const char *audit_cause = "ENOMEM";
struct ima_template_entry *entry;
struct integrity_iint_cache tmp_iint, *iint = &tmp_iint;
int result = -ENOMEM;
int violation = 0;
struct {
struct ima_digest_data hdr;
char digest[TPM_DIGEST_SIZE];
} hash;
memset(iint, 0, sizeof(*iint));
memset(&hash, 0, sizeof(hash));
iint->ima_hash = &hash.hdr;
iint->ima_hash->algo = HASH_ALGO_SHA1;
iint->ima_hash->length = SHA1_DIGEST_SIZE;
if (ima_used_chip) {
result = ima_calc_boot_aggregate(&hash.hdr);
if (result < 0) {
audit_cause = "hashing_error";
goto err_out;
}
}
result = ima_alloc_init_template(iint, NULL, boot_aggregate_name,
NULL, 0, &entry);
if (result < 0) {
audit_cause = "alloc_entry";
goto err_out;
}
result = ima_store_template(entry, violation, NULL,
boot_aggregate_name);
if (result < 0) {
ima_free_template_entry(entry);
audit_cause = "store_entry";
goto err_out;
}
return 0;
err_out:
integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op,
audit_cause, result, 0);
return result;
}
int __init ima_init(void)
{
u8 pcr_i[TPM_DIGEST_SIZE];
int rc;
ima_used_chip = 0;
rc = tpm_pcr_read(TPM_ANY_NUM, 0, pcr_i);
if (rc == 0)
ima_used_chip = 1;
if (!ima_used_chip)
pr_info("No TPM chip found, activating TPM-bypass!\n");
rc = ima_init_keyring(INTEGRITY_KEYRING_IMA);
if (rc)
return rc;
rc = ima_init_crypto();
if (rc)
return rc;
rc = ima_init_template();
if (rc != 0)
return rc;
rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */
if (rc != 0)
return rc;
ima_init_policy();
return ima_fs_init();
}

View file

@ -0,0 +1,360 @@
/*
* Copyright (C) 2005,2006,2007,2008 IBM Corporation
*
* Authors:
* Reiner Sailer <sailer@watson.ibm.com>
* Serge Hallyn <serue@us.ibm.com>
* Kylene Hall <kylene@us.ibm.com>
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: ima_main.c
* implements the IMA hooks: ima_bprm_check, ima_file_mmap,
* and ima_file_check.
*/
#include <linux/module.h>
#include <linux/file.h>
#include <linux/binfmts.h>
#include <linux/mount.h>
#include <linux/mman.h>
#include <linux/slab.h>
#include <linux/xattr.h>
#include <linux/ima.h>
#include <crypto/hash_info.h>
#include "ima.h"
int ima_initialized;
#ifdef CONFIG_IMA_APPRAISE
int ima_appraise = IMA_APPRAISE_ENFORCE;
#else
int ima_appraise;
#endif
int ima_hash_algo = HASH_ALGO_SHA1;
static int hash_setup_done;
static int __init hash_setup(char *str)
{
struct ima_template_desc *template_desc = ima_template_desc_current();
int i;
if (hash_setup_done)
return 1;
if (strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) {
if (strncmp(str, "sha1", 4) == 0)
ima_hash_algo = HASH_ALGO_SHA1;
else if (strncmp(str, "md5", 3) == 0)
ima_hash_algo = HASH_ALGO_MD5;
goto out;
}
for (i = 0; i < HASH_ALGO__LAST; i++) {
if (strcmp(str, hash_algo_name[i]) == 0) {
ima_hash_algo = i;
break;
}
}
out:
hash_setup_done = 1;
return 1;
}
__setup("ima_hash=", hash_setup);
/*
* ima_rdwr_violation_check
*
* Only invalidate the PCR for measured files:
* - Opening a file for write when already open for read,
* results in a time of measure, time of use (ToMToU) error.
* - Opening a file for read when already open for write,
* could result in a file measurement error.
*
*/
static void ima_rdwr_violation_check(struct file *file,
struct integrity_iint_cache *iint,
int must_measure,
char **pathbuf,
const char **pathname)
{
struct inode *inode = file_inode(file);
fmode_t mode = file->f_mode;
bool send_tomtou = false, send_writers = false;
if (mode & FMODE_WRITE) {
if (atomic_read(&inode->i_readcount) && IS_IMA(inode)) {
if (!iint)
iint = integrity_iint_find(inode);
/* IMA_MEASURE is set from reader side */
if (iint && (iint->flags & IMA_MEASURE))
send_tomtou = true;
}
} else {
if ((atomic_read(&inode->i_writecount) > 0) && must_measure)
send_writers = true;
}
if (!send_tomtou && !send_writers)
return;
*pathname = ima_d_path(&file->f_path, pathbuf);
if (send_tomtou)
ima_add_violation(file, *pathname, "invalid_pcr", "ToMToU");
if (send_writers)
ima_add_violation(file, *pathname,
"invalid_pcr", "open_writers");
}
static void ima_check_last_writer(struct integrity_iint_cache *iint,
struct inode *inode, struct file *file)
{
fmode_t mode = file->f_mode;
if (!(mode & FMODE_WRITE))
return;
mutex_lock(&inode->i_mutex);
if (atomic_read(&inode->i_writecount) == 1) {
if ((iint->version != inode->i_version) ||
(iint->flags & IMA_NEW_FILE)) {
iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE);
if (iint->flags & IMA_APPRAISE)
ima_update_xattr(iint, file);
}
}
mutex_unlock(&inode->i_mutex);
}
/**
* ima_file_free - called on __fput()
* @file: pointer to file structure being freed
*
* Flag files that changed, based on i_version
*/
void ima_file_free(struct file *file)
{
struct inode *inode = file_inode(file);
struct integrity_iint_cache *iint;
if (!iint_initialized || !S_ISREG(inode->i_mode))
return;
iint = integrity_iint_find(inode);
if (!iint)
return;
ima_check_last_writer(iint, inode, file);
}
static int process_measurement(struct file *file, int mask, int function,
int opened)
{
struct inode *inode = file_inode(file);
struct integrity_iint_cache *iint = NULL;
struct ima_template_desc *template_desc;
char *pathbuf = NULL;
const char *pathname = NULL;
int rc = -ENOMEM, action, must_appraise;
struct evm_ima_xattr_data *xattr_value = NULL, **xattr_ptr = NULL;
int xattr_len = 0;
bool violation_check;
if (!ima_policy_flag || !S_ISREG(inode->i_mode))
return 0;
/* Return an IMA_MEASURE, IMA_APPRAISE, IMA_AUDIT action
* bitmask based on the appraise/audit/measurement policy.
* Included is the appraise submask.
*/
action = ima_get_action(inode, mask, function);
violation_check = ((function == FILE_CHECK || function == MMAP_CHECK) &&
(ima_policy_flag & IMA_MEASURE));
if (!action && !violation_check)
return 0;
must_appraise = action & IMA_APPRAISE;
/* Is the appraise rule hook specific? */
if (action & IMA_FILE_APPRAISE)
function = FILE_CHECK;
mutex_lock(&inode->i_mutex);
if (action) {
iint = integrity_inode_get(inode);
if (!iint)
goto out;
}
if (violation_check) {
ima_rdwr_violation_check(file, iint, action & IMA_MEASURE,
&pathbuf, &pathname);
if (!action) {
rc = 0;
goto out_free;
}
}
/* Determine if already appraised/measured based on bitmask
* (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED,
* IMA_AUDIT, IMA_AUDITED)
*/
iint->flags |= action;
action &= IMA_DO_MASK;
action &= ~((iint->flags & IMA_DONE_MASK) >> 1);
/* Nothing to do, just return existing appraised status */
if (!action) {
if (must_appraise)
rc = ima_get_cache_status(iint, function);
goto out_digsig;
}
template_desc = ima_template_desc_current();
if ((action & IMA_APPRAISE_SUBMASK) ||
strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) != 0)
xattr_ptr = &xattr_value;
rc = ima_collect_measurement(iint, file, xattr_ptr, &xattr_len);
if (rc != 0) {
if (file->f_flags & O_DIRECT)
rc = (iint->flags & IMA_PERMIT_DIRECTIO) ? 0 : -EACCES;
goto out_digsig;
}
if (!pathname) /* ima_rdwr_violation possibly pre-fetched */
pathname = ima_d_path(&file->f_path, &pathbuf);
if (action & IMA_MEASURE)
ima_store_measurement(iint, file, pathname,
xattr_value, xattr_len);
if (action & IMA_APPRAISE_SUBMASK)
rc = ima_appraise_measurement(function, iint, file, pathname,
xattr_value, xattr_len, opened);
if (action & IMA_AUDIT)
ima_audit_measurement(iint, pathname);
out_digsig:
if ((mask & MAY_WRITE) && (iint->flags & IMA_DIGSIG))
rc = -EACCES;
kfree(xattr_value);
out_free:
kfree(pathbuf);
out:
mutex_unlock(&inode->i_mutex);
if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE))
return -EACCES;
return 0;
}
/**
* ima_file_mmap - based on policy, collect/store measurement.
* @file: pointer to the file to be measured (May be NULL)
* @prot: contains the protection that will be applied by the kernel.
*
* Measure files being mmapped executable based on the ima_must_measure()
* policy decision.
*
* On success return 0. On integrity appraisal error, assuming the file
* is in policy and IMA-appraisal is in enforcing mode, return -EACCES.
*/
int ima_file_mmap(struct file *file, unsigned long prot)
{
if (file && (prot & PROT_EXEC))
return process_measurement(file, MAY_EXEC, MMAP_CHECK, 0);
return 0;
}
/**
* ima_bprm_check - based on policy, collect/store measurement.
* @bprm: contains the linux_binprm structure
*
* The OS protects against an executable file, already open for write,
* from being executed in deny_write_access() and an executable file,
* already open for execute, from being modified in get_write_access().
* So we can be certain that what we verify and measure here is actually
* what is being executed.
*
* On success return 0. On integrity appraisal error, assuming the file
* is in policy and IMA-appraisal is in enforcing mode, return -EACCES.
*/
int ima_bprm_check(struct linux_binprm *bprm)
{
return process_measurement(bprm->file, MAY_EXEC, BPRM_CHECK, 0);
}
/**
* ima_path_check - based on policy, collect/store measurement.
* @file: pointer to the file to be measured
* @mask: contains MAY_READ, MAY_WRITE or MAY_EXECUTE
*
* Measure files based on the ima_must_measure() policy decision.
*
* On success return 0. On integrity appraisal error, assuming the file
* is in policy and IMA-appraisal is in enforcing mode, return -EACCES.
*/
int ima_file_check(struct file *file, int mask, int opened)
{
return process_measurement(file,
mask & (MAY_READ | MAY_WRITE | MAY_EXEC),
FILE_CHECK, opened);
}
EXPORT_SYMBOL_GPL(ima_file_check);
/**
* ima_module_check - based on policy, collect/store/appraise measurement.
* @file: pointer to the file to be measured/appraised
*
* Measure/appraise kernel modules based on policy.
*
* On success return 0. On integrity appraisal error, assuming the file
* is in policy and IMA-appraisal is in enforcing mode, return -EACCES.
*/
int ima_module_check(struct file *file)
{
if (!file) {
#ifndef CONFIG_MODULE_SIG_FORCE
if ((ima_appraise & IMA_APPRAISE_MODULES) &&
(ima_appraise & IMA_APPRAISE_ENFORCE))
return -EACCES; /* INTEGRITY_UNKNOWN */
#endif
return 0; /* We rely on module signature checking */
}
return process_measurement(file, MAY_EXEC, MODULE_CHECK, 0);
}
int ima_fw_from_file(struct file *file, char *buf, size_t size)
{
if (!file) {
if ((ima_appraise & IMA_APPRAISE_FIRMWARE) &&
(ima_appraise & IMA_APPRAISE_ENFORCE))
return -EACCES; /* INTEGRITY_UNKNOWN */
return 0;
}
return process_measurement(file, MAY_EXEC, FIRMWARE_CHECK, 0);
}
static int __init init_ima(void)
{
int error;
hash_setup(CONFIG_IMA_DEFAULT_HASH);
error = ima_init();
if (!error) {
ima_initialized = 1;
ima_update_policy_flag();
}
return error;
}
late_initcall(init_ima); /* Start IMA after the TPM is available */
MODULE_DESCRIPTION("Integrity Measurement Architecture");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,745 @@
/*
* Copyright (C) 2008 IBM Corporation
* Author: Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* ima_policy.c
* - initialize default measure policy rules
*
*/
#include <linux/module.h>
#include <linux/list.h>
#include <linux/security.h>
#include <linux/magic.h>
#include <linux/parser.h>
#include <linux/slab.h>
#include <linux/genhd.h>
#include "ima.h"
/* flags definitions */
#define IMA_FUNC 0x0001
#define IMA_MASK 0x0002
#define IMA_FSMAGIC 0x0004
#define IMA_UID 0x0008
#define IMA_FOWNER 0x0010
#define IMA_FSUUID 0x0020
#define UNKNOWN 0
#define MEASURE 0x0001 /* same as IMA_MEASURE */
#define DONT_MEASURE 0x0002
#define APPRAISE 0x0004 /* same as IMA_APPRAISE */
#define DONT_APPRAISE 0x0008
#define AUDIT 0x0040
int ima_policy_flag;
#define MAX_LSM_RULES 6
enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE,
LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE
};
struct ima_rule_entry {
struct list_head list;
int action;
unsigned int flags;
enum ima_hooks func;
int mask;
unsigned long fsmagic;
u8 fsuuid[16];
kuid_t uid;
kuid_t fowner;
struct {
void *rule; /* LSM file metadata specific */
void *args_p; /* audit value */
int type; /* audit type */
} lsm[MAX_LSM_RULES];
};
/*
* Without LSM specific knowledge, the default policy can only be
* written in terms of .action, .func, .mask, .fsmagic, .uid, and .fowner
*/
/*
* The minimum rule set to allow for full TCB coverage. Measures all files
* opened or mmap for exec and everything read by root. Dangerous because
* normal users can easily run the machine out of memory simply building
* and running executables.
*/
static struct ima_rule_entry default_rules[] = {
{.action = DONT_MEASURE, .fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = SYSFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = DEBUGFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = TMPFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = DEVPTS_SUPER_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = BINFMTFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC},
{.action = MEASURE, .func = MMAP_CHECK, .mask = MAY_EXEC,
.flags = IMA_FUNC | IMA_MASK},
{.action = MEASURE, .func = BPRM_CHECK, .mask = MAY_EXEC,
.flags = IMA_FUNC | IMA_MASK},
{.action = MEASURE, .func = FILE_CHECK, .mask = MAY_READ, .uid = GLOBAL_ROOT_UID,
.flags = IMA_FUNC | IMA_MASK | IMA_UID},
{.action = MEASURE, .func = MODULE_CHECK, .flags = IMA_FUNC},
{.action = MEASURE, .func = FIRMWARE_CHECK, .flags = IMA_FUNC},
};
static struct ima_rule_entry default_appraise_rules[] = {
{.action = DONT_APPRAISE, .fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = SYSFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = DEBUGFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = TMPFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = RAMFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = DEVPTS_SUPER_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = BINFMTFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC},
{.action = APPRAISE, .fowner = GLOBAL_ROOT_UID, .flags = IMA_FOWNER},
};
static LIST_HEAD(ima_default_rules);
static LIST_HEAD(ima_policy_rules);
static struct list_head *ima_rules;
static DEFINE_MUTEX(ima_rules_mutex);
static bool ima_use_tcb __initdata;
static int __init default_measure_policy_setup(char *str)
{
ima_use_tcb = 1;
return 1;
}
__setup("ima_tcb", default_measure_policy_setup);
static bool ima_use_appraise_tcb __initdata;
static int __init default_appraise_policy_setup(char *str)
{
ima_use_appraise_tcb = 1;
return 1;
}
__setup("ima_appraise_tcb", default_appraise_policy_setup);
/*
* Although the IMA policy does not change, the LSM policy can be
* reloaded, leaving the IMA LSM based rules referring to the old,
* stale LSM policy.
*
* Update the IMA LSM based rules to reflect the reloaded LSM policy.
* We assume the rules still exist; and BUG_ON() if they don't.
*/
static void ima_lsm_update_rules(void)
{
struct ima_rule_entry *entry, *tmp;
int result;
int i;
mutex_lock(&ima_rules_mutex);
list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) {
for (i = 0; i < MAX_LSM_RULES; i++) {
if (!entry->lsm[i].rule)
continue;
result = security_filter_rule_init(entry->lsm[i].type,
Audit_equal,
entry->lsm[i].args_p,
&entry->lsm[i].rule);
BUG_ON(!entry->lsm[i].rule);
}
}
mutex_unlock(&ima_rules_mutex);
}
/**
* ima_match_rules - determine whether an inode matches the measure rule.
* @rule: a pointer to a rule
* @inode: a pointer to an inode
* @func: LIM hook identifier
* @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC)
*
* Returns true on rule match, false on failure.
*/
static bool ima_match_rules(struct ima_rule_entry *rule,
struct inode *inode, enum ima_hooks func, int mask)
{
struct task_struct *tsk = current;
const struct cred *cred = current_cred();
int i;
if ((rule->flags & IMA_FUNC) &&
(rule->func != func && func != POST_SETATTR))
return false;
if ((rule->flags & IMA_MASK) &&
(rule->mask != mask && func != POST_SETATTR))
return false;
if ((rule->flags & IMA_FSMAGIC)
&& rule->fsmagic != inode->i_sb->s_magic)
return false;
if ((rule->flags & IMA_FSUUID) &&
memcmp(rule->fsuuid, inode->i_sb->s_uuid, sizeof(rule->fsuuid)))
return false;
if ((rule->flags & IMA_UID) && !uid_eq(rule->uid, cred->uid))
return false;
if ((rule->flags & IMA_FOWNER) && !uid_eq(rule->fowner, inode->i_uid))
return false;
for (i = 0; i < MAX_LSM_RULES; i++) {
int rc = 0;
u32 osid, sid;
int retried = 0;
if (!rule->lsm[i].rule)
continue;
retry:
switch (i) {
case LSM_OBJ_USER:
case LSM_OBJ_ROLE:
case LSM_OBJ_TYPE:
security_inode_getsecid(inode, &osid);
rc = security_filter_rule_match(osid,
rule->lsm[i].type,
Audit_equal,
rule->lsm[i].rule,
NULL);
break;
case LSM_SUBJ_USER:
case LSM_SUBJ_ROLE:
case LSM_SUBJ_TYPE:
security_task_getsecid(tsk, &sid);
rc = security_filter_rule_match(sid,
rule->lsm[i].type,
Audit_equal,
rule->lsm[i].rule,
NULL);
default:
break;
}
if ((rc < 0) && (!retried)) {
retried = 1;
ima_lsm_update_rules();
goto retry;
}
if (!rc)
return false;
}
return true;
}
/*
* In addition to knowing that we need to appraise the file in general,
* we need to differentiate between calling hooks, for hook specific rules.
*/
static int get_subaction(struct ima_rule_entry *rule, int func)
{
if (!(rule->flags & IMA_FUNC))
return IMA_FILE_APPRAISE;
switch (func) {
case MMAP_CHECK:
return IMA_MMAP_APPRAISE;
case BPRM_CHECK:
return IMA_BPRM_APPRAISE;
case MODULE_CHECK:
return IMA_MODULE_APPRAISE;
case FIRMWARE_CHECK:
return IMA_FIRMWARE_APPRAISE;
case FILE_CHECK:
default:
return IMA_FILE_APPRAISE;
}
}
/**
* ima_match_policy - decision based on LSM and other conditions
* @inode: pointer to an inode for which the policy decision is being made
* @func: IMA hook identifier
* @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC)
*
* Measure decision based on func/mask/fsmagic and LSM(subj/obj/type)
* conditions.
*
* (There is no need for locking when walking the policy list,
* as elements in the list are never deleted, nor does the list
* change.)
*/
int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask,
int flags)
{
struct ima_rule_entry *entry;
int action = 0, actmask = flags | (flags << 1);
list_for_each_entry(entry, ima_rules, list) {
if (!(entry->action & actmask))
continue;
if (!ima_match_rules(entry, inode, func, mask))
continue;
action |= entry->flags & IMA_ACTION_FLAGS;
action |= entry->action & IMA_DO_MASK;
if (entry->action & IMA_APPRAISE)
action |= get_subaction(entry, func);
if (entry->action & IMA_DO_MASK)
actmask &= ~(entry->action | entry->action << 1);
else
actmask &= ~(entry->action | entry->action >> 1);
if (!actmask)
break;
}
return action;
}
/*
* Initialize the ima_policy_flag variable based on the currently
* loaded policy. Based on this flag, the decision to short circuit
* out of a function or not call the function in the first place
* can be made earlier.
*/
void ima_update_policy_flag(void)
{
struct ima_rule_entry *entry;
ima_policy_flag = 0;
list_for_each_entry(entry, ima_rules, list) {
if (entry->action & IMA_DO_MASK)
ima_policy_flag |= entry->action;
}
if (!ima_appraise)
ima_policy_flag &= ~IMA_APPRAISE;
}
/**
* ima_init_policy - initialize the default measure rules.
*
* ima_rules points to either the ima_default_rules or the
* the new ima_policy_rules.
*/
void __init ima_init_policy(void)
{
int i, measure_entries, appraise_entries;
/* if !ima_use_tcb set entries = 0 so we load NO default rules */
measure_entries = ima_use_tcb ? ARRAY_SIZE(default_rules) : 0;
appraise_entries = ima_use_appraise_tcb ?
ARRAY_SIZE(default_appraise_rules) : 0;
for (i = 0; i < measure_entries + appraise_entries; i++) {
if (i < measure_entries)
list_add_tail(&default_rules[i].list,
&ima_default_rules);
else {
int j = i - measure_entries;
list_add_tail(&default_appraise_rules[j].list,
&ima_default_rules);
}
}
ima_rules = &ima_default_rules;
}
/**
* ima_update_policy - update default_rules with new measure rules
*
* Called on file .release to update the default rules with a complete new
* policy. Once updated, the policy is locked, no additional rules can be
* added to the policy.
*/
void ima_update_policy(void)
{
static const char op[] = "policy_update";
const char *cause = "already-exists";
int result = 1;
int audit_info = 0;
if (ima_rules == &ima_default_rules) {
ima_rules = &ima_policy_rules;
ima_update_policy_flag();
cause = "complete";
result = 0;
}
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
NULL, op, cause, result, audit_info);
}
enum {
Opt_err = -1,
Opt_measure = 1, Opt_dont_measure,
Opt_appraise, Opt_dont_appraise,
Opt_audit,
Opt_obj_user, Opt_obj_role, Opt_obj_type,
Opt_subj_user, Opt_subj_role, Opt_subj_type,
Opt_func, Opt_mask, Opt_fsmagic, Opt_uid, Opt_fowner,
Opt_appraise_type, Opt_fsuuid, Opt_permit_directio
};
static match_table_t policy_tokens = {
{Opt_measure, "measure"},
{Opt_dont_measure, "dont_measure"},
{Opt_appraise, "appraise"},
{Opt_dont_appraise, "dont_appraise"},
{Opt_audit, "audit"},
{Opt_obj_user, "obj_user=%s"},
{Opt_obj_role, "obj_role=%s"},
{Opt_obj_type, "obj_type=%s"},
{Opt_subj_user, "subj_user=%s"},
{Opt_subj_role, "subj_role=%s"},
{Opt_subj_type, "subj_type=%s"},
{Opt_func, "func=%s"},
{Opt_mask, "mask=%s"},
{Opt_fsmagic, "fsmagic=%s"},
{Opt_fsuuid, "fsuuid=%s"},
{Opt_uid, "uid=%s"},
{Opt_fowner, "fowner=%s"},
{Opt_appraise_type, "appraise_type=%s"},
{Opt_permit_directio, "permit_directio"},
{Opt_err, NULL}
};
static int ima_lsm_rule_init(struct ima_rule_entry *entry,
substring_t *args, int lsm_rule, int audit_type)
{
int result;
if (entry->lsm[lsm_rule].rule)
return -EINVAL;
entry->lsm[lsm_rule].args_p = match_strdup(args);
if (!entry->lsm[lsm_rule].args_p)
return -ENOMEM;
entry->lsm[lsm_rule].type = audit_type;
result = security_filter_rule_init(entry->lsm[lsm_rule].type,
Audit_equal,
entry->lsm[lsm_rule].args_p,
&entry->lsm[lsm_rule].rule);
if (!entry->lsm[lsm_rule].rule) {
kfree(entry->lsm[lsm_rule].args_p);
return -EINVAL;
}
return result;
}
static void ima_log_string(struct audit_buffer *ab, char *key, char *value)
{
audit_log_format(ab, "%s=", key);
audit_log_untrustedstring(ab, value);
audit_log_format(ab, " ");
}
static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
{
struct audit_buffer *ab;
char *p;
int result = 0;
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE);
entry->uid = INVALID_UID;
entry->fowner = INVALID_UID;
entry->action = UNKNOWN;
while ((p = strsep(&rule, " \t")) != NULL) {
substring_t args[MAX_OPT_ARGS];
int token;
unsigned long lnum;
if (result < 0)
break;
if ((*p == '\0') || (*p == ' ') || (*p == '\t'))
continue;
token = match_token(p, policy_tokens, args);
switch (token) {
case Opt_measure:
ima_log_string(ab, "action", "measure");
if (entry->action != UNKNOWN)
result = -EINVAL;
entry->action = MEASURE;
break;
case Opt_dont_measure:
ima_log_string(ab, "action", "dont_measure");
if (entry->action != UNKNOWN)
result = -EINVAL;
entry->action = DONT_MEASURE;
break;
case Opt_appraise:
ima_log_string(ab, "action", "appraise");
if (entry->action != UNKNOWN)
result = -EINVAL;
entry->action = APPRAISE;
break;
case Opt_dont_appraise:
ima_log_string(ab, "action", "dont_appraise");
if (entry->action != UNKNOWN)
result = -EINVAL;
entry->action = DONT_APPRAISE;
break;
case Opt_audit:
ima_log_string(ab, "action", "audit");
if (entry->action != UNKNOWN)
result = -EINVAL;
entry->action = AUDIT;
break;
case Opt_func:
ima_log_string(ab, "func", args[0].from);
if (entry->func)
result = -EINVAL;
if (strcmp(args[0].from, "FILE_CHECK") == 0)
entry->func = FILE_CHECK;
/* PATH_CHECK is for backwards compat */
else if (strcmp(args[0].from, "PATH_CHECK") == 0)
entry->func = FILE_CHECK;
else if (strcmp(args[0].from, "MODULE_CHECK") == 0)
entry->func = MODULE_CHECK;
else if (strcmp(args[0].from, "FIRMWARE_CHECK") == 0)
entry->func = FIRMWARE_CHECK;
else if ((strcmp(args[0].from, "FILE_MMAP") == 0)
|| (strcmp(args[0].from, "MMAP_CHECK") == 0))
entry->func = MMAP_CHECK;
else if (strcmp(args[0].from, "BPRM_CHECK") == 0)
entry->func = BPRM_CHECK;
else
result = -EINVAL;
if (!result)
entry->flags |= IMA_FUNC;
break;
case Opt_mask:
ima_log_string(ab, "mask", args[0].from);
if (entry->mask)
result = -EINVAL;
if ((strcmp(args[0].from, "MAY_EXEC")) == 0)
entry->mask = MAY_EXEC;
else if (strcmp(args[0].from, "MAY_WRITE") == 0)
entry->mask = MAY_WRITE;
else if (strcmp(args[0].from, "MAY_READ") == 0)
entry->mask = MAY_READ;
else if (strcmp(args[0].from, "MAY_APPEND") == 0)
entry->mask = MAY_APPEND;
else
result = -EINVAL;
if (!result)
entry->flags |= IMA_MASK;
break;
case Opt_fsmagic:
ima_log_string(ab, "fsmagic", args[0].from);
if (entry->fsmagic) {
result = -EINVAL;
break;
}
result = kstrtoul(args[0].from, 16, &entry->fsmagic);
if (!result)
entry->flags |= IMA_FSMAGIC;
break;
case Opt_fsuuid:
ima_log_string(ab, "fsuuid", args[0].from);
if (memchr_inv(entry->fsuuid, 0x00,
sizeof(entry->fsuuid))) {
result = -EINVAL;
break;
}
result = blk_part_pack_uuid(args[0].from,
entry->fsuuid);
if (!result)
entry->flags |= IMA_FSUUID;
break;
case Opt_uid:
ima_log_string(ab, "uid", args[0].from);
if (uid_valid(entry->uid)) {
result = -EINVAL;
break;
}
result = kstrtoul(args[0].from, 10, &lnum);
if (!result) {
entry->uid = make_kuid(current_user_ns(), (uid_t)lnum);
if (!uid_valid(entry->uid) || (((uid_t)lnum) != lnum))
result = -EINVAL;
else
entry->flags |= IMA_UID;
}
break;
case Opt_fowner:
ima_log_string(ab, "fowner", args[0].from);
if (uid_valid(entry->fowner)) {
result = -EINVAL;
break;
}
result = kstrtoul(args[0].from, 10, &lnum);
if (!result) {
entry->fowner = make_kuid(current_user_ns(), (uid_t)lnum);
if (!uid_valid(entry->fowner) || (((uid_t)lnum) != lnum))
result = -EINVAL;
else
entry->flags |= IMA_FOWNER;
}
break;
case Opt_obj_user:
ima_log_string(ab, "obj_user", args[0].from);
result = ima_lsm_rule_init(entry, args,
LSM_OBJ_USER,
AUDIT_OBJ_USER);
break;
case Opt_obj_role:
ima_log_string(ab, "obj_role", args[0].from);
result = ima_lsm_rule_init(entry, args,
LSM_OBJ_ROLE,
AUDIT_OBJ_ROLE);
break;
case Opt_obj_type:
ima_log_string(ab, "obj_type", args[0].from);
result = ima_lsm_rule_init(entry, args,
LSM_OBJ_TYPE,
AUDIT_OBJ_TYPE);
break;
case Opt_subj_user:
ima_log_string(ab, "subj_user", args[0].from);
result = ima_lsm_rule_init(entry, args,
LSM_SUBJ_USER,
AUDIT_SUBJ_USER);
break;
case Opt_subj_role:
ima_log_string(ab, "subj_role", args[0].from);
result = ima_lsm_rule_init(entry, args,
LSM_SUBJ_ROLE,
AUDIT_SUBJ_ROLE);
break;
case Opt_subj_type:
ima_log_string(ab, "subj_type", args[0].from);
result = ima_lsm_rule_init(entry, args,
LSM_SUBJ_TYPE,
AUDIT_SUBJ_TYPE);
break;
case Opt_appraise_type:
if (entry->action != APPRAISE) {
result = -EINVAL;
break;
}
ima_log_string(ab, "appraise_type", args[0].from);
if ((strcmp(args[0].from, "imasig")) == 0)
entry->flags |= IMA_DIGSIG_REQUIRED;
else
result = -EINVAL;
break;
case Opt_permit_directio:
entry->flags |= IMA_PERMIT_DIRECTIO;
break;
case Opt_err:
ima_log_string(ab, "UNKNOWN", p);
result = -EINVAL;
break;
}
}
if (!result && (entry->action == UNKNOWN))
result = -EINVAL;
else if (entry->func == MODULE_CHECK)
ima_appraise |= IMA_APPRAISE_MODULES;
else if (entry->func == FIRMWARE_CHECK)
ima_appraise |= IMA_APPRAISE_FIRMWARE;
audit_log_format(ab, "res=%d", !result);
audit_log_end(ab);
return result;
}
/**
* ima_parse_add_rule - add a rule to ima_policy_rules
* @rule - ima measurement policy rule
*
* Uses a mutex to protect the policy list from multiple concurrent writers.
* Returns the length of the rule parsed, an error code on failure
*/
ssize_t ima_parse_add_rule(char *rule)
{
static const char op[] = "update_policy";
char *p;
struct ima_rule_entry *entry;
ssize_t result, len;
int audit_info = 0;
/* Prevent installed policy from changing */
if (ima_rules != &ima_default_rules) {
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
NULL, op, "already-exists",
-EACCES, audit_info);
return -EACCES;
}
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
NULL, op, "-ENOMEM", -ENOMEM, audit_info);
return -ENOMEM;
}
INIT_LIST_HEAD(&entry->list);
p = strsep(&rule, "\n");
len = strlen(p) + 1;
if (*p == '#') {
kfree(entry);
return len;
}
result = ima_parse_rule(p, entry);
if (result) {
kfree(entry);
integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
NULL, op, "invalid-policy", result,
audit_info);
return result;
}
mutex_lock(&ima_rules_mutex);
list_add_tail(&entry->list, &ima_policy_rules);
mutex_unlock(&ima_rules_mutex);
return len;
}
/* ima_delete_rules called to cleanup invalid policy */
void ima_delete_rules(void)
{
struct ima_rule_entry *entry, *tmp;
int i;
mutex_lock(&ima_rules_mutex);
list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) {
for (i = 0; i < MAX_LSM_RULES; i++)
kfree(entry->lsm[i].args_p);
list_del(&entry->list);
kfree(entry);
}
mutex_unlock(&ima_rules_mutex);
}

View file

@ -0,0 +1,150 @@
/*
* Copyright (C) 2005,2006,2007,2008 IBM Corporation
*
* Authors:
* Serge Hallyn <serue@us.ibm.com>
* Reiner Sailer <sailer@watson.ibm.com>
* Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: ima_queue.c
* Implements queues that store template measurements and
* maintains aggregate over the stored measurements
* in the pre-configured TPM PCR (if available).
* The measurement list is append-only. No entry is
* ever removed or changed during the boot-cycle.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/rculist.h>
#include <linux/slab.h>
#include "ima.h"
#define AUDIT_CAUSE_LEN_MAX 32
LIST_HEAD(ima_measurements); /* list of all measurements */
/* key: inode (before secure-hashing a file) */
struct ima_h_table ima_htable = {
.len = ATOMIC_LONG_INIT(0),
.violations = ATOMIC_LONG_INIT(0),
.queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
};
/* mutex protects atomicity of extending measurement list
* and extending the TPM PCR aggregate. Since tpm_extend can take
* long (and the tpm driver uses a mutex), we can't use the spinlock.
*/
static DEFINE_MUTEX(ima_extend_list_mutex);
/* lookup up the digest value in the hash table, and return the entry */
static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value)
{
struct ima_queue_entry *qe, *ret = NULL;
unsigned int key;
int rc;
key = ima_hash_key(digest_value);
rcu_read_lock();
hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) {
rc = memcmp(qe->entry->digest, digest_value, TPM_DIGEST_SIZE);
if (rc == 0) {
ret = qe;
break;
}
}
rcu_read_unlock();
return ret;
}
/* ima_add_template_entry helper function:
* - Add template entry to measurement list and hash table.
*
* (Called with ima_extend_list_mutex held.)
*/
static int ima_add_digest_entry(struct ima_template_entry *entry)
{
struct ima_queue_entry *qe;
unsigned int key;
qe = kmalloc(sizeof(*qe), GFP_KERNEL);
if (qe == NULL) {
pr_err("OUT OF MEMORY ERROR creating queue entry\n");
return -ENOMEM;
}
qe->entry = entry;
INIT_LIST_HEAD(&qe->later);
list_add_tail_rcu(&qe->later, &ima_measurements);
atomic_long_inc(&ima_htable.len);
key = ima_hash_key(entry->digest);
hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
return 0;
}
static int ima_pcr_extend(const u8 *hash)
{
int result = 0;
if (!ima_used_chip)
return result;
result = tpm_pcr_extend(TPM_ANY_NUM, CONFIG_IMA_MEASURE_PCR_IDX, hash);
if (result != 0)
pr_err("Error Communicating to TPM chip, result: %d\n", result);
return result;
}
/* Add template entry to the measurement list and hash table,
* and extend the pcr.
*/
int ima_add_template_entry(struct ima_template_entry *entry, int violation,
const char *op, struct inode *inode,
const unsigned char *filename)
{
u8 digest[TPM_DIGEST_SIZE];
const char *audit_cause = "hash_added";
char tpm_audit_cause[AUDIT_CAUSE_LEN_MAX];
int audit_info = 1;
int result = 0, tpmresult = 0;
mutex_lock(&ima_extend_list_mutex);
if (!violation) {
memcpy(digest, entry->digest, sizeof(digest));
if (ima_lookup_digest_entry(digest)) {
audit_cause = "hash_exists";
result = -EEXIST;
goto out;
}
}
result = ima_add_digest_entry(entry);
if (result < 0) {
audit_cause = "ENOMEM";
audit_info = 0;
goto out;
}
if (violation) /* invalidate pcr */
memset(digest, 0xff, sizeof(digest));
tpmresult = ima_pcr_extend(digest);
if (tpmresult != 0) {
snprintf(tpm_audit_cause, AUDIT_CAUSE_LEN_MAX, "TPM_error(%d)",
tpmresult);
audit_cause = tpm_audit_cause;
audit_info = 0;
}
out:
mutex_unlock(&ima_extend_list_mutex);
integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename,
op, audit_cause, result, audit_info);
return result;
}

View file

@ -0,0 +1,170 @@
/*
* Copyright (C) 2013 Politecnico di Torino, Italy
* TORSEC group -- http://security.polito.it
*
* Author: Roberto Sassu <roberto.sassu@polito.it>
*
* 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.
*
* File: ima_template.c
* Helpers to manage template descriptors.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <crypto/hash_info.h>
#include "ima.h"
#include "ima_template_lib.h"
static struct ima_template_desc defined_templates[] = {
{.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT},
{.name = "ima-ng", .fmt = "d-ng|n-ng"},
{.name = "ima-sig", .fmt = "d-ng|n-ng|sig"},
};
static struct ima_template_field supported_fields[] = {
{.field_id = "d", .field_init = ima_eventdigest_init,
.field_show = ima_show_template_digest},
{.field_id = "n", .field_init = ima_eventname_init,
.field_show = ima_show_template_string},
{.field_id = "d-ng", .field_init = ima_eventdigest_ng_init,
.field_show = ima_show_template_digest_ng},
{.field_id = "n-ng", .field_init = ima_eventname_ng_init,
.field_show = ima_show_template_string},
{.field_id = "sig", .field_init = ima_eventsig_init,
.field_show = ima_show_template_sig},
};
static struct ima_template_desc *ima_template;
static struct ima_template_desc *lookup_template_desc(const char *name);
static int __init ima_template_setup(char *str)
{
struct ima_template_desc *template_desc;
int template_len = strlen(str);
/*
* Verify that a template with the supplied name exists.
* If not, use CONFIG_IMA_DEFAULT_TEMPLATE.
*/
template_desc = lookup_template_desc(str);
if (!template_desc)
return 1;
/*
* Verify whether the current hash algorithm is supported
* by the 'ima' template.
*/
if (template_len == 3 && strcmp(str, IMA_TEMPLATE_IMA_NAME) == 0 &&
ima_hash_algo != HASH_ALGO_SHA1 && ima_hash_algo != HASH_ALGO_MD5) {
pr_err("template does not support hash alg\n");
return 1;
}
ima_template = template_desc;
return 1;
}
__setup("ima_template=", ima_template_setup);
static struct ima_template_desc *lookup_template_desc(const char *name)
{
int i;
for (i = 0; i < ARRAY_SIZE(defined_templates); i++) {
if (strcmp(defined_templates[i].name, name) == 0)
return defined_templates + i;
}
return NULL;
}
static struct ima_template_field *lookup_template_field(const char *field_id)
{
int i;
for (i = 0; i < ARRAY_SIZE(supported_fields); i++)
if (strncmp(supported_fields[i].field_id, field_id,
IMA_TEMPLATE_FIELD_ID_MAX_LEN) == 0)
return &supported_fields[i];
return NULL;
}
static int template_fmt_size(const char *template_fmt)
{
char c;
int template_fmt_len = strlen(template_fmt);
int i = 0, j = 0;
while (i < template_fmt_len) {
c = template_fmt[i];
if (c == '|')
j++;
i++;
}
return j + 1;
}
static int template_desc_init_fields(const char *template_fmt,
struct ima_template_field ***fields,
int *num_fields)
{
char *c, *template_fmt_copy, *template_fmt_ptr;
int template_num_fields = template_fmt_size(template_fmt);
int i, result = 0;
if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX)
return -EINVAL;
/* copying is needed as strsep() modifies the original buffer */
template_fmt_copy = kstrdup(template_fmt, GFP_KERNEL);
if (template_fmt_copy == NULL)
return -ENOMEM;
*fields = kzalloc(template_num_fields * sizeof(*fields), GFP_KERNEL);
if (*fields == NULL) {
result = -ENOMEM;
goto out;
}
template_fmt_ptr = template_fmt_copy;
for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL &&
i < template_num_fields; i++) {
struct ima_template_field *f = lookup_template_field(c);
if (!f) {
result = -ENOENT;
goto out;
}
(*fields)[i] = f;
}
*num_fields = i;
out:
if (result < 0) {
kfree(*fields);
*fields = NULL;
}
kfree(template_fmt_copy);
return result;
}
struct ima_template_desc *ima_template_desc_current(void)
{
if (!ima_template)
ima_template =
lookup_template_desc(CONFIG_IMA_DEFAULT_TEMPLATE);
return ima_template;
}
int __init ima_init_template(void)
{
struct ima_template_desc *template = ima_template_desc_current();
return template_desc_init_fields(template->fmt,
&(template->fields),
&(template->num_fields));
}

View file

@ -0,0 +1,342 @@
/*
* Copyright (C) 2013 Politecnico di Torino, Italy
* TORSEC group -- http://security.polito.it
*
* Author: Roberto Sassu <roberto.sassu@polito.it>
*
* 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.
*
* File: ima_template_lib.c
* Library of supported template fields.
*/
#include <crypto/hash_info.h>
#include "ima_template_lib.h"
static bool ima_template_hash_algo_allowed(u8 algo)
{
if (algo == HASH_ALGO_SHA1 || algo == HASH_ALGO_MD5)
return true;
return false;
}
enum data_formats {
DATA_FMT_DIGEST = 0,
DATA_FMT_DIGEST_WITH_ALGO,
DATA_FMT_STRING,
DATA_FMT_HEX
};
static int ima_write_template_field_data(const void *data, const u32 datalen,
enum data_formats datafmt,
struct ima_field_data *field_data)
{
u8 *buf, *buf_ptr;
u32 buflen = datalen;
if (datafmt == DATA_FMT_STRING)
buflen = datalen + 1;
buf = kzalloc(buflen, GFP_KERNEL);
if (!buf)
return -ENOMEM;
memcpy(buf, data, datalen);
/*
* Replace all space characters with underscore for event names and
* strings. This avoid that, during the parsing of a measurements list,
* filenames with spaces or that end with the suffix ' (deleted)' are
* split into multiple template fields (the space is the delimitator
* character for measurements lists in ASCII format).
*/
if (datafmt == DATA_FMT_STRING) {
for (buf_ptr = buf; buf_ptr - buf < datalen; buf_ptr++)
if (*buf_ptr == ' ')
*buf_ptr = '_';
}
field_data->data = buf;
field_data->len = buflen;
return 0;
}
static void ima_show_template_data_ascii(struct seq_file *m,
enum ima_show_type show,
enum data_formats datafmt,
struct ima_field_data *field_data)
{
u8 *buf_ptr = field_data->data, buflen = field_data->len;
switch (datafmt) {
case DATA_FMT_DIGEST_WITH_ALGO:
buf_ptr = strnchr(field_data->data, buflen, ':');
if (buf_ptr != field_data->data)
seq_printf(m, "%s", field_data->data);
/* skip ':' and '\0' */
buf_ptr += 2;
buflen -= buf_ptr - field_data->data;
case DATA_FMT_DIGEST:
case DATA_FMT_HEX:
if (!buflen)
break;
ima_print_digest(m, buf_ptr, buflen);
break;
case DATA_FMT_STRING:
seq_printf(m, "%s", buf_ptr);
break;
default:
break;
}
}
static void ima_show_template_data_binary(struct seq_file *m,
enum ima_show_type show,
enum data_formats datafmt,
struct ima_field_data *field_data)
{
u32 len = (show == IMA_SHOW_BINARY_OLD_STRING_FMT) ?
strlen(field_data->data) : field_data->len;
if (show != IMA_SHOW_BINARY_NO_FIELD_LEN)
ima_putc(m, &len, sizeof(len));
if (!len)
return;
ima_putc(m, field_data->data, len);
}
static void ima_show_template_field_data(struct seq_file *m,
enum ima_show_type show,
enum data_formats datafmt,
struct ima_field_data *field_data)
{
switch (show) {
case IMA_SHOW_ASCII:
ima_show_template_data_ascii(m, show, datafmt, field_data);
break;
case IMA_SHOW_BINARY:
case IMA_SHOW_BINARY_NO_FIELD_LEN:
case IMA_SHOW_BINARY_OLD_STRING_FMT:
ima_show_template_data_binary(m, show, datafmt, field_data);
break;
default:
break;
}
}
void ima_show_template_digest(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data)
{
ima_show_template_field_data(m, show, DATA_FMT_DIGEST, field_data);
}
void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data)
{
ima_show_template_field_data(m, show, DATA_FMT_DIGEST_WITH_ALGO,
field_data);
}
void ima_show_template_string(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data)
{
ima_show_template_field_data(m, show, DATA_FMT_STRING, field_data);
}
void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data)
{
ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data);
}
static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo,
struct ima_field_data *field_data)
{
/*
* digest formats:
* - DATA_FMT_DIGEST: digest
* - DATA_FMT_DIGEST_WITH_ALGO: [<hash algo>] + ':' + '\0' + digest,
* where <hash algo> is provided if the hash algoritm is not
* SHA1 or MD5
*/
u8 buffer[CRYPTO_MAX_ALG_NAME + 2 + IMA_MAX_DIGEST_SIZE] = { 0 };
enum data_formats fmt = DATA_FMT_DIGEST;
u32 offset = 0;
if (hash_algo < HASH_ALGO__LAST) {
fmt = DATA_FMT_DIGEST_WITH_ALGO;
offset += snprintf(buffer, CRYPTO_MAX_ALG_NAME + 1, "%s",
hash_algo_name[hash_algo]);
buffer[offset] = ':';
offset += 2;
}
if (digest)
memcpy(buffer + offset, digest, digestsize);
else
/*
* If digest is NULL, the event being recorded is a violation.
* Make room for the digest by increasing the offset of
* IMA_DIGEST_SIZE.
*/
offset += IMA_DIGEST_SIZE;
return ima_write_template_field_data(buffer, offset + digestsize,
fmt, field_data);
}
/*
* This function writes the digest of an event (with size limit).
*/
int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data)
{
struct {
struct ima_digest_data hdr;
char digest[IMA_MAX_DIGEST_SIZE];
} hash;
u8 *cur_digest = NULL;
u32 cur_digestsize = 0;
struct inode *inode;
int result;
memset(&hash, 0, sizeof(hash));
if (!iint) /* recording a violation. */
goto out;
if (ima_template_hash_algo_allowed(iint->ima_hash->algo)) {
cur_digest = iint->ima_hash->digest;
cur_digestsize = iint->ima_hash->length;
goto out;
}
if (!file) /* missing info to re-calculate the digest */
return -EINVAL;
inode = file_inode(file);
hash.hdr.algo = ima_template_hash_algo_allowed(ima_hash_algo) ?
ima_hash_algo : HASH_ALGO_SHA1;
result = ima_calc_file_hash(file, &hash.hdr);
if (result) {
integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode,
filename, "collect_data",
"failed", result, 0);
return result;
}
cur_digest = hash.hdr.digest;
cur_digestsize = hash.hdr.length;
out:
return ima_eventdigest_init_common(cur_digest, cur_digestsize,
HASH_ALGO__LAST, field_data);
}
/*
* This function writes the digest of an event (without size limit).
*/
int ima_eventdigest_ng_init(struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, struct ima_field_data *field_data)
{
u8 *cur_digest = NULL, hash_algo = HASH_ALGO_SHA1;
u32 cur_digestsize = 0;
/* If iint is NULL, we are recording a violation. */
if (!iint)
goto out;
cur_digest = iint->ima_hash->digest;
cur_digestsize = iint->ima_hash->length;
hash_algo = iint->ima_hash->algo;
out:
return ima_eventdigest_init_common(cur_digest, cur_digestsize,
hash_algo, field_data);
}
static int ima_eventname_init_common(struct integrity_iint_cache *iint,
struct file *file,
const unsigned char *filename,
struct ima_field_data *field_data,
bool size_limit)
{
const char *cur_filename = NULL;
u32 cur_filename_len = 0;
BUG_ON(filename == NULL && file == NULL);
if (filename) {
cur_filename = filename;
cur_filename_len = strlen(filename);
if (!size_limit || cur_filename_len <= IMA_EVENT_NAME_LEN_MAX)
goto out;
}
if (file) {
cur_filename = file->f_dentry->d_name.name;
cur_filename_len = strlen(cur_filename);
} else
/*
* Truncate filename if the latter is too long and
* the file descriptor is not available.
*/
cur_filename_len = IMA_EVENT_NAME_LEN_MAX;
out:
return ima_write_template_field_data(cur_filename, cur_filename_len,
DATA_FMT_STRING, field_data);
}
/*
* This function writes the name of an event (with size limit).
*/
int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data)
{
return ima_eventname_init_common(iint, file, filename,
field_data, true);
}
/*
* This function writes the name of an event (without size limit).
*/
int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data)
{
return ima_eventname_init_common(iint, file, filename,
field_data, false);
}
/*
* ima_eventsig_init - include the file signature as part of the template data
*/
int ima_eventsig_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data)
{
enum data_formats fmt = DATA_FMT_HEX;
int rc = 0;
if ((!xattr_value) || (xattr_value->type != EVM_IMA_XATTR_DIGSIG))
goto out;
rc = ima_write_template_field_data(xattr_value, xattr_len, fmt,
field_data);
out:
return rc;
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2013 Politecnico di Torino, Italy
* TORSEC group -- http://security.polito.it
*
* Author: Roberto Sassu <roberto.sassu@polito.it>
*
* 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.
*
* File: ima_template_lib.h
* Header for the library of supported template fields.
*/
#ifndef __LINUX_IMA_TEMPLATE_LIB_H
#define __LINUX_IMA_TEMPLATE_LIB_H
#include <linux/seq_file.h>
#include "ima.h"
void ima_show_template_digest(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
void ima_show_template_string(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data);
int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data);
int ima_eventdigest_ng_init(struct integrity_iint_cache *iint,
struct file *file, const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value,
int xattr_len, struct ima_field_data *field_data);
int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data);
int ima_eventsig_init(struct integrity_iint_cache *iint, struct file *file,
const unsigned char *filename,
struct evm_ima_xattr_data *xattr_value, int xattr_len,
struct ima_field_data *field_data);
#endif /* __LINUX_IMA_TEMPLATE_LIB_H */

View file

@ -0,0 +1,175 @@
/*
* Copyright (C) 2009-2010 IBM Corporation
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
*
* 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/types.h>
#include <linux/integrity.h>
#include <crypto/sha.h>
#include <linux/key.h>
/* iint action cache flags */
#define IMA_MEASURE 0x00000001
#define IMA_MEASURED 0x00000002
#define IMA_APPRAISE 0x00000004
#define IMA_APPRAISED 0x00000008
/*#define IMA_COLLECT 0x00000010 do not use this flag */
#define IMA_COLLECTED 0x00000020
#define IMA_AUDIT 0x00000040
#define IMA_AUDITED 0x00000080
/* iint cache flags */
#define IMA_ACTION_FLAGS 0xff000000
#define IMA_DIGSIG 0x01000000
#define IMA_DIGSIG_REQUIRED 0x02000000
#define IMA_PERMIT_DIRECTIO 0x04000000
#define IMA_NEW_FILE 0x08000000
#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \
IMA_APPRAISE_SUBMASK)
#define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_AUDITED | \
IMA_COLLECTED | IMA_APPRAISED_SUBMASK)
/* iint subaction appraise cache flags */
#define IMA_FILE_APPRAISE 0x00000100
#define IMA_FILE_APPRAISED 0x00000200
#define IMA_MMAP_APPRAISE 0x00000400
#define IMA_MMAP_APPRAISED 0x00000800
#define IMA_BPRM_APPRAISE 0x00001000
#define IMA_BPRM_APPRAISED 0x00002000
#define IMA_MODULE_APPRAISE 0x00004000
#define IMA_MODULE_APPRAISED 0x00008000
#define IMA_FIRMWARE_APPRAISE 0x00010000
#define IMA_FIRMWARE_APPRAISED 0x00020000
#define IMA_APPRAISE_SUBMASK (IMA_FILE_APPRAISE | IMA_MMAP_APPRAISE | \
IMA_BPRM_APPRAISE | IMA_MODULE_APPRAISE | \
IMA_FIRMWARE_APPRAISE)
#define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \
IMA_BPRM_APPRAISED | IMA_MODULE_APPRAISED | \
IMA_FIRMWARE_APPRAISED)
enum evm_ima_xattr_type {
IMA_XATTR_DIGEST = 0x01,
EVM_XATTR_HMAC,
EVM_IMA_XATTR_DIGSIG,
IMA_XATTR_DIGEST_NG,
IMA_XATTR_LAST
};
struct evm_ima_xattr_data {
u8 type;
u8 digest[SHA1_DIGEST_SIZE];
} __packed;
#define IMA_MAX_DIGEST_SIZE 64
struct ima_digest_data {
u8 algo;
u8 length;
union {
struct {
u8 unused;
u8 type;
} sha1;
struct {
u8 type;
u8 algo;
} ng;
u8 data[2];
} xattr;
u8 digest[0];
} __packed;
/*
* signature format v2 - for using with asymmetric keys
*/
struct signature_v2_hdr {
uint8_t type; /* xattr type */
uint8_t version; /* signature format version */
uint8_t hash_algo; /* Digest algorithm [enum pkey_hash_algo] */
uint32_t keyid; /* IMA key identifier - not X509/PGP specific */
uint16_t sig_size; /* signature size */
uint8_t sig[0]; /* signature payload */
} __packed;
/* integrity data associated with an inode */
struct integrity_iint_cache {
struct rb_node rb_node; /* rooted in integrity_iint_tree */
struct inode *inode; /* back pointer to inode in question */
u64 version; /* track inode changes */
unsigned long flags;
enum integrity_status ima_file_status:4;
enum integrity_status ima_mmap_status:4;
enum integrity_status ima_bprm_status:4;
enum integrity_status ima_module_status:4;
enum integrity_status ima_firmware_status:4;
enum integrity_status evm_status:4;
struct ima_digest_data *ima_hash;
};
/* rbtree tree calls to lookup, insert, delete
* integrity data associated with an inode.
*/
struct integrity_iint_cache *integrity_iint_find(struct inode *inode);
#define INTEGRITY_KEYRING_EVM 0
#define INTEGRITY_KEYRING_MODULE 1
#define INTEGRITY_KEYRING_IMA 2
#define INTEGRITY_KEYRING_MAX 3
#ifdef CONFIG_INTEGRITY_SIGNATURE
int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen,
const char *digest, int digestlen);
int integrity_init_keyring(const unsigned int id);
#else
static inline int integrity_digsig_verify(const unsigned int id,
const char *sig, int siglen,
const char *digest, int digestlen)
{
return -EOPNOTSUPP;
}
static inline int integrity_init_keyring(const unsigned int id)
{
return 0;
}
#endif /* CONFIG_INTEGRITY_SIGNATURE */
#ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS
int asymmetric_verify(struct key *keyring, const char *sig,
int siglen, const char *data, int datalen);
#else
static inline int asymmetric_verify(struct key *keyring, const char *sig,
int siglen, const char *data, int datalen)
{
return -EOPNOTSUPP;
}
#endif
#ifdef CONFIG_INTEGRITY_AUDIT
/* declarations */
void integrity_audit_msg(int audit_msgno, struct inode *inode,
const unsigned char *fname, const char *op,
const char *cause, int result, int info);
#else
static inline void integrity_audit_msg(int audit_msgno, struct inode *inode,
const unsigned char *fname,
const char *op, const char *cause,
int result, int info)
{
}
#endif
/* set during initialization */
extern int iint_initialized;

View file

@ -0,0 +1,65 @@
/*
* Copyright (C) 2008 IBM Corporation
* Author: Mimi Zohar <zohar@us.ibm.com>
*
* 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.
*
* File: integrity_audit.c
* Audit calls for the integrity subsystem
*/
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/audit.h>
#include "integrity.h"
static int integrity_audit_info;
/* ima_audit_setup - enable informational auditing messages */
static int __init integrity_audit_setup(char *str)
{
unsigned long audit;
if (!kstrtoul(str, 0, &audit))
integrity_audit_info = audit ? 1 : 0;
return 1;
}
__setup("integrity_audit=", integrity_audit_setup);
void integrity_audit_msg(int audit_msgno, struct inode *inode,
const unsigned char *fname, const char *op,
const char *cause, int result, int audit_info)
{
struct audit_buffer *ab;
char name[TASK_COMM_LEN];
if (!integrity_audit_info && audit_info == 1) /* Skip info messages */
return;
ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno);
audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u",
task_pid_nr(current),
from_kuid(&init_user_ns, current_cred()->uid),
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current));
audit_log_task_context(ab);
audit_log_format(ab, " op=");
audit_log_string(ab, op);
audit_log_format(ab, " cause=");
audit_log_string(ab, cause);
audit_log_format(ab, " comm=");
audit_log_untrustedstring(ab, get_task_comm(name, current));
if (fname) {
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, fname);
}
if (inode) {
audit_log_format(ab, " dev=");
audit_log_untrustedstring(ab, inode->i_sb->s_id);
audit_log_format(ab, " ino=%lu", inode->i_ino);
}
audit_log_format(ab, " res=%d", !result);
audit_log_end(ab);
}

100
security/keys/Kconfig Normal file
View file

@ -0,0 +1,100 @@
#
# Key management configuration
#
config KEYS
bool "Enable access key retention support"
select ASSOCIATIVE_ARRAY
help
This option provides support for retaining authentication tokens and
access keys in the kernel.
It also includes provision of methods by which such keys might be
associated with a process so that network filesystems, encryption
support and the like can find them.
Furthermore, a special type of key is available that acts as keyring:
a searchable sequence of keys. Each process is equipped with access
to five standard keyrings: UID-specific, GID-specific, session,
process and thread.
If you are unsure as to whether this is required, answer N.
config PERSISTENT_KEYRINGS
bool "Enable register of persistent per-UID keyrings"
depends on KEYS
help
This option provides a register of persistent per-UID keyrings,
primarily aimed at Kerberos key storage. The keyrings are persistent
in the sense that they stay around after all processes of that UID
have exited, not that they survive the machine being rebooted.
A particular keyring may be accessed by either the user whose keyring
it is or by a process with administrative privileges. The active
LSMs gets to rule on which admin-level processes get to access the
cache.
Keyrings are created and added into the register upon demand and get
removed if they expire (a default timeout is set upon creation).
config BIG_KEYS
bool "Large payload keys"
depends on KEYS
depends on TMPFS
help
This option provides support for holding large keys within the kernel
(for example Kerberos ticket caches). The data may be stored out to
swapspace by tmpfs.
If you are unsure as to whether this is required, answer N.
config TRUSTED_KEYS
tristate "TRUSTED KEYS"
depends on KEYS && TCG_TPM
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_SHA1
help
This option provides support for creating, sealing, and unsealing
keys in the kernel. Trusted keys are random number symmetric keys,
generated and RSA-sealed by the TPM. The TPM only unseals the keys,
if the boot PCRs and other criteria match. Userspace will only ever
see encrypted blobs.
If you are unsure as to whether this is required, answer N.
config ENCRYPTED_KEYS
tristate "ENCRYPTED KEYS"
depends on KEYS
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_AES
select CRYPTO_CBC
select CRYPTO_SHA256
select CRYPTO_RNG
help
This option provides support for create/encrypting/decrypting keys
in the kernel. Encrypted keys are kernel generated random numbers,
which are encrypted/decrypted with a 'master' symmetric key. The
'master' key can be either a trusted-key or user-key type.
Userspace only ever sees/stores encrypted blobs.
If you are unsure as to whether this is required, answer N.
config KEYS_DEBUG_PROC_KEYS
bool "Enable the /proc/keys file by which keys may be viewed"
depends on KEYS
help
This option turns on support for the /proc/keys file - through which
can be listed all the keys on the system that are viewable by the
reading process.
The only keys included in the list are those that grant View
permission to the reading process whether or not it possesses them.
Note that LSM security checks are still performed, and may further
filter out keys that the current process is not authorised to view.
Only key attributes are listed here; key payloads are not included in
the resulting table.
If you are unsure as to whether this is required, answer N.

28
security/keys/Makefile Normal file
View file

@ -0,0 +1,28 @@
#
# Makefile for key management
#
#
# Core
#
obj-y := \
gc.o \
key.o \
keyring.o \
keyctl.o \
permission.o \
process_keys.o \
request_key.o \
request_key_auth.o \
user_defined.o
obj-$(CONFIG_KEYS_COMPAT) += compat.o
obj-$(CONFIG_PROC_FS) += proc.o
obj-$(CONFIG_SYSCTL) += sysctl.o
obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o
#
# Key types
#
obj-$(CONFIG_BIG_KEYS) += big_key.o
obj-$(CONFIG_TRUSTED_KEYS) += trusted.o
obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/

214
security/keys/big_key.c Normal file
View file

@ -0,0 +1,214 @@
/* Large capacity key type
*
* Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public Licence
* as published by the Free Software Foundation; either version
* 2 of the Licence, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/seq_file.h>
#include <linux/file.h>
#include <linux/shmem_fs.h>
#include <linux/err.h>
#include <keys/user-type.h>
#include <keys/big_key-type.h>
MODULE_LICENSE("GPL");
/*
* If the data is under this limit, there's no point creating a shm file to
* hold it as the permanently resident metadata for the shmem fs will be at
* least as large as the data.
*/
#define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry))
/*
* big_key defined keys take an arbitrary string as the description and an
* arbitrary blob of data as the payload
*/
struct key_type key_type_big_key = {
.name = "big_key",
.preparse = big_key_preparse,
.free_preparse = big_key_free_preparse,
.instantiate = generic_key_instantiate,
.revoke = big_key_revoke,
.destroy = big_key_destroy,
.describe = big_key_describe,
.read = big_key_read,
};
/*
* Preparse a big key
*/
int big_key_preparse(struct key_preparsed_payload *prep)
{
struct path *path = (struct path *)&prep->payload;
struct file *file;
ssize_t written;
size_t datalen = prep->datalen;
int ret;
ret = -EINVAL;
if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data)
goto error;
/* Set an arbitrary quota */
prep->quotalen = 16;
prep->type_data[1] = (void *)(unsigned long)datalen;
if (datalen > BIG_KEY_FILE_THRESHOLD) {
/* Create a shmem file to store the data in. This will permit the data
* to be swapped out if needed.
*
* TODO: Encrypt the stored data with a temporary key.
*/
file = shmem_kernel_file_setup("", datalen, 0);
if (IS_ERR(file)) {
ret = PTR_ERR(file);
goto error;
}
written = kernel_write(file, prep->data, prep->datalen, 0);
if (written != datalen) {
ret = written;
if (written >= 0)
ret = -ENOMEM;
goto err_fput;
}
/* Pin the mount and dentry to the key so that we can open it again
* later
*/
*path = file->f_path;
path_get(path);
fput(file);
} else {
/* Just store the data in a buffer */
void *data = kmalloc(datalen, GFP_KERNEL);
if (!data)
return -ENOMEM;
prep->payload[0] = memcpy(data, prep->data, prep->datalen);
}
return 0;
err_fput:
fput(file);
error:
return ret;
}
/*
* Clear preparsement.
*/
void big_key_free_preparse(struct key_preparsed_payload *prep)
{
if (prep->datalen > BIG_KEY_FILE_THRESHOLD) {
struct path *path = (struct path *)&prep->payload;
path_put(path);
} else {
kfree(prep->payload[0]);
}
}
/*
* dispose of the links from a revoked keyring
* - called with the key sem write-locked
*/
void big_key_revoke(struct key *key)
{
struct path *path = (struct path *)&key->payload.data2;
/* clear the quota */
key_payload_reserve(key, 0);
if (key_is_instantiated(key) && key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD)
vfs_truncate(path, 0);
}
/*
* dispose of the data dangling from the corpse of a big_key key
*/
void big_key_destroy(struct key *key)
{
if (key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD) {
struct path *path = (struct path *)&key->payload.data2;
path_put(path);
path->mnt = NULL;
path->dentry = NULL;
} else {
kfree(key->payload.data);
key->payload.data = NULL;
}
}
/*
* describe the big_key key
*/
void big_key_describe(const struct key *key, struct seq_file *m)
{
unsigned long datalen = key->type_data.x[1];
seq_puts(m, key->description);
if (key_is_instantiated(key))
seq_printf(m, ": %lu [%s]",
datalen,
datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff");
}
/*
* read the key data
* - the key's semaphore is read-locked
*/
long big_key_read(const struct key *key, char __user *buffer, size_t buflen)
{
unsigned long datalen = key->type_data.x[1];
long ret;
if (!buffer || buflen < datalen)
return datalen;
if (datalen > BIG_KEY_FILE_THRESHOLD) {
struct path *path = (struct path *)&key->payload.data2;
struct file *file;
loff_t pos;
file = dentry_open(path, O_RDONLY, current_cred());
if (IS_ERR(file))
return PTR_ERR(file);
pos = 0;
ret = vfs_read(file, buffer, datalen, &pos);
fput(file);
if (ret >= 0 && ret != datalen)
ret = -EIO;
} else {
ret = datalen;
if (copy_to_user(buffer, key->payload.data, datalen) != 0)
ret = -EFAULT;
}
return ret;
}
/*
* Module stuff
*/
static int __init big_key_init(void)
{
return register_key_type(&key_type_big_key);
}
static void __exit big_key_cleanup(void)
{
unregister_key_type(&key_type_big_key);
}
module_init(big_key_init);
module_exit(big_key_cleanup);

147
security/keys/compat.c Normal file
View file

@ -0,0 +1,147 @@
/* 32-bit compatibility syscall for 64-bit systems
*
* Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/syscalls.h>
#include <linux/keyctl.h>
#include <linux/compat.h>
#include <linux/slab.h>
#include "internal.h"
/*
* Instantiate a key with the specified compatibility multipart payload and
* link the key into the destination keyring if one is given.
*
* The caller must have the appropriate instantiation permit set for this to
* work (see keyctl_assume_authority). No other permissions are required.
*
* If successful, 0 will be returned.
*/
static long compat_keyctl_instantiate_key_iov(
key_serial_t id,
const struct compat_iovec __user *_payload_iov,
unsigned ioc,
key_serial_t ringid)
{
struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
long ret;
if (!_payload_iov || !ioc)
goto no_payload;
ret = compat_rw_copy_check_uvector(WRITE, _payload_iov, ioc,
ARRAY_SIZE(iovstack),
iovstack, &iov);
if (ret < 0)
goto err;
if (ret == 0)
goto no_payload_free;
ret = keyctl_instantiate_key_common(id, iov, ioc, ret, ringid);
err:
if (iov != iovstack)
kfree(iov);
return ret;
no_payload_free:
if (iov != iovstack)
kfree(iov);
no_payload:
return keyctl_instantiate_key_common(id, NULL, 0, 0, ringid);
}
/*
* The key control system call, 32-bit compatibility version for 64-bit archs
*
* This should only be called if the 64-bit arch uses weird pointers in 32-bit
* mode or doesn't guarantee that the top 32-bits of the argument registers on
* taking a 32-bit syscall are zero. If you can, you should call sys_keyctl()
* directly.
*/
COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
u32, arg2, u32, arg3, u32, arg4, u32, arg5)
{
switch (option) {
case KEYCTL_GET_KEYRING_ID:
return keyctl_get_keyring_ID(arg2, arg3);
case KEYCTL_JOIN_SESSION_KEYRING:
return keyctl_join_session_keyring(compat_ptr(arg2));
case KEYCTL_UPDATE:
return keyctl_update_key(arg2, compat_ptr(arg3), arg4);
case KEYCTL_REVOKE:
return keyctl_revoke_key(arg2);
case KEYCTL_DESCRIBE:
return keyctl_describe_key(arg2, compat_ptr(arg3), arg4);
case KEYCTL_CLEAR:
return keyctl_keyring_clear(arg2);
case KEYCTL_LINK:
return keyctl_keyring_link(arg2, arg3);
case KEYCTL_UNLINK:
return keyctl_keyring_unlink(arg2, arg3);
case KEYCTL_SEARCH:
return keyctl_keyring_search(arg2, compat_ptr(arg3),
compat_ptr(arg4), arg5);
case KEYCTL_READ:
return keyctl_read_key(arg2, compat_ptr(arg3), arg4);
case KEYCTL_CHOWN:
return keyctl_chown_key(arg2, arg3, arg4);
case KEYCTL_SETPERM:
return keyctl_setperm_key(arg2, arg3);
case KEYCTL_INSTANTIATE:
return keyctl_instantiate_key(arg2, compat_ptr(arg3), arg4,
arg5);
case KEYCTL_NEGATE:
return keyctl_negate_key(arg2, arg3, arg4);
case KEYCTL_SET_REQKEY_KEYRING:
return keyctl_set_reqkey_keyring(arg2);
case KEYCTL_SET_TIMEOUT:
return keyctl_set_timeout(arg2, arg3);
case KEYCTL_ASSUME_AUTHORITY:
return keyctl_assume_authority(arg2);
case KEYCTL_GET_SECURITY:
return keyctl_get_security(arg2, compat_ptr(arg3), arg4);
case KEYCTL_SESSION_TO_PARENT:
return keyctl_session_to_parent();
case KEYCTL_REJECT:
return keyctl_reject_key(arg2, arg3, arg4, arg5);
case KEYCTL_INSTANTIATE_IOV:
return compat_keyctl_instantiate_key_iov(
arg2, compat_ptr(arg3), arg4, arg5);
case KEYCTL_INVALIDATE:
return keyctl_invalidate_key(arg2);
case KEYCTL_GET_PERSISTENT:
return keyctl_get_persistent(arg2, arg3);
default:
return -EOPNOTSUPP;
}
}

View file

@ -0,0 +1,10 @@
#
# Makefile for encrypted keys
#
obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys.o
encrypted-keys-y := encrypted.o ecryptfs_format.o
masterkey-$(CONFIG_TRUSTED_KEYS) := masterkey_trusted.o
masterkey-$(CONFIG_TRUSTED_KEYS)-$(CONFIG_ENCRYPTED_KEYS) := masterkey_trusted.o
encrypted-keys-y += $(masterkey-y) $(masterkey-m-m)

View file

@ -0,0 +1,81 @@
/*
* ecryptfs_format.c: helper functions for the encrypted key type
*
* Copyright (C) 2006 International Business Machines Corp.
* Copyright (C) 2010 Politecnico di Torino, Italy
* TORSEC group -- http://security.polito.it
*
* Authors:
* Michael A. Halcrow <mahalcro@us.ibm.com>
* Tyler Hicks <tyhicks@ou.edu>
* Roberto Sassu <roberto.sassu@polito.it>
*
* 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/module.h>
#include "ecryptfs_format.h"
u8 *ecryptfs_get_auth_tok_key(struct ecryptfs_auth_tok *auth_tok)
{
return auth_tok->token.password.session_key_encryption_key;
}
EXPORT_SYMBOL(ecryptfs_get_auth_tok_key);
/*
* ecryptfs_get_versions()
*
* Source code taken from the software 'ecryptfs-utils' version 83.
*
*/
void ecryptfs_get_versions(int *major, int *minor, int *file_version)
{
*major = ECRYPTFS_VERSION_MAJOR;
*minor = ECRYPTFS_VERSION_MINOR;
if (file_version)
*file_version = ECRYPTFS_SUPPORTED_FILE_VERSION;
}
EXPORT_SYMBOL(ecryptfs_get_versions);
/*
* ecryptfs_fill_auth_tok - fill the ecryptfs_auth_tok structure
*
* Fill the ecryptfs_auth_tok structure with required ecryptfs data.
* The source code is inspired to the original function generate_payload()
* shipped with the software 'ecryptfs-utils' version 83.
*
*/
int ecryptfs_fill_auth_tok(struct ecryptfs_auth_tok *auth_tok,
const char *key_desc)
{
int major, minor;
ecryptfs_get_versions(&major, &minor, NULL);
auth_tok->version = (((uint16_t)(major << 8) & 0xFF00)
| ((uint16_t)minor & 0x00FF));
auth_tok->token_type = ECRYPTFS_PASSWORD;
strncpy((char *)auth_tok->token.password.signature, key_desc,
ECRYPTFS_PASSWORD_SIG_SIZE);
auth_tok->token.password.session_key_encryption_key_bytes =
ECRYPTFS_MAX_KEY_BYTES;
/*
* Removed auth_tok->token.password.salt and
* auth_tok->token.password.session_key_encryption_key
* initialization from the original code
*/
/* TODO: Make the hash parameterizable via policy */
auth_tok->token.password.flags |=
ECRYPTFS_SESSION_KEY_ENCRYPTION_KEY_SET;
/* The kernel code will encrypt the session key. */
auth_tok->session_key.encrypted_key[0] = 0;
auth_tok->session_key.encrypted_key_size = 0;
/* Default; subject to change by kernel eCryptfs */
auth_tok->token.password.hash_algo = PGP_DIGEST_ALGO_SHA512;
auth_tok->token.password.flags &= ~(ECRYPTFS_PERSISTENT_PASSWORD);
return 0;
}
EXPORT_SYMBOL(ecryptfs_fill_auth_tok);
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,30 @@
/*
* ecryptfs_format.h: helper functions for the encrypted key type
*
* Copyright (C) 2006 International Business Machines Corp.
* Copyright (C) 2010 Politecnico di Torino, Italy
* TORSEC group -- http://security.polito.it
*
* Authors:
* Michael A. Halcrow <mahalcro@us.ibm.com>
* Tyler Hicks <tyhicks@ou.edu>
* Roberto Sassu <roberto.sassu@polito.it>
*
* 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 __KEYS_ECRYPTFS_H
#define __KEYS_ECRYPTFS_H
#include <linux/ecryptfs.h>
#define PGP_DIGEST_ALGO_SHA512 10
u8 *ecryptfs_get_auth_tok_key(struct ecryptfs_auth_tok *auth_tok);
void ecryptfs_get_versions(int *major, int *minor, int *file_version);
int ecryptfs_fill_auth_tok(struct ecryptfs_auth_tok *auth_tok,
const char *key_desc);
#endif /* __KEYS_ECRYPTFS_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@
#ifndef __ENCRYPTED_KEY_H
#define __ENCRYPTED_KEY_H
#define ENCRYPTED_DEBUG 0
#if defined(CONFIG_TRUSTED_KEYS) || \
(defined(CONFIG_TRUSTED_KEYS_MODULE) && defined(CONFIG_ENCRYPTED_KEYS_MODULE))
extern struct key *request_trusted_key(const char *trusted_desc,
u8 **master_key, size_t *master_keylen);
#else
static inline struct key *request_trusted_key(const char *trusted_desc,
u8 **master_key,
size_t *master_keylen)
{
return ERR_PTR(-EOPNOTSUPP);
}
#endif
#if ENCRYPTED_DEBUG
static inline void dump_master_key(const u8 *master_key, size_t master_keylen)
{
print_hex_dump(KERN_ERR, "master key: ", DUMP_PREFIX_NONE, 32, 1,
master_key, master_keylen, 0);
}
static inline void dump_decrypted_data(struct encrypted_key_payload *epayload)
{
print_hex_dump(KERN_ERR, "decrypted data: ", DUMP_PREFIX_NONE, 32, 1,
epayload->decrypted_data,
epayload->decrypted_datalen, 0);
}
static inline void dump_encrypted_data(struct encrypted_key_payload *epayload,
unsigned int encrypted_datalen)
{
print_hex_dump(KERN_ERR, "encrypted data: ", DUMP_PREFIX_NONE, 32, 1,
epayload->encrypted_data, encrypted_datalen, 0);
}
static inline void dump_hmac(const char *str, const u8 *digest,
unsigned int hmac_size)
{
if (str)
pr_info("encrypted_key: %s", str);
print_hex_dump(KERN_ERR, "hmac: ", DUMP_PREFIX_NONE, 32, 1, digest,
hmac_size, 0);
}
#else
static inline void dump_master_key(const u8 *master_key, size_t master_keylen)
{
}
static inline void dump_decrypted_data(struct encrypted_key_payload *epayload)
{
}
static inline void dump_encrypted_data(struct encrypted_key_payload *epayload,
unsigned int encrypted_datalen)
{
}
static inline void dump_hmac(const char *str, const u8 *digest,
unsigned int hmac_size)
{
}
#endif
#endif

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2010 IBM Corporation
* Copyright (C) 2010 Politecnico di Torino, Italy
* TORSEC group -- http://security.polito.it
*
* Authors:
* Mimi Zohar <zohar@us.ibm.com>
* Roberto Sassu <roberto.sassu@polito.it>
*
* 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.
*
* See Documentation/security/keys-trusted-encrypted.txt
*/
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/err.h>
#include <keys/trusted-type.h>
#include <keys/encrypted-type.h>
#include "encrypted.h"
/*
* request_trusted_key - request the trusted key
*
* Trusted keys are sealed to PCRs and other metadata. Although userspace
* manages both trusted/encrypted key-types, like the encrypted key type
* data, trusted key type data is not visible decrypted from userspace.
*/
struct key *request_trusted_key(const char *trusted_desc,
u8 **master_key, size_t *master_keylen)
{
struct trusted_key_payload *tpayload;
struct key *tkey;
tkey = request_key(&key_type_trusted, trusted_desc, NULL);
if (IS_ERR(tkey))
goto error;
down_read(&tkey->sem);
tpayload = tkey->payload.data;
*master_key = tpayload->key;
*master_keylen = tpayload->key_len;
error:
return tkey;
}

358
security/keys/gc.c Normal file
View file

@ -0,0 +1,358 @@
/* Key garbage collector
*
* Copyright (C) 2009-2011 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public Licence
* as published by the Free Software Foundation; either version
* 2 of the Licence, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/security.h>
#include <keys/keyring-type.h>
#include "internal.h"
/*
* Delay between key revocation/expiry in seconds
*/
unsigned key_gc_delay = 5 * 60;
/*
* Reaper for unused keys.
*/
static void key_garbage_collector(struct work_struct *work);
DECLARE_WORK(key_gc_work, key_garbage_collector);
/*
* Reaper for links from keyrings to dead keys.
*/
static void key_gc_timer_func(unsigned long);
static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0);
static time_t key_gc_next_run = LONG_MAX;
static struct key_type *key_gc_dead_keytype;
static unsigned long key_gc_flags;
#define KEY_GC_KEY_EXPIRED 0 /* A key expired and needs unlinking */
#define KEY_GC_REAP_KEYTYPE 1 /* A keytype is being unregistered */
#define KEY_GC_REAPING_KEYTYPE 2 /* Cleared when keytype reaped */
/*
* Any key whose type gets unregistered will be re-typed to this if it can't be
* immediately unlinked.
*/
struct key_type key_type_dead = {
.name = "dead",
};
/*
* Schedule a garbage collection run.
* - time precision isn't particularly important
*/
void key_schedule_gc(time_t gc_at)
{
unsigned long expires;
time_t now = current_kernel_time().tv_sec;
kenter("%ld", gc_at - now);
if (gc_at <= now || test_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) {
kdebug("IMMEDIATE");
schedule_work(&key_gc_work);
} else if (gc_at < key_gc_next_run) {
kdebug("DEFERRED");
key_gc_next_run = gc_at;
expires = jiffies + (gc_at - now) * HZ;
mod_timer(&key_gc_timer, expires);
}
}
/*
* Schedule a dead links collection run.
*/
void key_schedule_gc_links(void)
{
set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags);
schedule_work(&key_gc_work);
}
/*
* Some key's cleanup time was met after it expired, so we need to get the
* reaper to go through a cycle finding expired keys.
*/
static void key_gc_timer_func(unsigned long data)
{
kenter("");
key_gc_next_run = LONG_MAX;
key_schedule_gc_links();
}
/*
* Reap keys of dead type.
*
* We use three flags to make sure we see three complete cycles of the garbage
* collector: the first to mark keys of that type as being dead, the second to
* collect dead links and the third to clean up the dead keys. We have to be
* careful as there may already be a cycle in progress.
*
* The caller must be holding key_types_sem.
*/
void key_gc_keytype(struct key_type *ktype)
{
kenter("%s", ktype->name);
key_gc_dead_keytype = ktype;
set_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags);
smp_mb();
set_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags);
kdebug("schedule");
schedule_work(&key_gc_work);
kdebug("sleep");
wait_on_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE,
TASK_UNINTERRUPTIBLE);
key_gc_dead_keytype = NULL;
kleave("");
}
/*
* Garbage collect a list of unreferenced, detached keys
*/
static noinline void key_gc_unused_keys(struct list_head *keys)
{
while (!list_empty(keys)) {
struct key *key =
list_entry(keys->next, struct key, graveyard_link);
list_del(&key->graveyard_link);
kdebug("- %u", key->serial);
key_check(key);
security_key_free(key);
/* deal with the user's key tracking and quota */
if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
spin_lock(&key->user->lock);
key->user->qnkeys--;
key->user->qnbytes -= key->quotalen;
spin_unlock(&key->user->lock);
}
atomic_dec(&key->user->nkeys);
if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
atomic_dec(&key->user->nikeys);
/* now throw away the key memory */
if (key->type->destroy)
key->type->destroy(key);
key_user_put(key->user);
kfree(key->description);
#ifdef KEY_DEBUGGING
key->magic = KEY_DEBUG_MAGIC_X;
#endif
kmem_cache_free(key_jar, key);
}
}
/*
* Garbage collector for unused keys.
*
* This is done in process context so that we don't have to disable interrupts
* all over the place. key_put() schedules this rather than trying to do the
* cleanup itself, which means key_put() doesn't have to sleep.
*/
static void key_garbage_collector(struct work_struct *work)
{
static LIST_HEAD(graveyard);
static u8 gc_state; /* Internal persistent state */
#define KEY_GC_REAP_AGAIN 0x01 /* - Need another cycle */
#define KEY_GC_REAPING_LINKS 0x02 /* - We need to reap links */
#define KEY_GC_SET_TIMER 0x04 /* - We need to restart the timer */
#define KEY_GC_REAPING_DEAD_1 0x10 /* - We need to mark dead keys */
#define KEY_GC_REAPING_DEAD_2 0x20 /* - We need to reap dead key links */
#define KEY_GC_REAPING_DEAD_3 0x40 /* - We need to reap dead keys */
#define KEY_GC_FOUND_DEAD_KEY 0x80 /* - We found at least one dead key */
struct rb_node *cursor;
struct key *key;
time_t new_timer, limit;
kenter("[%lx,%x]", key_gc_flags, gc_state);
limit = current_kernel_time().tv_sec;
if (limit > key_gc_delay)
limit -= key_gc_delay;
else
limit = key_gc_delay;
/* Work out what we're going to be doing in this pass */
gc_state &= KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2;
gc_state <<= 1;
if (test_and_clear_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags))
gc_state |= KEY_GC_REAPING_LINKS | KEY_GC_SET_TIMER;
if (test_and_clear_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags))
gc_state |= KEY_GC_REAPING_DEAD_1;
kdebug("new pass %x", gc_state);
new_timer = LONG_MAX;
/* As only this function is permitted to remove things from the key
* serial tree, if cursor is non-NULL then it will always point to a
* valid node in the tree - even if lock got dropped.
*/
spin_lock(&key_serial_lock);
cursor = rb_first(&key_serial_tree);
continue_scanning:
while (cursor) {
key = rb_entry(cursor, struct key, serial_node);
cursor = rb_next(cursor);
if (atomic_read(&key->usage) == 0)
goto found_unreferenced_key;
if (unlikely(gc_state & KEY_GC_REAPING_DEAD_1)) {
if (key->type == key_gc_dead_keytype) {
gc_state |= KEY_GC_FOUND_DEAD_KEY;
set_bit(KEY_FLAG_DEAD, &key->flags);
key->perm = 0;
goto skip_dead_key;
}
}
if (gc_state & KEY_GC_SET_TIMER) {
if (key->expiry > limit && key->expiry < new_timer) {
kdebug("will expire %x in %ld",
key_serial(key), key->expiry - limit);
new_timer = key->expiry;
}
}
if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2))
if (key->type == key_gc_dead_keytype)
gc_state |= KEY_GC_FOUND_DEAD_KEY;
if ((gc_state & KEY_GC_REAPING_LINKS) ||
unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) {
if (key->type == &key_type_keyring)
goto found_keyring;
}
if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3))
if (key->type == key_gc_dead_keytype)
goto destroy_dead_key;
skip_dead_key:
if (spin_is_contended(&key_serial_lock) || need_resched())
goto contended;
}
contended:
spin_unlock(&key_serial_lock);
maybe_resched:
if (cursor) {
cond_resched();
spin_lock(&key_serial_lock);
goto continue_scanning;
}
/* We've completed the pass. Set the timer if we need to and queue a
* new cycle if necessary. We keep executing cycles until we find one
* where we didn't reap any keys.
*/
kdebug("pass complete");
if (gc_state & KEY_GC_SET_TIMER && new_timer != (time_t)LONG_MAX) {
new_timer += key_gc_delay;
key_schedule_gc(new_timer);
}
if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2) ||
!list_empty(&graveyard)) {
/* Make sure that all pending keyring payload destructions are
* fulfilled and that people aren't now looking at dead or
* dying keys that they don't have a reference upon or a link
* to.
*/
kdebug("gc sync");
synchronize_rcu();
}
if (!list_empty(&graveyard)) {
kdebug("gc keys");
key_gc_unused_keys(&graveyard);
}
if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 |
KEY_GC_REAPING_DEAD_2))) {
if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) {
/* No remaining dead keys: short circuit the remaining
* keytype reap cycles.
*/
kdebug("dead short");
gc_state &= ~(KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2);
gc_state |= KEY_GC_REAPING_DEAD_3;
} else {
gc_state |= KEY_GC_REAP_AGAIN;
}
}
if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) {
kdebug("dead wake");
smp_mb();
clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags);
wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE);
}
if (gc_state & KEY_GC_REAP_AGAIN)
schedule_work(&key_gc_work);
kleave(" [end %x]", gc_state);
return;
/* We found an unreferenced key - once we've removed it from the tree,
* we can safely drop the lock.
*/
found_unreferenced_key:
kdebug("unrefd key %d", key->serial);
rb_erase(&key->serial_node, &key_serial_tree);
spin_unlock(&key_serial_lock);
list_add_tail(&key->graveyard_link, &graveyard);
gc_state |= KEY_GC_REAP_AGAIN;
goto maybe_resched;
/* We found a keyring and we need to check the payload for links to
* dead or expired keys. We don't flag another reap immediately as we
* have to wait for the old payload to be destroyed by RCU before we
* can reap the keys to which it refers.
*/
found_keyring:
spin_unlock(&key_serial_lock);
keyring_gc(key, limit);
goto maybe_resched;
/* We found a dead key that is still referenced. Reset its type and
* destroy its payload with its semaphore held.
*/
destroy_dead_key:
spin_unlock(&key_serial_lock);
kdebug("destroy key %d", key->serial);
down_write(&key->sem);
key->type = &key_type_dead;
if (key_gc_dead_keytype->destroy)
key_gc_dead_keytype->destroy(key);
memset(&key->payload, KEY_DESTROY, sizeof(key->payload));
up_write(&key->sem);
goto maybe_resched;
}

277
security/keys/internal.h Normal file
View file

@ -0,0 +1,277 @@
/* Authentication token and access key management internal defs
*
* Copyright (C) 2003-5, 2007 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#ifndef _INTERNAL_H
#define _INTERNAL_H
#include <linux/sched.h>
#include <linux/key-type.h>
#include <linux/task_work.h>
struct iovec;
#ifdef __KDEBUG
#define kenter(FMT, ...) \
printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__)
#define kleave(FMT, ...) \
printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__)
#define kdebug(FMT, ...) \
printk(KERN_DEBUG " "FMT"\n", ##__VA_ARGS__)
#else
#define kenter(FMT, ...) \
no_printk(KERN_DEBUG "==> %s("FMT")\n", __func__, ##__VA_ARGS__)
#define kleave(FMT, ...) \
no_printk(KERN_DEBUG "<== %s()"FMT"\n", __func__, ##__VA_ARGS__)
#define kdebug(FMT, ...) \
no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__)
#endif
extern struct key_type key_type_dead;
extern struct key_type key_type_user;
extern struct key_type key_type_logon;
/*****************************************************************************/
/*
* Keep track of keys for a user.
*
* This needs to be separate to user_struct to avoid a refcount-loop
* (user_struct pins some keyrings which pin this struct).
*
* We also keep track of keys under request from userspace for this UID here.
*/
struct key_user {
struct rb_node node;
struct mutex cons_lock; /* construction initiation lock */
spinlock_t lock;
atomic_t usage; /* for accessing qnkeys & qnbytes */
atomic_t nkeys; /* number of keys */
atomic_t nikeys; /* number of instantiated keys */
kuid_t uid;
int qnkeys; /* number of keys allocated to this user */
int qnbytes; /* number of bytes allocated to this user */
};
extern struct rb_root key_user_tree;
extern spinlock_t key_user_lock;
extern struct key_user root_key_user;
extern struct key_user *key_user_lookup(kuid_t uid);
extern void key_user_put(struct key_user *user);
/*
* Key quota limits.
* - root has its own separate limits to everyone else
*/
extern unsigned key_quota_root_maxkeys;
extern unsigned key_quota_root_maxbytes;
extern unsigned key_quota_maxkeys;
extern unsigned key_quota_maxbytes;
#define KEYQUOTA_LINK_BYTES 4 /* a link in a keyring is worth 4 bytes */
extern struct kmem_cache *key_jar;
extern struct rb_root key_serial_tree;
extern spinlock_t key_serial_lock;
extern struct mutex key_construction_mutex;
extern wait_queue_head_t request_key_conswq;
extern struct key_type *key_type_lookup(const char *type);
extern void key_type_put(struct key_type *ktype);
extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit **_edit);
extern int __key_link_check_live_key(struct key *keyring, struct key *key);
extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
extern void __key_link_end(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit *edit);
extern key_ref_t find_key_to_update(key_ref_t keyring_ref,
const struct keyring_index_key *index_key);
extern struct key *keyring_search_instkey(struct key *keyring,
key_serial_t target_id);
extern int iterate_over_keyring(const struct key *keyring,
int (*func)(const struct key *key, void *data),
void *data);
struct keyring_search_context {
struct keyring_index_key index_key;
const struct cred *cred;
struct key_match_data match_data;
unsigned flags;
#define KEYRING_SEARCH_NO_STATE_CHECK 0x0001 /* Skip state checks */
#define KEYRING_SEARCH_DO_STATE_CHECK 0x0002 /* Override NO_STATE_CHECK */
#define KEYRING_SEARCH_NO_UPDATE_TIME 0x0004 /* Don't update times */
#define KEYRING_SEARCH_NO_CHECK_PERM 0x0008 /* Don't check permissions */
#define KEYRING_SEARCH_DETECT_TOO_DEEP 0x0010 /* Give an error on excessive depth */
#define KEYRING_SEARCH_SKIP_EXPIRED 0x0020 /* Ignore expired keys (intention to replace) */
int (*iterator)(const void *object, void *iterator_data);
/* Internal stuff */
int skipped_ret;
bool possessed;
key_ref_t result;
struct timespec now;
};
extern bool key_default_cmp(const struct key *key,
const struct key_match_data *match_data);
extern key_ref_t keyring_search_aux(key_ref_t keyring_ref,
struct keyring_search_context *ctx);
extern key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx);
extern key_ref_t search_process_keyrings(struct keyring_search_context *ctx);
extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check);
extern int install_user_keyrings(void);
extern int install_thread_keyring_to_cred(struct cred *);
extern int install_process_keyring_to_cred(struct cred *);
extern int install_session_keyring_to_cred(struct cred *, struct key *);
extern struct key *request_key_and_link(struct key_type *type,
const char *description,
const void *callout_info,
size_t callout_len,
void *aux,
struct key *dest_keyring,
unsigned long flags);
extern bool lookup_user_key_possessed(const struct key *key,
const struct key_match_data *match_data);
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
key_perm_t perm);
#define KEY_LOOKUP_CREATE 0x01
#define KEY_LOOKUP_PARTIAL 0x02
#define KEY_LOOKUP_FOR_UNLINK 0x04
extern long join_session_keyring(const char *name);
extern void key_change_session_keyring(struct callback_head *twork);
extern struct work_struct key_gc_work;
extern unsigned key_gc_delay;
extern void keyring_gc(struct key *keyring, time_t limit);
extern void key_schedule_gc(time_t gc_at);
extern void key_schedule_gc_links(void);
extern void key_gc_keytype(struct key_type *ktype);
extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
key_perm_t perm);
/*
* Check to see whether permission is granted to use a key in the desired way.
*/
static inline int key_permission(const key_ref_t key_ref, unsigned perm)
{
return key_task_permission(key_ref, current_cred(), perm);
}
/*
* Authorisation record for request_key().
*/
struct request_key_auth {
struct key *target_key;
struct key *dest_keyring;
const struct cred *cred;
void *callout_info;
size_t callout_len;
pid_t pid;
};
extern struct key_type key_type_request_key_auth;
extern struct key *request_key_auth_new(struct key *target,
const void *callout_info,
size_t callout_len,
struct key *dest_keyring);
extern struct key *key_get_instantiation_authkey(key_serial_t target_id);
/*
* Determine whether a key is dead.
*/
static inline bool key_is_dead(const struct key *key, time_t limit)
{
return
key->flags & ((1 << KEY_FLAG_DEAD) |
(1 << KEY_FLAG_INVALIDATED)) ||
(key->expiry > 0 && key->expiry <= limit);
}
/*
* keyctl() functions
*/
extern long keyctl_get_keyring_ID(key_serial_t, int);
extern long keyctl_join_session_keyring(const char __user *);
extern long keyctl_update_key(key_serial_t, const void __user *, size_t);
extern long keyctl_revoke_key(key_serial_t);
extern long keyctl_keyring_clear(key_serial_t);
extern long keyctl_keyring_link(key_serial_t, key_serial_t);
extern long keyctl_keyring_unlink(key_serial_t, key_serial_t);
extern long keyctl_describe_key(key_serial_t, char __user *, size_t);
extern long keyctl_keyring_search(key_serial_t, const char __user *,
const char __user *, key_serial_t);
extern long keyctl_read_key(key_serial_t, char __user *, size_t);
extern long keyctl_chown_key(key_serial_t, uid_t, gid_t);
extern long keyctl_setperm_key(key_serial_t, key_perm_t);
extern long keyctl_instantiate_key(key_serial_t, const void __user *,
size_t, key_serial_t);
extern long keyctl_negate_key(key_serial_t, unsigned, key_serial_t);
extern long keyctl_set_reqkey_keyring(int);
extern long keyctl_set_timeout(key_serial_t, unsigned);
extern long keyctl_assume_authority(key_serial_t);
extern long keyctl_get_security(key_serial_t keyid, char __user *buffer,
size_t buflen);
extern long keyctl_session_to_parent(void);
extern long keyctl_reject_key(key_serial_t, unsigned, unsigned, key_serial_t);
extern long keyctl_instantiate_key_iov(key_serial_t,
const struct iovec __user *,
unsigned, key_serial_t);
extern long keyctl_invalidate_key(key_serial_t);
extern long keyctl_instantiate_key_common(key_serial_t,
const struct iovec *,
unsigned, size_t, key_serial_t);
#ifdef CONFIG_PERSISTENT_KEYRINGS
extern long keyctl_get_persistent(uid_t, key_serial_t);
extern unsigned persistent_keyring_expiry;
#else
static inline long keyctl_get_persistent(uid_t uid, key_serial_t destring)
{
return -EOPNOTSUPP;
}
#endif
/*
* Debugging key validation
*/
#ifdef KEY_DEBUGGING
extern void __key_check(const struct key *);
static inline void key_check(const struct key *key)
{
if (key && (IS_ERR(key) || key->magic != KEY_DEBUG_MAGIC))
__key_check(key);
}
#else
#define key_check(key) do {} while(0)
#endif
#endif /* _INTERNAL_H */

1142
security/keys/key.c Normal file

File diff suppressed because it is too large Load diff

1689
security/keys/keyctl.c Normal file

File diff suppressed because it is too large Load diff

1394
security/keys/keyring.c Normal file

File diff suppressed because it is too large Load diff

110
security/keys/permission.c Normal file
View file

@ -0,0 +1,110 @@
/* Key permission checking
*
* Copyright (C) 2005 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/security.h>
#include "internal.h"
/**
* key_task_permission - Check a key can be used
* @key_ref: The key to check.
* @cred: The credentials to use.
* @perm: The permissions to check for.
*
* Check to see whether permission is granted to use a key in the desired way,
* but permit the security modules to override.
*
* The caller must hold either a ref on cred or must hold the RCU readlock.
*
* Returns 0 if successful, -EACCES if access is denied based on the
* permissions bits or the LSM check.
*/
int key_task_permission(const key_ref_t key_ref, const struct cred *cred,
unsigned perm)
{
struct key *key;
key_perm_t kperm;
int ret;
key = key_ref_to_ptr(key_ref);
/* use the second 8-bits of permissions for keys the caller owns */
if (uid_eq(key->uid, cred->fsuid)) {
kperm = key->perm >> 16;
goto use_these_perms;
}
/* use the third 8-bits of permissions for keys the caller has a group
* membership in common with */
if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) {
if (gid_eq(key->gid, cred->fsgid)) {
kperm = key->perm >> 8;
goto use_these_perms;
}
ret = groups_search(cred->group_info, key->gid);
if (ret) {
kperm = key->perm >> 8;
goto use_these_perms;
}
}
/* otherwise use the least-significant 8-bits */
kperm = key->perm;
use_these_perms:
/* use the top 8-bits of permissions for keys the caller possesses
* - possessor permissions are additive with other permissions
*/
if (is_key_possessed(key_ref))
kperm |= key->perm >> 24;
kperm = kperm & perm & KEY_NEED_ALL;
if (kperm != perm)
return -EACCES;
/* let LSM be the final arbiter */
return security_key_permission(key_ref, cred, perm);
}
EXPORT_SYMBOL(key_task_permission);
/**
* key_validate - Validate a key.
* @key: The key to be validated.
*
* Check that a key is valid, returning 0 if the key is okay, -ENOKEY if the
* key is invalidated, -EKEYREVOKED if the key's type has been removed or if
* the key has been revoked or -EKEYEXPIRED if the key has expired.
*/
int key_validate(const struct key *key)
{
unsigned long flags = key->flags;
if (flags & (1 << KEY_FLAG_INVALIDATED))
return -ENOKEY;
/* check it's still accessible */
if (flags & ((1 << KEY_FLAG_REVOKED) |
(1 << KEY_FLAG_DEAD)))
return -EKEYREVOKED;
/* check it hasn't expired */
if (key->expiry) {
struct timespec now = current_kernel_time();
if (now.tv_sec >= key->expiry)
return -EKEYEXPIRED;
}
return 0;
}
EXPORT_SYMBOL(key_validate);

167
security/keys/persistent.c Normal file
View file

@ -0,0 +1,167 @@
/* General persistent per-UID keyrings register
*
* Copyright (C) 2013 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public Licence
* as published by the Free Software Foundation; either version
* 2 of the Licence, or (at your option) any later version.
*/
#include <linux/user_namespace.h>
#include "internal.h"
unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */
/*
* Create the persistent keyring register for the current user namespace.
*
* Called with the namespace's sem locked for writing.
*/
static int key_create_persistent_register(struct user_namespace *ns)
{
struct key *reg = keyring_alloc(".persistent_register",
KUIDT_INIT(0), KGIDT_INIT(0),
current_cred(),
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
KEY_USR_VIEW | KEY_USR_READ),
KEY_ALLOC_NOT_IN_QUOTA, NULL);
if (IS_ERR(reg))
return PTR_ERR(reg);
ns->persistent_keyring_register = reg;
return 0;
}
/*
* Create the persistent keyring for the specified user.
*
* Called with the namespace's sem locked for writing.
*/
static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid,
struct keyring_index_key *index_key)
{
struct key *persistent;
key_ref_t reg_ref, persistent_ref;
if (!ns->persistent_keyring_register) {
long err = key_create_persistent_register(ns);
if (err < 0)
return ERR_PTR(err);
} else {
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
persistent_ref = find_key_to_update(reg_ref, index_key);
if (persistent_ref)
return persistent_ref;
}
persistent = keyring_alloc(index_key->description,
uid, INVALID_GID, current_cred(),
((KEY_POS_ALL & ~KEY_POS_SETATTR) |
KEY_USR_VIEW | KEY_USR_READ),
KEY_ALLOC_NOT_IN_QUOTA,
ns->persistent_keyring_register);
if (IS_ERR(persistent))
return ERR_CAST(persistent);
return make_key_ref(persistent, true);
}
/*
* Get the persistent keyring for a specific UID and link it to the nominated
* keyring.
*/
static long key_get_persistent(struct user_namespace *ns, kuid_t uid,
key_ref_t dest_ref)
{
struct keyring_index_key index_key;
struct key *persistent;
key_ref_t reg_ref, persistent_ref;
char buf[32];
long ret;
/* Look in the register if it exists */
index_key.type = &key_type_keyring;
index_key.description = buf;
index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid));
if (ns->persistent_keyring_register) {
reg_ref = make_key_ref(ns->persistent_keyring_register, true);
down_read(&ns->persistent_keyring_register_sem);
persistent_ref = find_key_to_update(reg_ref, &index_key);
up_read(&ns->persistent_keyring_register_sem);
if (persistent_ref)
goto found;
}
/* It wasn't in the register, so we'll need to create it. We might
* also need to create the register.
*/
down_write(&ns->persistent_keyring_register_sem);
persistent_ref = key_create_persistent(ns, uid, &index_key);
up_write(&ns->persistent_keyring_register_sem);
if (!IS_ERR(persistent_ref))
goto found;
return PTR_ERR(persistent_ref);
found:
ret = key_task_permission(persistent_ref, current_cred(), KEY_NEED_LINK);
if (ret == 0) {
persistent = key_ref_to_ptr(persistent_ref);
ret = key_link(key_ref_to_ptr(dest_ref), persistent);
if (ret == 0) {
key_set_timeout(persistent, persistent_keyring_expiry);
ret = persistent->serial;
}
}
key_ref_put(persistent_ref);
return ret;
}
/*
* Get the persistent keyring for a specific UID and link it to the nominated
* keyring.
*/
long keyctl_get_persistent(uid_t _uid, key_serial_t destid)
{
struct user_namespace *ns = current_user_ns();
key_ref_t dest_ref;
kuid_t uid;
long ret;
/* -1 indicates the current user */
if (_uid == (uid_t)-1) {
uid = current_uid();
} else {
uid = make_kuid(ns, _uid);
if (!uid_valid(uid))
return -EINVAL;
/* You can only see your own persistent cache if you're not
* sufficiently privileged.
*/
if (!uid_eq(uid, current_uid()) &&
!uid_eq(uid, current_euid()) &&
!ns_capable(ns, CAP_SETUID))
return -EPERM;
}
/* There must be a destination keyring */
dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_NEED_WRITE);
if (IS_ERR(dest_ref))
return PTR_ERR(dest_ref);
if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) {
ret = -ENOTDIR;
goto out_put_dest;
}
ret = key_get_persistent(ns, uid, dest_ref);
out_put_dest:
key_ref_put(dest_ref);
return ret;
}

360
security/keys/proc.c Normal file
View file

@ -0,0 +1,360 @@
/* procfs files for key database enumeration
*
* Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/errno.h>
#include "internal.h"
#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS
static int proc_keys_open(struct inode *inode, struct file *file);
static void *proc_keys_start(struct seq_file *p, loff_t *_pos);
static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos);
static void proc_keys_stop(struct seq_file *p, void *v);
static int proc_keys_show(struct seq_file *m, void *v);
static const struct seq_operations proc_keys_ops = {
.start = proc_keys_start,
.next = proc_keys_next,
.stop = proc_keys_stop,
.show = proc_keys_show,
};
static const struct file_operations proc_keys_fops = {
.open = proc_keys_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
#endif
static int proc_key_users_open(struct inode *inode, struct file *file);
static void *proc_key_users_start(struct seq_file *p, loff_t *_pos);
static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos);
static void proc_key_users_stop(struct seq_file *p, void *v);
static int proc_key_users_show(struct seq_file *m, void *v);
static const struct seq_operations proc_key_users_ops = {
.start = proc_key_users_start,
.next = proc_key_users_next,
.stop = proc_key_users_stop,
.show = proc_key_users_show,
};
static const struct file_operations proc_key_users_fops = {
.open = proc_key_users_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
/*
* Declare the /proc files.
*/
static int __init key_proc_init(void)
{
struct proc_dir_entry *p;
#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS
p = proc_create("keys", 0, NULL, &proc_keys_fops);
if (!p)
panic("Cannot create /proc/keys\n");
#endif
p = proc_create("key-users", 0, NULL, &proc_key_users_fops);
if (!p)
panic("Cannot create /proc/key-users\n");
return 0;
}
__initcall(key_proc_init);
/*
* Implement "/proc/keys" to provide a list of the keys on the system that
* grant View permission to the caller.
*/
#ifdef CONFIG_KEYS_DEBUG_PROC_KEYS
static struct rb_node *key_serial_next(struct seq_file *p, struct rb_node *n)
{
struct user_namespace *user_ns = seq_user_ns(p);
n = rb_next(n);
while (n) {
struct key *key = rb_entry(n, struct key, serial_node);
if (kuid_has_mapping(user_ns, key->user->uid))
break;
n = rb_next(n);
}
return n;
}
static int proc_keys_open(struct inode *inode, struct file *file)
{
return seq_open(file, &proc_keys_ops);
}
static struct key *find_ge_key(struct seq_file *p, key_serial_t id)
{
struct user_namespace *user_ns = seq_user_ns(p);
struct rb_node *n = key_serial_tree.rb_node;
struct key *minkey = NULL;
while (n) {
struct key *key = rb_entry(n, struct key, serial_node);
if (id < key->serial) {
if (!minkey || minkey->serial > key->serial)
minkey = key;
n = n->rb_left;
} else if (id > key->serial) {
n = n->rb_right;
} else {
minkey = key;
break;
}
key = NULL;
}
if (!minkey)
return NULL;
for (;;) {
if (kuid_has_mapping(user_ns, minkey->user->uid))
return minkey;
n = rb_next(&minkey->serial_node);
if (!n)
return NULL;
minkey = rb_entry(n, struct key, serial_node);
}
}
static void *proc_keys_start(struct seq_file *p, loff_t *_pos)
__acquires(key_serial_lock)
{
key_serial_t pos = *_pos;
struct key *key;
spin_lock(&key_serial_lock);
if (*_pos > INT_MAX)
return NULL;
key = find_ge_key(p, pos);
if (!key)
return NULL;
*_pos = key->serial;
return &key->serial_node;
}
static inline key_serial_t key_node_serial(struct rb_node *n)
{
struct key *key = rb_entry(n, struct key, serial_node);
return key->serial;
}
static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos)
{
struct rb_node *n;
n = key_serial_next(p, v);
if (n)
*_pos = key_node_serial(n);
return n;
}
static void proc_keys_stop(struct seq_file *p, void *v)
__releases(key_serial_lock)
{
spin_unlock(&key_serial_lock);
}
static int proc_keys_show(struct seq_file *m, void *v)
{
struct rb_node *_p = v;
struct key *key = rb_entry(_p, struct key, serial_node);
struct timespec now;
unsigned long timo;
key_ref_t key_ref, skey_ref;
char xbuf[12];
int rc;
struct keyring_search_context ctx = {
.index_key.type = key->type,
.index_key.description = key->description,
.cred = current_cred(),
.match_data.cmp = lookup_user_key_possessed,
.match_data.raw_data = key,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
.flags = KEYRING_SEARCH_NO_STATE_CHECK,
};
key_ref = make_key_ref(key, 0);
/* determine if the key is possessed by this process (a test we can
* skip if the key does not indicate the possessor can view it
*/
if (key->perm & KEY_POS_VIEW) {
skey_ref = search_my_process_keyrings(&ctx);
if (!IS_ERR(skey_ref)) {
key_ref_put(skey_ref);
key_ref = make_key_ref(key, 1);
}
}
/* check whether the current task is allowed to view the key (assuming
* non-possession)
* - the caller holds a spinlock, and thus the RCU read lock, making our
* access to __current_cred() safe
*/
rc = key_task_permission(key_ref, ctx.cred, KEY_NEED_VIEW);
if (rc < 0)
return 0;
now = current_kernel_time();
rcu_read_lock();
/* come up with a suitable timeout value */
if (key->expiry == 0) {
memcpy(xbuf, "perm", 5);
} else if (now.tv_sec >= key->expiry) {
memcpy(xbuf, "expd", 5);
} else {
timo = key->expiry - now.tv_sec;
if (timo < 60)
sprintf(xbuf, "%lus", timo);
else if (timo < 60*60)
sprintf(xbuf, "%lum", timo / 60);
else if (timo < 60*60*24)
sprintf(xbuf, "%luh", timo / (60*60));
else if (timo < 60*60*24*7)
sprintf(xbuf, "%lud", timo / (60*60*24));
else
sprintf(xbuf, "%luw", timo / (60*60*24*7));
}
#define showflag(KEY, LETTER, FLAG) \
(test_bit(FLAG, &(KEY)->flags) ? LETTER : '-')
seq_printf(m, "%08x %c%c%c%c%c%c%c %5d %4s %08x %5d %5d %-9.9s ",
key->serial,
showflag(key, 'I', KEY_FLAG_INSTANTIATED),
showflag(key, 'R', KEY_FLAG_REVOKED),
showflag(key, 'D', KEY_FLAG_DEAD),
showflag(key, 'Q', KEY_FLAG_IN_QUOTA),
showflag(key, 'U', KEY_FLAG_USER_CONSTRUCT),
showflag(key, 'N', KEY_FLAG_NEGATIVE),
showflag(key, 'i', KEY_FLAG_INVALIDATED),
atomic_read(&key->usage),
xbuf,
key->perm,
from_kuid_munged(seq_user_ns(m), key->uid),
from_kgid_munged(seq_user_ns(m), key->gid),
key->type->name);
#undef showflag
if (key->type->describe)
key->type->describe(key, m);
seq_putc(m, '\n');
rcu_read_unlock();
return 0;
}
#endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */
static struct rb_node *__key_user_next(struct user_namespace *user_ns, struct rb_node *n)
{
while (n) {
struct key_user *user = rb_entry(n, struct key_user, node);
if (kuid_has_mapping(user_ns, user->uid))
break;
n = rb_next(n);
}
return n;
}
static struct rb_node *key_user_next(struct user_namespace *user_ns, struct rb_node *n)
{
return __key_user_next(user_ns, rb_next(n));
}
static struct rb_node *key_user_first(struct user_namespace *user_ns, struct rb_root *r)
{
struct rb_node *n = rb_first(r);
return __key_user_next(user_ns, n);
}
/*
* Implement "/proc/key-users" to provides a list of the key users and their
* quotas.
*/
static int proc_key_users_open(struct inode *inode, struct file *file)
{
return seq_open(file, &proc_key_users_ops);
}
static void *proc_key_users_start(struct seq_file *p, loff_t *_pos)
__acquires(key_user_lock)
{
struct rb_node *_p;
loff_t pos = *_pos;
spin_lock(&key_user_lock);
_p = key_user_first(seq_user_ns(p), &key_user_tree);
while (pos > 0 && _p) {
pos--;
_p = key_user_next(seq_user_ns(p), _p);
}
return _p;
}
static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos)
{
(*_pos)++;
return key_user_next(seq_user_ns(p), (struct rb_node *)v);
}
static void proc_key_users_stop(struct seq_file *p, void *v)
__releases(key_user_lock)
{
spin_unlock(&key_user_lock);
}
static int proc_key_users_show(struct seq_file *m, void *v)
{
struct rb_node *_p = v;
struct key_user *user = rb_entry(_p, struct key_user, node);
unsigned maxkeys = uid_eq(user->uid, GLOBAL_ROOT_UID) ?
key_quota_root_maxkeys : key_quota_maxkeys;
unsigned maxbytes = uid_eq(user->uid, GLOBAL_ROOT_UID) ?
key_quota_root_maxbytes : key_quota_maxbytes;
seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n",
from_kuid_munged(seq_user_ns(m), user->uid),
atomic_read(&user->usage),
atomic_read(&user->nkeys),
atomic_read(&user->nikeys),
user->qnkeys,
maxkeys,
user->qnbytes,
maxbytes);
return 0;
}

View file

@ -0,0 +1,870 @@
/* Manage a process's keyrings
*
* Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/keyctl.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/security.h>
#include <linux/user_namespace.h>
#include <asm/uaccess.h>
#include "internal.h"
/* Session keyring create vs join semaphore */
static DEFINE_MUTEX(key_session_mutex);
/* User keyring creation semaphore */
static DEFINE_MUTEX(key_user_keyring_mutex);
/* The root user's tracking struct */
struct key_user root_key_user = {
.usage = ATOMIC_INIT(3),
.cons_lock = __MUTEX_INITIALIZER(root_key_user.cons_lock),
.lock = __SPIN_LOCK_UNLOCKED(root_key_user.lock),
.nkeys = ATOMIC_INIT(2),
.nikeys = ATOMIC_INIT(2),
.uid = GLOBAL_ROOT_UID,
};
/*
* Install the user and user session keyrings for the current process's UID.
*/
int install_user_keyrings(void)
{
struct user_struct *user;
const struct cred *cred;
struct key *uid_keyring, *session_keyring;
key_perm_t user_keyring_perm;
char buf[20];
int ret;
uid_t uid;
user_keyring_perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL;
cred = current_cred();
user = cred->user;
uid = from_kuid(cred->user_ns, user->uid);
kenter("%p{%u}", user, uid);
if (user->uid_keyring && user->session_keyring) {
kleave(" = 0 [exist]");
return 0;
}
mutex_lock(&key_user_keyring_mutex);
ret = 0;
if (!user->uid_keyring) {
/* get the UID-specific keyring
* - there may be one in existence already as it may have been
* pinned by a session, but the user_struct pointing to it
* may have been destroyed by setuid */
sprintf(buf, "_uid.%u", uid);
uid_keyring = find_keyring_by_name(buf, true);
if (IS_ERR(uid_keyring)) {
uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID,
cred, user_keyring_perm,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(uid_keyring)) {
ret = PTR_ERR(uid_keyring);
goto error;
}
}
/* get a default session keyring (which might also exist
* already) */
sprintf(buf, "_uid_ses.%u", uid);
session_keyring = find_keyring_by_name(buf, true);
if (IS_ERR(session_keyring)) {
session_keyring =
keyring_alloc(buf, user->uid, INVALID_GID,
cred, user_keyring_perm,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(session_keyring)) {
ret = PTR_ERR(session_keyring);
goto error_release;
}
/* we install a link from the user session keyring to
* the user keyring */
ret = key_link(session_keyring, uid_keyring);
if (ret < 0)
goto error_release_both;
}
/* install the keyrings */
user->uid_keyring = uid_keyring;
user->session_keyring = session_keyring;
}
mutex_unlock(&key_user_keyring_mutex);
kleave(" = 0");
return 0;
error_release_both:
key_put(session_keyring);
error_release:
key_put(uid_keyring);
error:
mutex_unlock(&key_user_keyring_mutex);
kleave(" = %d", ret);
return ret;
}
/*
* Install a fresh thread keyring directly to new credentials. This keyring is
* allowed to overrun the quota.
*/
int install_thread_keyring_to_cred(struct cred *new)
{
struct key *keyring;
keyring = keyring_alloc("_tid", new->uid, new->gid, new,
KEY_POS_ALL | KEY_USR_VIEW,
KEY_ALLOC_QUOTA_OVERRUN, NULL);
if (IS_ERR(keyring))
return PTR_ERR(keyring);
new->thread_keyring = keyring;
return 0;
}
/*
* Install a fresh thread keyring, discarding the old one.
*/
static int install_thread_keyring(void)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
BUG_ON(new->thread_keyring);
ret = install_thread_keyring_to_cred(new);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
/*
* Install a process keyring directly to a credentials struct.
*
* Returns -EEXIST if there was already a process keyring, 0 if one installed,
* and other value on any other error
*/
int install_process_keyring_to_cred(struct cred *new)
{
struct key *keyring;
if (new->process_keyring)
return -EEXIST;
keyring = keyring_alloc("_pid", new->uid, new->gid, new,
KEY_POS_ALL | KEY_USR_VIEW,
KEY_ALLOC_QUOTA_OVERRUN, NULL);
if (IS_ERR(keyring))
return PTR_ERR(keyring);
new->process_keyring = keyring;
return 0;
}
/*
* Make sure a process keyring is installed for the current process. The
* existing process keyring is not replaced.
*
* Returns 0 if there is a process keyring by the end of this function, some
* error otherwise.
*/
static int install_process_keyring(void)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = install_process_keyring_to_cred(new);
if (ret < 0) {
abort_creds(new);
return ret != -EEXIST ? ret : 0;
}
return commit_creds(new);
}
/*
* Install a session keyring directly to a credentials struct.
*/
int install_session_keyring_to_cred(struct cred *cred, struct key *keyring)
{
unsigned long flags;
struct key *old;
might_sleep();
/* create an empty session keyring */
if (!keyring) {
flags = KEY_ALLOC_QUOTA_OVERRUN;
if (cred->session_keyring)
flags = KEY_ALLOC_IN_QUOTA;
keyring = keyring_alloc("_ses", cred->uid, cred->gid, cred,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
flags, NULL);
if (IS_ERR(keyring))
return PTR_ERR(keyring);
} else {
__key_get(keyring);
}
/* install the keyring */
old = cred->session_keyring;
rcu_assign_pointer(cred->session_keyring, keyring);
if (old)
key_put(old);
return 0;
}
/*
* Install a session keyring, discarding the old one. If a keyring is not
* supplied, an empty one is invented.
*/
static int install_session_keyring(struct key *keyring)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
/*
* Handle the fsuid changing.
*/
void key_fsuid_changed(struct task_struct *tsk)
{
/* update the ownership of the thread keyring */
BUG_ON(!tsk->cred);
if (tsk->cred->thread_keyring) {
down_write(&tsk->cred->thread_keyring->sem);
tsk->cred->thread_keyring->uid = tsk->cred->fsuid;
up_write(&tsk->cred->thread_keyring->sem);
}
}
/*
* Handle the fsgid changing.
*/
void key_fsgid_changed(struct task_struct *tsk)
{
/* update the ownership of the thread keyring */
BUG_ON(!tsk->cred);
if (tsk->cred->thread_keyring) {
down_write(&tsk->cred->thread_keyring->sem);
tsk->cred->thread_keyring->gid = tsk->cred->fsgid;
up_write(&tsk->cred->thread_keyring->sem);
}
}
/*
* Search the process keyrings attached to the supplied cred for the first
* matching key.
*
* The search criteria are the type and the match function. The description is
* given to the match function as a parameter, but doesn't otherwise influence
* the search. Typically the match function will compare the description
* parameter to the key's description.
*
* This can only search keyrings that grant Search permission to the supplied
* credentials. Keyrings linked to searched keyrings will also be searched if
* they grant Search permission too. Keys can only be found if they grant
* Search permission to the credentials.
*
* Returns a pointer to the key with the key usage count incremented if
* successful, -EAGAIN if we didn't find any matching key or -ENOKEY if we only
* matched negative keys.
*
* In the case of a successful return, the possession attribute is set on the
* returned key reference.
*/
key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx)
{
key_ref_t key_ref, ret, err;
/* we want to return -EAGAIN or -ENOKEY if any of the keyrings were
* searchable, but we failed to find a key or we found a negative key;
* otherwise we want to return a sample error (probably -EACCES) if
* none of the keyrings were searchable
*
* in terms of priority: success > -ENOKEY > -EAGAIN > other error
*/
key_ref = NULL;
ret = NULL;
err = ERR_PTR(-EAGAIN);
/* search the thread keyring first */
if (ctx->cred->thread_keyring) {
key_ref = keyring_search_aux(
make_key_ref(ctx->cred->thread_keyring, 1), ctx);
if (!IS_ERR(key_ref))
goto found;
switch (PTR_ERR(key_ref)) {
case -EAGAIN: /* no key */
case -ENOKEY: /* negative key */
ret = key_ref;
break;
default:
err = key_ref;
break;
}
}
/* search the process keyring second */
if (ctx->cred->process_keyring) {
key_ref = keyring_search_aux(
make_key_ref(ctx->cred->process_keyring, 1), ctx);
if (!IS_ERR(key_ref))
goto found;
switch (PTR_ERR(key_ref)) {
case -EAGAIN: /* no key */
if (ret)
break;
case -ENOKEY: /* negative key */
ret = key_ref;
break;
default:
err = key_ref;
break;
}
}
/* search the session keyring */
if (ctx->cred->session_keyring) {
rcu_read_lock();
key_ref = keyring_search_aux(
make_key_ref(rcu_dereference(ctx->cred->session_keyring), 1),
ctx);
rcu_read_unlock();
if (!IS_ERR(key_ref))
goto found;
switch (PTR_ERR(key_ref)) {
case -EAGAIN: /* no key */
if (ret)
break;
case -ENOKEY: /* negative key */
ret = key_ref;
break;
default:
err = key_ref;
break;
}
}
/* or search the user-session keyring */
else if (ctx->cred->user->session_keyring) {
key_ref = keyring_search_aux(
make_key_ref(ctx->cred->user->session_keyring, 1),
ctx);
if (!IS_ERR(key_ref))
goto found;
switch (PTR_ERR(key_ref)) {
case -EAGAIN: /* no key */
if (ret)
break;
case -ENOKEY: /* negative key */
ret = key_ref;
break;
default:
err = key_ref;
break;
}
}
/* no key - decide on the error we're going to go for */
key_ref = ret ? ret : err;
found:
return key_ref;
}
/*
* Search the process keyrings attached to the supplied cred for the first
* matching key in the manner of search_my_process_keyrings(), but also search
* the keys attached to the assumed authorisation key using its credentials if
* one is available.
*
* Return same as search_my_process_keyrings().
*/
key_ref_t search_process_keyrings(struct keyring_search_context *ctx)
{
struct request_key_auth *rka;
key_ref_t key_ref, ret = ERR_PTR(-EACCES), err;
might_sleep();
key_ref = search_my_process_keyrings(ctx);
if (!IS_ERR(key_ref))
goto found;
err = key_ref;
/* if this process has an instantiation authorisation key, then we also
* search the keyrings of the process mentioned there
* - we don't permit access to request_key auth keys via this method
*/
if (ctx->cred->request_key_auth &&
ctx->cred == current_cred() &&
ctx->index_key.type != &key_type_request_key_auth
) {
const struct cred *cred = ctx->cred;
/* defend against the auth key being revoked */
down_read(&cred->request_key_auth->sem);
if (key_validate(ctx->cred->request_key_auth) == 0) {
rka = ctx->cred->request_key_auth->payload.data;
ctx->cred = rka->cred;
key_ref = search_process_keyrings(ctx);
ctx->cred = cred;
up_read(&cred->request_key_auth->sem);
if (!IS_ERR(key_ref))
goto found;
ret = key_ref;
} else {
up_read(&cred->request_key_auth->sem);
}
}
/* no key - decide on the error we're going to go for */
if (err == ERR_PTR(-ENOKEY) || ret == ERR_PTR(-ENOKEY))
key_ref = ERR_PTR(-ENOKEY);
else if (err == ERR_PTR(-EACCES))
key_ref = ret;
else
key_ref = err;
found:
return key_ref;
}
/*
* See if the key we're looking at is the target key.
*/
bool lookup_user_key_possessed(const struct key *key,
const struct key_match_data *match_data)
{
return key == match_data->raw_data;
}
/*
* Look up a key ID given us by userspace with a given permissions mask to get
* the key it refers to.
*
* Flags can be passed to request that special keyrings be created if referred
* to directly, to permit partially constructed keys to be found and to skip
* validity and permission checks on the found key.
*
* Returns a pointer to the key with an incremented usage count if successful;
* -EINVAL if the key ID is invalid; -ENOKEY if the key ID does not correspond
* to a key or the best found key was a negative key; -EKEYREVOKED or
* -EKEYEXPIRED if the best found key was revoked or expired; -EACCES if the
* found key doesn't grant the requested permit or the LSM denied access to it;
* or -ENOMEM if a special keyring couldn't be created.
*
* In the case of a successful return, the possession attribute is set on the
* returned key reference.
*/
key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags,
key_perm_t perm)
{
struct keyring_search_context ctx = {
.match_data.cmp = lookup_user_key_possessed,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
.flags = KEYRING_SEARCH_NO_STATE_CHECK,
};
struct request_key_auth *rka;
struct key *key;
key_ref_t key_ref, skey_ref;
int ret;
try_again:
ctx.cred = get_current_cred();
key_ref = ERR_PTR(-ENOKEY);
switch (id) {
case KEY_SPEC_THREAD_KEYRING:
if (!ctx.cred->thread_keyring) {
if (!(lflags & KEY_LOOKUP_CREATE))
goto error;
ret = install_thread_keyring();
if (ret < 0) {
key_ref = ERR_PTR(ret);
goto error;
}
goto reget_creds;
}
key = ctx.cred->thread_keyring;
__key_get(key);
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_PROCESS_KEYRING:
if (!ctx.cred->process_keyring) {
if (!(lflags & KEY_LOOKUP_CREATE))
goto error;
ret = install_process_keyring();
if (ret < 0) {
key_ref = ERR_PTR(ret);
goto error;
}
goto reget_creds;
}
key = ctx.cred->process_keyring;
__key_get(key);
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_SESSION_KEYRING:
if (!ctx.cred->session_keyring) {
/* always install a session keyring upon access if one
* doesn't exist yet */
ret = install_user_keyrings();
if (ret < 0)
goto error;
if (lflags & KEY_LOOKUP_CREATE)
ret = join_session_keyring(NULL);
else
ret = install_session_keyring(
ctx.cred->user->session_keyring);
if (ret < 0)
goto error;
goto reget_creds;
} else if (ctx.cred->session_keyring ==
ctx.cred->user->session_keyring &&
lflags & KEY_LOOKUP_CREATE) {
ret = join_session_keyring(NULL);
if (ret < 0)
goto error;
goto reget_creds;
}
rcu_read_lock();
key = rcu_dereference(ctx.cred->session_keyring);
__key_get(key);
rcu_read_unlock();
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_USER_KEYRING:
if (!ctx.cred->user->uid_keyring) {
ret = install_user_keyrings();
if (ret < 0)
goto error;
}
key = ctx.cred->user->uid_keyring;
__key_get(key);
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_USER_SESSION_KEYRING:
if (!ctx.cred->user->session_keyring) {
ret = install_user_keyrings();
if (ret < 0)
goto error;
}
key = ctx.cred->user->session_keyring;
__key_get(key);
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_GROUP_KEYRING:
/* group keyrings are not yet supported */
key_ref = ERR_PTR(-EINVAL);
goto error;
case KEY_SPEC_REQKEY_AUTH_KEY:
key = ctx.cred->request_key_auth;
if (!key)
goto error;
__key_get(key);
key_ref = make_key_ref(key, 1);
break;
case KEY_SPEC_REQUESTOR_KEYRING:
if (!ctx.cred->request_key_auth)
goto error;
down_read(&ctx.cred->request_key_auth->sem);
if (test_bit(KEY_FLAG_REVOKED,
&ctx.cred->request_key_auth->flags)) {
key_ref = ERR_PTR(-EKEYREVOKED);
key = NULL;
} else {
rka = ctx.cred->request_key_auth->payload.data;
key = rka->dest_keyring;
__key_get(key);
}
up_read(&ctx.cred->request_key_auth->sem);
if (!key)
goto error;
key_ref = make_key_ref(key, 1);
break;
default:
key_ref = ERR_PTR(-EINVAL);
if (id < 1)
goto error;
key = key_lookup(id);
if (IS_ERR(key)) {
key_ref = ERR_CAST(key);
goto error;
}
key_ref = make_key_ref(key, 0);
/* check to see if we possess the key */
ctx.index_key.type = key->type;
ctx.index_key.description = key->description;
ctx.index_key.desc_len = strlen(key->description);
ctx.match_data.raw_data = key;
kdebug("check possessed");
skey_ref = search_process_keyrings(&ctx);
kdebug("possessed=%p", skey_ref);
if (!IS_ERR(skey_ref)) {
key_put(key);
key_ref = skey_ref;
}
break;
}
/* unlink does not use the nominated key in any way, so can skip all
* the permission checks as it is only concerned with the keyring */
if (lflags & KEY_LOOKUP_FOR_UNLINK) {
ret = 0;
goto error;
}
if (!(lflags & KEY_LOOKUP_PARTIAL)) {
ret = wait_for_key_construction(key, true);
switch (ret) {
case -ERESTARTSYS:
goto invalid_key;
default:
if (perm)
goto invalid_key;
case 0:
break;
}
} else if (perm) {
ret = key_validate(key);
if (ret < 0)
goto invalid_key;
}
ret = -EIO;
if (!(lflags & KEY_LOOKUP_PARTIAL) &&
!test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
goto invalid_key;
/* check the permissions */
ret = key_task_permission(key_ref, ctx.cred, perm);
if (ret < 0)
goto invalid_key;
key->last_used_at = current_kernel_time().tv_sec;
error:
put_cred(ctx.cred);
return key_ref;
invalid_key:
key_ref_put(key_ref);
key_ref = ERR_PTR(ret);
goto error;
/* if we attempted to install a keyring, then it may have caused new
* creds to be installed */
reget_creds:
put_cred(ctx.cred);
goto try_again;
}
/*
* Join the named keyring as the session keyring if possible else attempt to
* create a new one of that name and join that.
*
* If the name is NULL, an empty anonymous keyring will be installed as the
* session keyring.
*
* Named session keyrings are joined with a semaphore held to prevent the
* keyrings from going away whilst the attempt is made to going them and also
* to prevent a race in creating compatible session keyrings.
*/
long join_session_keyring(const char *name)
{
const struct cred *old;
struct cred *new;
struct key *keyring;
long ret, serial;
new = prepare_creds();
if (!new)
return -ENOMEM;
old = current_cred();
/* if no name is provided, install an anonymous keyring */
if (!name) {
ret = install_session_keyring_to_cred(new, NULL);
if (ret < 0)
goto error;
serial = new->session_keyring->serial;
ret = commit_creds(new);
if (ret == 0)
ret = serial;
goto okay;
}
/* allow the user to join or create a named keyring */
mutex_lock(&key_session_mutex);
/* look for an existing keyring of this name */
keyring = find_keyring_by_name(name, false);
if (PTR_ERR(keyring) == -ENOKEY) {
/* not found - try and create a new one */
keyring = keyring_alloc(
name, old->uid, old->gid, old,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ | KEY_USR_LINK,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
} else if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
} else if (keyring == new->session_keyring) {
ret = 0;
goto error2;
}
/* we've got a keyring - now to install it */
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0)
goto error2;
commit_creds(new);
mutex_unlock(&key_session_mutex);
ret = keyring->serial;
key_put(keyring);
okay:
return ret;
error2:
mutex_unlock(&key_session_mutex);
error:
abort_creds(new);
return ret;
}
/*
* Replace a process's session keyring on behalf of one of its children when
* the target process is about to resume userspace execution.
*/
void key_change_session_keyring(struct callback_head *twork)
{
const struct cred *old = current_cred();
struct cred *new = container_of(twork, struct cred, rcu);
if (unlikely(current->flags & PF_EXITING)) {
put_cred(new);
return;
}
new-> uid = old-> uid;
new-> euid = old-> euid;
new-> suid = old-> suid;
new->fsuid = old->fsuid;
new-> gid = old-> gid;
new-> egid = old-> egid;
new-> sgid = old-> sgid;
new->fsgid = old->fsgid;
new->user = get_uid(old->user);
new->user_ns = get_user_ns(old->user_ns);
new->group_info = get_group_info(old->group_info);
new->securebits = old->securebits;
new->cap_inheritable = old->cap_inheritable;
new->cap_permitted = old->cap_permitted;
new->cap_effective = old->cap_effective;
new->cap_bset = old->cap_bset;
new->jit_keyring = old->jit_keyring;
new->thread_keyring = key_get(old->thread_keyring);
new->process_keyring = key_get(old->process_keyring);
security_transfer_creds(new, old);
commit_creds(new);
}
/*
* Make sure that root's user and user-session keyrings exist.
*/
static int __init init_root_keyring(void)
{
return install_user_keyrings();
}
late_initcall(init_root_keyring);

722
security/keys/request_key.c Normal file
View file

@ -0,0 +1,722 @@
/* Request a key from userspace
*
* Copyright (C) 2004-2007 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*
* See Documentation/security/keys-request-key.txt
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kmod.h>
#include <linux/err.h>
#include <linux/keyctl.h>
#include <linux/slab.h>
#include "internal.h"
#define key_negative_timeout 60 /* default timeout on a negative key's existence */
/**
* complete_request_key - Complete the construction of a key.
* @cons: The key construction record.
* @error: The success or failute of the construction.
*
* Complete the attempt to construct a key. The key will be negated
* if an error is indicated. The authorisation key will be revoked
* unconditionally.
*/
void complete_request_key(struct key_construction *cons, int error)
{
kenter("{%d,%d},%d", cons->key->serial, cons->authkey->serial, error);
if (error < 0)
key_negate_and_link(cons->key, key_negative_timeout, NULL,
cons->authkey);
else
key_revoke(cons->authkey);
key_put(cons->key);
key_put(cons->authkey);
kfree(cons);
}
EXPORT_SYMBOL(complete_request_key);
/*
* Initialise a usermode helper that is going to have a specific session
* keyring.
*
* This is called in context of freshly forked kthread before kernel_execve(),
* so we can simply install the desired session_keyring at this point.
*/
static int umh_keys_init(struct subprocess_info *info, struct cred *cred)
{
struct key *keyring = info->data;
return install_session_keyring_to_cred(cred, keyring);
}
/*
* Clean up a usermode helper with session keyring.
*/
static void umh_keys_cleanup(struct subprocess_info *info)
{
struct key *keyring = info->data;
key_put(keyring);
}
/*
* Call a usermode helper with a specific session keyring.
*/
static int call_usermodehelper_keys(char *path, char **argv, char **envp,
struct key *session_keyring, int wait)
{
struct subprocess_info *info;
info = call_usermodehelper_setup(path, argv, envp, GFP_KERNEL,
umh_keys_init, umh_keys_cleanup,
session_keyring);
if (!info)
return -ENOMEM;
key_get(session_keyring);
return call_usermodehelper_exec(info, wait);
}
/*
* Request userspace finish the construction of a key
* - execute "/sbin/request-key <op> <key> <uid> <gid> <keyring> <keyring> <keyring>"
*/
static int call_sbin_request_key(struct key_construction *cons,
const char *op,
void *aux)
{
const struct cred *cred = current_cred();
key_serial_t prkey, sskey;
struct key *key = cons->key, *authkey = cons->authkey, *keyring,
*session;
char *argv[9], *envp[3], uid_str[12], gid_str[12];
char key_str[12], keyring_str[3][12];
char desc[20];
int ret, i;
kenter("{%d},{%d},%s", key->serial, authkey->serial, op);
ret = install_user_keyrings();
if (ret < 0)
goto error_alloc;
/* allocate a new session keyring */
sprintf(desc, "_req.%u", key->serial);
cred = get_current_cred();
keyring = keyring_alloc(desc, cred->fsuid, cred->fsgid, cred,
KEY_POS_ALL | KEY_USR_VIEW | KEY_USR_READ,
KEY_ALLOC_QUOTA_OVERRUN, NULL);
put_cred(cred);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error_alloc;
}
/* attach the auth key to the session keyring */
ret = key_link(keyring, authkey);
if (ret < 0)
goto error_link;
/* record the UID and GID */
sprintf(uid_str, "%d", from_kuid(&init_user_ns, cred->fsuid));
sprintf(gid_str, "%d", from_kgid(&init_user_ns, cred->fsgid));
/* we say which key is under construction */
sprintf(key_str, "%d", key->serial);
/* we specify the process's default keyrings */
sprintf(keyring_str[0], "%d",
cred->thread_keyring ? cred->thread_keyring->serial : 0);
prkey = 0;
if (cred->process_keyring)
prkey = cred->process_keyring->serial;
sprintf(keyring_str[1], "%d", prkey);
rcu_read_lock();
session = rcu_dereference(cred->session_keyring);
if (!session)
session = cred->user->session_keyring;
sskey = session->serial;
rcu_read_unlock();
sprintf(keyring_str[2], "%d", sskey);
/* set up a minimal environment */
i = 0;
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
envp[i] = NULL;
/* set up the argument list */
i = 0;
argv[i++] = "/sbin/request-key";
argv[i++] = (char *) op;
argv[i++] = key_str;
argv[i++] = uid_str;
argv[i++] = gid_str;
argv[i++] = keyring_str[0];
argv[i++] = keyring_str[1];
argv[i++] = keyring_str[2];
argv[i] = NULL;
/* do it */
ret = call_usermodehelper_keys(argv[0], argv, envp, keyring,
UMH_WAIT_PROC);
kdebug("usermode -> 0x%x", ret);
if (ret >= 0) {
/* ret is the exit/wait code */
if (test_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags) ||
key_validate(key) < 0)
ret = -ENOKEY;
else
/* ignore any errors from userspace if the key was
* instantiated */
ret = 0;
}
error_link:
key_put(keyring);
error_alloc:
complete_request_key(cons, ret);
kleave(" = %d", ret);
return ret;
}
/*
* Call out to userspace for key construction.
*
* Program failure is ignored in favour of key status.
*/
static int construct_key(struct key *key, const void *callout_info,
size_t callout_len, void *aux,
struct key *dest_keyring)
{
struct key_construction *cons;
request_key_actor_t actor;
struct key *authkey;
int ret;
kenter("%d,%p,%zu,%p", key->serial, callout_info, callout_len, aux);
cons = kmalloc(sizeof(*cons), GFP_KERNEL);
if (!cons)
return -ENOMEM;
/* allocate an authorisation key */
authkey = request_key_auth_new(key, callout_info, callout_len,
dest_keyring);
if (IS_ERR(authkey)) {
kfree(cons);
ret = PTR_ERR(authkey);
authkey = NULL;
} else {
cons->authkey = key_get(authkey);
cons->key = key_get(key);
/* make the call */
actor = call_sbin_request_key;
if (key->type->request_key)
actor = key->type->request_key;
ret = actor(cons, "create", aux);
/* check that the actor called complete_request_key() prior to
* returning an error */
WARN_ON(ret < 0 &&
!test_bit(KEY_FLAG_REVOKED, &authkey->flags));
key_put(authkey);
}
kleave(" = %d", ret);
return ret;
}
/*
* Get the appropriate destination keyring for the request.
*
* The keyring selected is returned with an extra reference upon it which the
* caller must release.
*/
static void construct_get_dest_keyring(struct key **_dest_keyring)
{
struct request_key_auth *rka;
const struct cred *cred = current_cred();
struct key *dest_keyring = *_dest_keyring, *authkey;
kenter("%p", dest_keyring);
/* find the appropriate keyring */
if (dest_keyring) {
/* the caller supplied one */
key_get(dest_keyring);
} else {
/* use a default keyring; falling through the cases until we
* find one that we actually have */
switch (cred->jit_keyring) {
case KEY_REQKEY_DEFL_DEFAULT:
case KEY_REQKEY_DEFL_REQUESTOR_KEYRING:
if (cred->request_key_auth) {
authkey = cred->request_key_auth;
down_read(&authkey->sem);
rka = authkey->payload.data;
if (!test_bit(KEY_FLAG_REVOKED,
&authkey->flags))
dest_keyring =
key_get(rka->dest_keyring);
up_read(&authkey->sem);
if (dest_keyring)
break;
}
case KEY_REQKEY_DEFL_THREAD_KEYRING:
dest_keyring = key_get(cred->thread_keyring);
if (dest_keyring)
break;
case KEY_REQKEY_DEFL_PROCESS_KEYRING:
dest_keyring = key_get(cred->process_keyring);
if (dest_keyring)
break;
case KEY_REQKEY_DEFL_SESSION_KEYRING:
rcu_read_lock();
dest_keyring = key_get(
rcu_dereference(cred->session_keyring));
rcu_read_unlock();
if (dest_keyring)
break;
case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
dest_keyring =
key_get(cred->user->session_keyring);
break;
case KEY_REQKEY_DEFL_USER_KEYRING:
dest_keyring = key_get(cred->user->uid_keyring);
break;
case KEY_REQKEY_DEFL_GROUP_KEYRING:
default:
BUG();
}
}
*_dest_keyring = dest_keyring;
kleave(" [dk %d]", key_serial(dest_keyring));
return;
}
/*
* Allocate a new key in under-construction state and attempt to link it in to
* the requested keyring.
*
* May return a key that's already under construction instead if there was a
* race between two thread calling request_key().
*/
static int construct_alloc_key(struct keyring_search_context *ctx,
struct key *dest_keyring,
unsigned long flags,
struct key_user *user,
struct key **_key)
{
struct assoc_array_edit *edit;
struct key *key;
key_perm_t perm;
key_ref_t key_ref;
int ret;
kenter("%s,%s,,,",
ctx->index_key.type->name, ctx->index_key.description);
*_key = NULL;
mutex_lock(&user->cons_lock);
perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR;
perm |= KEY_USR_VIEW;
if (ctx->index_key.type->read)
perm |= KEY_POS_READ;
if (ctx->index_key.type == &key_type_keyring ||
ctx->index_key.type->update)
perm |= KEY_POS_WRITE;
key = key_alloc(ctx->index_key.type, ctx->index_key.description,
ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred,
perm, flags);
if (IS_ERR(key))
goto alloc_failed;
set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags);
if (dest_keyring) {
ret = __key_link_begin(dest_keyring, &ctx->index_key, &edit);
if (ret < 0)
goto link_prealloc_failed;
}
/* attach the key to the destination keyring under lock, but we do need
* to do another check just in case someone beat us to it whilst we
* waited for locks */
mutex_lock(&key_construction_mutex);
key_ref = search_process_keyrings(ctx);
if (!IS_ERR(key_ref))
goto key_already_present;
if (dest_keyring)
__key_link(key, &edit);
mutex_unlock(&key_construction_mutex);
if (dest_keyring)
__key_link_end(dest_keyring, &ctx->index_key, edit);
mutex_unlock(&user->cons_lock);
*_key = key;
kleave(" = 0 [%d]", key_serial(key));
return 0;
/* the key is now present - we tell the caller that we found it by
* returning -EINPROGRESS */
key_already_present:
key_put(key);
mutex_unlock(&key_construction_mutex);
key = key_ref_to_ptr(key_ref);
if (dest_keyring) {
ret = __key_link_check_live_key(dest_keyring, key);
if (ret == 0)
__key_link(key, &edit);
__key_link_end(dest_keyring, &ctx->index_key, edit);
if (ret < 0)
goto link_check_failed;
}
mutex_unlock(&user->cons_lock);
*_key = key;
kleave(" = -EINPROGRESS [%d]", key_serial(key));
return -EINPROGRESS;
link_check_failed:
mutex_unlock(&user->cons_lock);
key_put(key);
kleave(" = %d [linkcheck]", ret);
return ret;
link_prealloc_failed:
mutex_unlock(&user->cons_lock);
kleave(" = %d [prelink]", ret);
return ret;
alloc_failed:
mutex_unlock(&user->cons_lock);
kleave(" = %ld", PTR_ERR(key));
return PTR_ERR(key);
}
/*
* Commence key construction.
*/
static struct key *construct_key_and_link(struct keyring_search_context *ctx,
const char *callout_info,
size_t callout_len,
void *aux,
struct key *dest_keyring,
unsigned long flags)
{
struct key_user *user;
struct key *key;
int ret;
kenter("");
user = key_user_lookup(current_fsuid());
if (!user)
return ERR_PTR(-ENOMEM);
construct_get_dest_keyring(&dest_keyring);
ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key);
key_user_put(user);
if (ret == 0) {
ret = construct_key(key, callout_info, callout_len, aux,
dest_keyring);
if (ret < 0) {
kdebug("cons failed");
goto construction_failed;
}
} else if (ret == -EINPROGRESS) {
ret = 0;
} else {
goto couldnt_alloc_key;
}
key_put(dest_keyring);
kleave(" = key %d", key_serial(key));
return key;
construction_failed:
key_negate_and_link(key, key_negative_timeout, NULL, NULL);
key_put(key);
couldnt_alloc_key:
key_put(dest_keyring);
kleave(" = %d", ret);
return ERR_PTR(ret);
}
/**
* request_key_and_link - Request a key and cache it in a keyring.
* @type: The type of key we want.
* @description: The searchable description of the key.
* @callout_info: The data to pass to the instantiation upcall (or NULL).
* @callout_len: The length of callout_info.
* @aux: Auxiliary data for the upcall.
* @dest_keyring: Where to cache the key.
* @flags: Flags to key_alloc().
*
* A key matching the specified criteria is searched for in the process's
* keyrings and returned with its usage count incremented if found. Otherwise,
* if callout_info is not NULL, a key will be allocated and some service
* (probably in userspace) will be asked to instantiate it.
*
* If successfully found or created, the key will be linked to the destination
* keyring if one is provided.
*
* Returns a pointer to the key if successful; -EACCES, -ENOKEY, -EKEYREVOKED
* or -EKEYEXPIRED if an inaccessible, negative, revoked or expired key was
* found; -ENOKEY if no key was found and no @callout_info was given; -EDQUOT
* if insufficient key quota was available to create a new key; or -ENOMEM if
* insufficient memory was available.
*
* If the returned key was created, then it may still be under construction,
* and wait_for_key_construction() should be used to wait for that to complete.
*/
struct key *request_key_and_link(struct key_type *type,
const char *description,
const void *callout_info,
size_t callout_len,
void *aux,
struct key *dest_keyring,
unsigned long flags)
{
struct keyring_search_context ctx = {
.index_key.type = type,
.index_key.description = description,
.cred = current_cred(),
.match_data.cmp = key_default_cmp,
.match_data.raw_data = description,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
.flags = (KEYRING_SEARCH_DO_STATE_CHECK |
KEYRING_SEARCH_SKIP_EXPIRED),
};
struct key *key;
key_ref_t key_ref;
int ret;
kenter("%s,%s,%p,%zu,%p,%p,%lx",
ctx.index_key.type->name, ctx.index_key.description,
callout_info, callout_len, aux, dest_keyring, flags);
if (type->match_preparse) {
ret = type->match_preparse(&ctx.match_data);
if (ret < 0) {
key = ERR_PTR(ret);
goto error;
}
}
/* search all the process keyrings for a key */
key_ref = search_process_keyrings(&ctx);
if (!IS_ERR(key_ref)) {
key = key_ref_to_ptr(key_ref);
if (dest_keyring) {
construct_get_dest_keyring(&dest_keyring);
ret = key_link(dest_keyring, key);
key_put(dest_keyring);
if (ret < 0) {
key_put(key);
key = ERR_PTR(ret);
goto error_free;
}
}
} else if (PTR_ERR(key_ref) != -EAGAIN) {
key = ERR_CAST(key_ref);
} else {
/* the search failed, but the keyrings were searchable, so we
* should consult userspace if we can */
key = ERR_PTR(-ENOKEY);
if (!callout_info)
goto error_free;
key = construct_key_and_link(&ctx, callout_info, callout_len,
aux, dest_keyring, flags);
}
error_free:
if (type->match_free)
type->match_free(&ctx.match_data);
error:
kleave(" = %p", key);
return key;
}
/**
* wait_for_key_construction - Wait for construction of a key to complete
* @key: The key being waited for.
* @intr: Whether to wait interruptibly.
*
* Wait for a key to finish being constructed.
*
* Returns 0 if successful; -ERESTARTSYS if the wait was interrupted; -ENOKEY
* if the key was negated; or -EKEYREVOKED or -EKEYEXPIRED if the key was
* revoked or expired.
*/
int wait_for_key_construction(struct key *key, bool intr)
{
int ret;
ret = wait_on_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT,
intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE);
if (ret)
return -ERESTARTSYS;
if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
smp_rmb();
return key->type_data.reject_error;
}
return key_validate(key);
}
EXPORT_SYMBOL(wait_for_key_construction);
/**
* request_key - Request a key and wait for construction
* @type: Type of key.
* @description: The searchable description of the key.
* @callout_info: The data to pass to the instantiation upcall (or NULL).
*
* As for request_key_and_link() except that it does not add the returned key
* to a keyring if found, new keys are always allocated in the user's quota,
* the callout_info must be a NUL-terminated string and no auxiliary data can
* be passed.
*
* Furthermore, it then works as wait_for_key_construction() to wait for the
* completion of keys undergoing construction with a non-interruptible wait.
*/
struct key *request_key(struct key_type *type,
const char *description,
const char *callout_info)
{
struct key *key;
size_t callout_len = 0;
int ret;
if (callout_info)
callout_len = strlen(callout_info);
key = request_key_and_link(type, description, callout_info, callout_len,
NULL, NULL, KEY_ALLOC_IN_QUOTA);
if (!IS_ERR(key)) {
ret = wait_for_key_construction(key, false);
if (ret < 0) {
key_put(key);
return ERR_PTR(ret);
}
}
return key;
}
EXPORT_SYMBOL(request_key);
/**
* request_key_with_auxdata - Request a key with auxiliary data for the upcaller
* @type: The type of key we want.
* @description: The searchable description of the key.
* @callout_info: The data to pass to the instantiation upcall (or NULL).
* @callout_len: The length of callout_info.
* @aux: Auxiliary data for the upcall.
*
* As for request_key_and_link() except that it does not add the returned key
* to a keyring if found and new keys are always allocated in the user's quota.
*
* Furthermore, it then works as wait_for_key_construction() to wait for the
* completion of keys undergoing construction with a non-interruptible wait.
*/
struct key *request_key_with_auxdata(struct key_type *type,
const char *description,
const void *callout_info,
size_t callout_len,
void *aux)
{
struct key *key;
int ret;
key = request_key_and_link(type, description, callout_info, callout_len,
aux, NULL, KEY_ALLOC_IN_QUOTA);
if (!IS_ERR(key)) {
ret = wait_for_key_construction(key, false);
if (ret < 0) {
key_put(key);
return ERR_PTR(ret);
}
}
return key;
}
EXPORT_SYMBOL(request_key_with_auxdata);
/*
* request_key_async - Request a key (allow async construction)
* @type: Type of key.
* @description: The searchable description of the key.
* @callout_info: The data to pass to the instantiation upcall (or NULL).
* @callout_len: The length of callout_info.
*
* As for request_key_and_link() except that it does not add the returned key
* to a keyring if found, new keys are always allocated in the user's quota and
* no auxiliary data can be passed.
*
* The caller should call wait_for_key_construction() to wait for the
* completion of the returned key if it is still undergoing construction.
*/
struct key *request_key_async(struct key_type *type,
const char *description,
const void *callout_info,
size_t callout_len)
{
return request_key_and_link(type, description, callout_info,
callout_len, NULL, NULL,
KEY_ALLOC_IN_QUOTA);
}
EXPORT_SYMBOL(request_key_async);
/*
* request a key with auxiliary data for the upcaller (allow async construction)
* @type: Type of key.
* @description: The searchable description of the key.
* @callout_info: The data to pass to the instantiation upcall (or NULL).
* @callout_len: The length of callout_info.
* @aux: Auxiliary data for the upcall.
*
* As for request_key_and_link() except that it does not add the returned key
* to a keyring if found and new keys are always allocated in the user's quota.
*
* The caller should call wait_for_key_construction() to wait for the
* completion of the returned key if it is still undergoing construction.
*/
struct key *request_key_async_with_auxdata(struct key_type *type,
const char *description,
const void *callout_info,
size_t callout_len,
void *aux)
{
return request_key_and_link(type, description, callout_info,
callout_len, aux, NULL, KEY_ALLOC_IN_QUOTA);
}
EXPORT_SYMBOL(request_key_async_with_auxdata);

View file

@ -0,0 +1,276 @@
/* Request key authorisation token key definition.
*
* Copyright (C) 2005 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*
* See Documentation/security/keys-request-key.txt
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/err.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "internal.h"
#include <keys/user-type.h>
static int request_key_auth_preparse(struct key_preparsed_payload *);
static void request_key_auth_free_preparse(struct key_preparsed_payload *);
static int request_key_auth_instantiate(struct key *,
struct key_preparsed_payload *);
static void request_key_auth_describe(const struct key *, struct seq_file *);
static void request_key_auth_revoke(struct key *);
static void request_key_auth_destroy(struct key *);
static long request_key_auth_read(const struct key *, char __user *, size_t);
/*
* The request-key authorisation key type definition.
*/
struct key_type key_type_request_key_auth = {
.name = ".request_key_auth",
.def_datalen = sizeof(struct request_key_auth),
.preparse = request_key_auth_preparse,
.free_preparse = request_key_auth_free_preparse,
.instantiate = request_key_auth_instantiate,
.describe = request_key_auth_describe,
.revoke = request_key_auth_revoke,
.destroy = request_key_auth_destroy,
.read = request_key_auth_read,
};
static int request_key_auth_preparse(struct key_preparsed_payload *prep)
{
return 0;
}
static void request_key_auth_free_preparse(struct key_preparsed_payload *prep)
{
}
/*
* Instantiate a request-key authorisation key.
*/
static int request_key_auth_instantiate(struct key *key,
struct key_preparsed_payload *prep)
{
key->payload.data = (struct request_key_auth *)prep->data;
return 0;
}
/*
* Describe an authorisation token.
*/
static void request_key_auth_describe(const struct key *key,
struct seq_file *m)
{
struct request_key_auth *rka = key->payload.data;
seq_puts(m, "key:");
seq_puts(m, key->description);
if (key_is_instantiated(key))
seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len);
}
/*
* Read the callout_info data (retrieves the callout information).
* - the key's semaphore is read-locked
*/
static long request_key_auth_read(const struct key *key,
char __user *buffer, size_t buflen)
{
struct request_key_auth *rka = key->payload.data;
size_t datalen;
long ret;
datalen = rka->callout_len;
ret = datalen;
/* we can return the data as is */
if (buffer && buflen > 0) {
if (buflen > datalen)
buflen = datalen;
if (copy_to_user(buffer, rka->callout_info, buflen) != 0)
ret = -EFAULT;
}
return ret;
}
/*
* Handle revocation of an authorisation token key.
*
* Called with the key sem write-locked.
*/
static void request_key_auth_revoke(struct key *key)
{
struct request_key_auth *rka = key->payload.data;
kenter("{%d}", key->serial);
if (rka->cred) {
put_cred(rka->cred);
rka->cred = NULL;
}
}
/*
* Destroy an instantiation authorisation token key.
*/
static void request_key_auth_destroy(struct key *key)
{
struct request_key_auth *rka = key->payload.data;
kenter("{%d}", key->serial);
if (rka->cred) {
put_cred(rka->cred);
rka->cred = NULL;
}
key_put(rka->target_key);
key_put(rka->dest_keyring);
kfree(rka->callout_info);
kfree(rka);
}
/*
* Create an authorisation token for /sbin/request-key or whoever to gain
* access to the caller's security data.
*/
struct key *request_key_auth_new(struct key *target, const void *callout_info,
size_t callout_len, struct key *dest_keyring)
{
struct request_key_auth *rka, *irka;
const struct cred *cred = current->cred;
struct key *authkey = NULL;
char desc[20];
int ret;
kenter("%d,", target->serial);
/* allocate a auth record */
rka = kmalloc(sizeof(*rka), GFP_KERNEL);
if (!rka) {
kleave(" = -ENOMEM");
return ERR_PTR(-ENOMEM);
}
rka->callout_info = kmalloc(callout_len, GFP_KERNEL);
if (!rka->callout_info) {
kleave(" = -ENOMEM");
kfree(rka);
return ERR_PTR(-ENOMEM);
}
/* see if the calling process is already servicing the key request of
* another process */
if (cred->request_key_auth) {
/* it is - use that instantiation context here too */
down_read(&cred->request_key_auth->sem);
/* if the auth key has been revoked, then the key we're
* servicing is already instantiated */
if (test_bit(KEY_FLAG_REVOKED, &cred->request_key_auth->flags))
goto auth_key_revoked;
irka = cred->request_key_auth->payload.data;
rka->cred = get_cred(irka->cred);
rka->pid = irka->pid;
up_read(&cred->request_key_auth->sem);
}
else {
/* it isn't - use this process as the context */
rka->cred = get_cred(cred);
rka->pid = current->pid;
}
rka->target_key = key_get(target);
rka->dest_keyring = key_get(dest_keyring);
memcpy(rka->callout_info, callout_info, callout_len);
rka->callout_len = callout_len;
/* allocate the auth key */
sprintf(desc, "%x", target->serial);
authkey = key_alloc(&key_type_request_key_auth, desc,
cred->fsuid, cred->fsgid, cred,
KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH |
KEY_USR_VIEW, KEY_ALLOC_NOT_IN_QUOTA);
if (IS_ERR(authkey)) {
ret = PTR_ERR(authkey);
goto error_alloc;
}
/* construct the auth key */
ret = key_instantiate_and_link(authkey, rka, 0, NULL, NULL);
if (ret < 0)
goto error_inst;
kleave(" = {%d,%d}", authkey->serial, atomic_read(&authkey->usage));
return authkey;
auth_key_revoked:
up_read(&cred->request_key_auth->sem);
kfree(rka->callout_info);
kfree(rka);
kleave("= -EKEYREVOKED");
return ERR_PTR(-EKEYREVOKED);
error_inst:
key_revoke(authkey);
key_put(authkey);
error_alloc:
key_put(rka->target_key);
key_put(rka->dest_keyring);
kfree(rka->callout_info);
kfree(rka);
kleave("= %d", ret);
return ERR_PTR(ret);
}
/*
* Search the current process's keyrings for the authorisation key for
* instantiation of a key.
*/
struct key *key_get_instantiation_authkey(key_serial_t target_id)
{
char description[16];
struct keyring_search_context ctx = {
.index_key.type = &key_type_request_key_auth,
.index_key.description = description,
.cred = current_cred(),
.match_data.cmp = key_default_cmp,
.match_data.raw_data = description,
.match_data.lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT,
.flags = KEYRING_SEARCH_DO_STATE_CHECK,
};
struct key *authkey;
key_ref_t authkey_ref;
sprintf(description, "%x", target_id);
authkey_ref = search_process_keyrings(&ctx);
if (IS_ERR(authkey_ref)) {
authkey = ERR_CAST(authkey_ref);
if (authkey == ERR_PTR(-EAGAIN))
authkey = ERR_PTR(-ENOKEY);
goto error;
}
authkey = key_ref_to_ptr(authkey_ref);
if (test_bit(KEY_FLAG_REVOKED, &authkey->flags)) {
key_put(authkey);
authkey = ERR_PTR(-EKEYREVOKED);
}
error:
return authkey;
}

76
security/keys/sysctl.c Normal file
View file

@ -0,0 +1,76 @@
/* Key management controls
*
* Copyright (C) 2008 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public Licence
* as published by the Free Software Foundation; either version
* 2 of the Licence, or (at your option) any later version.
*/
#include <linux/key.h>
#include <linux/sysctl.h>
#include "internal.h"
static const int zero, one = 1, max = INT_MAX;
struct ctl_table key_sysctls[] = {
{
.procname = "maxkeys",
.data = &key_quota_maxkeys,
.maxlen = sizeof(unsigned),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = (void *) &one,
.extra2 = (void *) &max,
},
{
.procname = "maxbytes",
.data = &key_quota_maxbytes,
.maxlen = sizeof(unsigned),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = (void *) &one,
.extra2 = (void *) &max,
},
{
.procname = "root_maxkeys",
.data = &key_quota_root_maxkeys,
.maxlen = sizeof(unsigned),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = (void *) &one,
.extra2 = (void *) &max,
},
{
.procname = "root_maxbytes",
.data = &key_quota_root_maxbytes,
.maxlen = sizeof(unsigned),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = (void *) &one,
.extra2 = (void *) &max,
},
{
.procname = "gc_delay",
.data = &key_gc_delay,
.maxlen = sizeof(unsigned),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = (void *) &zero,
.extra2 = (void *) &max,
},
#ifdef CONFIG_PERSISTENT_KEYRINGS
{
.procname = "persistent_keyring_expiry",
.data = &persistent_keyring_expiry,
.maxlen = sizeof(unsigned),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = (void *) &zero,
.extra2 = (void *) &max,
},
#endif
{ }
};

1162
security/keys/trusted.c Normal file

File diff suppressed because it is too large Load diff

134
security/keys/trusted.h Normal file
View file

@ -0,0 +1,134 @@
#ifndef __TRUSTED_KEY_H
#define __TRUSTED_KEY_H
/* implementation specific TPM constants */
#define MAX_PCRINFO_SIZE 64
#define MAX_BUF_SIZE 512
#define TPM_GETRANDOM_SIZE 14
#define TPM_OSAP_SIZE 36
#define TPM_OIAP_SIZE 10
#define TPM_SEAL_SIZE 87
#define TPM_UNSEAL_SIZE 104
#define TPM_SIZE_OFFSET 2
#define TPM_RETURN_OFFSET 6
#define TPM_DATA_OFFSET 10
#define LOAD32(buffer, offset) (ntohl(*(uint32_t *)&buffer[offset]))
#define LOAD32N(buffer, offset) (*(uint32_t *)&buffer[offset])
#define LOAD16(buffer, offset) (ntohs(*(uint16_t *)&buffer[offset]))
struct tpm_buf {
int len;
unsigned char data[MAX_BUF_SIZE];
};
#define INIT_BUF(tb) (tb->len = 0)
struct osapsess {
uint32_t handle;
unsigned char secret[SHA1_DIGEST_SIZE];
unsigned char enonce[TPM_NONCE_SIZE];
};
/* discrete values, but have to store in uint16_t for TPM use */
enum {
SEAL_keytype = 1,
SRK_keytype = 4
};
struct trusted_key_options {
uint16_t keytype;
uint32_t keyhandle;
unsigned char keyauth[SHA1_DIGEST_SIZE];
unsigned char blobauth[SHA1_DIGEST_SIZE];
uint32_t pcrinfo_len;
unsigned char pcrinfo[MAX_PCRINFO_SIZE];
int pcrlock;
};
#define TPM_DEBUG 0
#if TPM_DEBUG
static inline void dump_options(struct trusted_key_options *o)
{
pr_info("trusted_key: sealing key type %d\n", o->keytype);
pr_info("trusted_key: sealing key handle %0X\n", o->keyhandle);
pr_info("trusted_key: pcrlock %d\n", o->pcrlock);
pr_info("trusted_key: pcrinfo %d\n", o->pcrinfo_len);
print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE,
16, 1, o->pcrinfo, o->pcrinfo_len, 0);
}
static inline void dump_payload(struct trusted_key_payload *p)
{
pr_info("trusted_key: key_len %d\n", p->key_len);
print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE,
16, 1, p->key, p->key_len, 0);
pr_info("trusted_key: bloblen %d\n", p->blob_len);
print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE,
16, 1, p->blob, p->blob_len, 0);
pr_info("trusted_key: migratable %d\n", p->migratable);
}
static inline void dump_sess(struct osapsess *s)
{
print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE,
16, 1, &s->handle, 4, 0);
pr_info("trusted-key: secret:\n");
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
16, 1, &s->secret, SHA1_DIGEST_SIZE, 0);
pr_info("trusted-key: enonce:\n");
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE,
16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0);
}
static inline void dump_tpm_buf(unsigned char *buf)
{
int len;
pr_info("\ntrusted-key: tpm buffer\n");
len = LOAD32(buf, TPM_SIZE_OFFSET);
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0);
}
#else
static inline void dump_options(struct trusted_key_options *o)
{
}
static inline void dump_payload(struct trusted_key_payload *p)
{
}
static inline void dump_sess(struct osapsess *s)
{
}
static inline void dump_tpm_buf(unsigned char *buf)
{
}
#endif
static inline void store8(struct tpm_buf *buf, const unsigned char value)
{
buf->data[buf->len++] = value;
}
static inline void store16(struct tpm_buf *buf, const uint16_t value)
{
*(uint16_t *) & buf->data[buf->len] = htons(value);
buf->len += sizeof value;
}
static inline void store32(struct tpm_buf *buf, const uint32_t value)
{
*(uint32_t *) & buf->data[buf->len] = htonl(value);
buf->len += sizeof value;
}
static inline void storebytes(struct tpm_buf *buf, const unsigned char *in,
const int len)
{
memcpy(buf->data + buf->len, in, len);
buf->len += len;
}
#endif

View file

@ -0,0 +1,227 @@
/* user_defined.c: user defined key type
*
* Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <linux/err.h>
#include <keys/user-type.h>
#include <asm/uaccess.h>
#include "internal.h"
static int logon_vet_description(const char *desc);
/*
* user defined keys take an arbitrary string as the description and an
* arbitrary blob of data as the payload
*/
struct key_type key_type_user = {
.name = "user",
.preparse = user_preparse,
.free_preparse = user_free_preparse,
.instantiate = generic_key_instantiate,
.update = user_update,
.revoke = user_revoke,
.destroy = user_destroy,
.describe = user_describe,
.read = user_read,
};
EXPORT_SYMBOL_GPL(key_type_user);
/*
* This key type is essentially the same as key_type_user, but it does
* not define a .read op. This is suitable for storing username and
* password pairs in the keyring that you do not want to be readable
* from userspace.
*/
struct key_type key_type_logon = {
.name = "logon",
.preparse = user_preparse,
.free_preparse = user_free_preparse,
.instantiate = generic_key_instantiate,
.update = user_update,
.revoke = user_revoke,
.destroy = user_destroy,
.describe = user_describe,
.vet_description = logon_vet_description,
};
EXPORT_SYMBOL_GPL(key_type_logon);
/*
* Preparse a user defined key payload
*/
int user_preparse(struct key_preparsed_payload *prep)
{
struct user_key_payload *upayload;
size_t datalen = prep->datalen;
if (datalen <= 0 || datalen > 32767 || !prep->data)
return -EINVAL;
upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL);
if (!upayload)
return -ENOMEM;
/* attach the data */
prep->quotalen = datalen;
prep->payload[0] = upayload;
upayload->datalen = datalen;
memcpy(upayload->data, prep->data, datalen);
return 0;
}
EXPORT_SYMBOL_GPL(user_preparse);
/*
* Free a preparse of a user defined key payload
*/
void user_free_preparse(struct key_preparsed_payload *prep)
{
kfree(prep->payload[0]);
}
EXPORT_SYMBOL_GPL(user_free_preparse);
/*
* update a user defined key
* - the key's semaphore is write-locked
*/
int user_update(struct key *key, struct key_preparsed_payload *prep)
{
struct user_key_payload *upayload, *zap;
size_t datalen = prep->datalen;
int ret;
ret = -EINVAL;
if (datalen <= 0 || datalen > 32767 || !prep->data)
goto error;
/* construct a replacement payload */
ret = -ENOMEM;
upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL);
if (!upayload)
goto error;
upayload->datalen = datalen;
memcpy(upayload->data, prep->data, datalen);
/* check the quota and attach the new data */
zap = upayload;
ret = key_payload_reserve(key, datalen);
if (ret == 0) {
/* attach the new data, displacing the old */
zap = key->payload.data;
rcu_assign_keypointer(key, upayload);
key->expiry = 0;
}
if (zap)
kfree_rcu(zap, rcu);
error:
return ret;
}
EXPORT_SYMBOL_GPL(user_update);
/*
* dispose of the links from a revoked keyring
* - called with the key sem write-locked
*/
void user_revoke(struct key *key)
{
struct user_key_payload *upayload = key->payload.data;
/* clear the quota */
key_payload_reserve(key, 0);
if (upayload) {
rcu_assign_keypointer(key, NULL);
kfree_rcu(upayload, rcu);
}
}
EXPORT_SYMBOL(user_revoke);
/*
* dispose of the data dangling from the corpse of a user key
*/
void user_destroy(struct key *key)
{
struct user_key_payload *upayload = key->payload.data;
#ifdef CONFIG_CRYPTO_FIPS
if(upayload)
{
memset(upayload->data, 0, upayload->datalen);
}
#endif
kfree(upayload);
}
EXPORT_SYMBOL_GPL(user_destroy);
/*
* describe the user key
*/
void user_describe(const struct key *key, struct seq_file *m)
{
seq_puts(m, key->description);
if (key_is_instantiated(key))
seq_printf(m, ": %u", key->datalen);
}
EXPORT_SYMBOL_GPL(user_describe);
/*
* read the key data
* - the key's semaphore is read-locked
*/
long user_read(const struct key *key, char __user *buffer, size_t buflen)
{
struct user_key_payload *upayload;
long ret;
upayload = rcu_dereference_key(key);
ret = upayload->datalen;
/* we can return the data as is */
if (buffer && buflen > 0) {
if (buflen > upayload->datalen)
buflen = upayload->datalen;
if (copy_to_user(buffer, upayload->data, buflen) != 0)
ret = -EFAULT;
}
return ret;
}
EXPORT_SYMBOL_GPL(user_read);
/* Vet the description for a "logon" key */
static int logon_vet_description(const char *desc)
{
char *p;
/* require a "qualified" description string */
p = strchr(desc, ':');
if (!p)
return -EINVAL;
/* also reject description with ':' as first char */
if (p == desc)
return -EINVAL;
return 0;
}

433
security/lsm_audit.c Normal file
View file

@ -0,0 +1,433 @@
/*
* common LSM auditing functions
*
* Based on code written for SELinux by :
* Stephen Smalley, <sds@epoch.ncsc.mil>
* James Morris <jmorris@redhat.com>
* Author : Etienne Basset, <etienne.basset@ensta.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*/
#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/gfp.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/un.h>
#include <net/af_unix.h>
#include <linux/audit.h>
#include <linux/ipv6.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/dccp.h>
#include <linux/sctp.h>
#include <linux/lsm_audit.h>
/**
* ipv4_skb_to_auditdata : fill auditdata from skb
* @skb : the skb
* @ad : the audit data to fill
* @proto : the layer 4 protocol
*
* return 0 on success
*/
int ipv4_skb_to_auditdata(struct sk_buff *skb,
struct common_audit_data *ad, u8 *proto)
{
int ret = 0;
struct iphdr *ih;
ih = ip_hdr(skb);
if (ih == NULL)
return -EINVAL;
ad->u.net->v4info.saddr = ih->saddr;
ad->u.net->v4info.daddr = ih->daddr;
if (proto)
*proto = ih->protocol;
/* non initial fragment */
if (ntohs(ih->frag_off) & IP_OFFSET)
return 0;
switch (ih->protocol) {
case IPPROTO_TCP: {
struct tcphdr *th = tcp_hdr(skb);
if (th == NULL)
break;
ad->u.net->sport = th->source;
ad->u.net->dport = th->dest;
break;
}
case IPPROTO_UDP: {
struct udphdr *uh = udp_hdr(skb);
if (uh == NULL)
break;
ad->u.net->sport = uh->source;
ad->u.net->dport = uh->dest;
break;
}
case IPPROTO_DCCP: {
struct dccp_hdr *dh = dccp_hdr(skb);
if (dh == NULL)
break;
ad->u.net->sport = dh->dccph_sport;
ad->u.net->dport = dh->dccph_dport;
break;
}
case IPPROTO_SCTP: {
struct sctphdr *sh = sctp_hdr(skb);
if (sh == NULL)
break;
ad->u.net->sport = sh->source;
ad->u.net->dport = sh->dest;
break;
}
default:
ret = -EINVAL;
}
return ret;
}
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
/**
* ipv6_skb_to_auditdata : fill auditdata from skb
* @skb : the skb
* @ad : the audit data to fill
* @proto : the layer 4 protocol
*
* return 0 on success
*/
int ipv6_skb_to_auditdata(struct sk_buff *skb,
struct common_audit_data *ad, u8 *proto)
{
int offset, ret = 0;
struct ipv6hdr *ip6;
u8 nexthdr;
__be16 frag_off;
ip6 = ipv6_hdr(skb);
if (ip6 == NULL)
return -EINVAL;
ad->u.net->v6info.saddr = ip6->saddr;
ad->u.net->v6info.daddr = ip6->daddr;
ret = 0;
/* IPv6 can have several extension header before the Transport header
* skip them */
offset = skb_network_offset(skb);
offset += sizeof(*ip6);
nexthdr = ip6->nexthdr;
offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off);
if (offset < 0)
return 0;
if (proto)
*proto = nexthdr;
switch (nexthdr) {
case IPPROTO_TCP: {
struct tcphdr _tcph, *th;
th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph);
if (th == NULL)
break;
ad->u.net->sport = th->source;
ad->u.net->dport = th->dest;
break;
}
case IPPROTO_UDP: {
struct udphdr _udph, *uh;
uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph);
if (uh == NULL)
break;
ad->u.net->sport = uh->source;
ad->u.net->dport = uh->dest;
break;
}
case IPPROTO_DCCP: {
struct dccp_hdr _dccph, *dh;
dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph);
if (dh == NULL)
break;
ad->u.net->sport = dh->dccph_sport;
ad->u.net->dport = dh->dccph_dport;
break;
}
case IPPROTO_SCTP: {
struct sctphdr _sctph, *sh;
sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph);
if (sh == NULL)
break;
ad->u.net->sport = sh->source;
ad->u.net->dport = sh->dest;
break;
}
default:
ret = -EINVAL;
}
return ret;
}
#endif
static inline void print_ipv6_addr(struct audit_buffer *ab,
struct in6_addr *addr, __be16 port,
char *name1, char *name2)
{
if (!ipv6_addr_any(addr))
audit_log_format(ab, " %s=%pI6c", name1, addr);
if (port)
audit_log_format(ab, " %s=%d", name2, ntohs(port));
}
static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr,
__be16 port, char *name1, char *name2)
{
if (addr)
audit_log_format(ab, " %s=%pI4", name1, &addr);
if (port)
audit_log_format(ab, " %s=%d", name2, ntohs(port));
}
/**
* dump_common_audit_data - helper to dump common audit data
* @a : common audit data
*
*/
static void dump_common_audit_data(struct audit_buffer *ab,
struct common_audit_data *a)
{
struct task_struct *tsk = current;
/*
* To keep stack sizes in check force programers to notice if they
* start making this union too large! See struct lsm_network_audit
* as an example of how to deal with large data.
*/
BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2);
audit_log_format(ab, " pid=%d comm=", task_pid_nr(tsk));
audit_log_untrustedstring(ab, tsk->comm);
switch (a->type) {
case LSM_AUDIT_DATA_NONE:
return;
case LSM_AUDIT_DATA_IPC:
audit_log_format(ab, " key=%d ", a->u.ipc_id);
break;
case LSM_AUDIT_DATA_CAP:
audit_log_format(ab, " capability=%d ", a->u.cap);
break;
case LSM_AUDIT_DATA_PATH: {
struct inode *inode;
audit_log_d_path(ab, " path=", &a->u.path);
inode = a->u.path.dentry->d_inode;
if (inode) {
audit_log_format(ab, " dev=");
audit_log_untrustedstring(ab, inode->i_sb->s_id);
audit_log_format(ab, " ino=%lu", inode->i_ino);
}
break;
}
case LSM_AUDIT_DATA_IOCTL_OP: {
struct inode *inode;
audit_log_d_path(ab, " path=", &a->u.op->path);
inode = a->u.op->path.dentry->d_inode;
if (inode) {
audit_log_format(ab, " dev=");
audit_log_untrustedstring(ab, inode->i_sb->s_id);
audit_log_format(ab, " ino=%lu", inode->i_ino);
}
audit_log_format(ab, " ioctlcmd=%hx", a->u.op->cmd);
break;
}
case LSM_AUDIT_DATA_DENTRY: {
struct inode *inode;
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, a->u.dentry->d_name.name);
inode = a->u.dentry->d_inode;
if (inode) {
audit_log_format(ab, " dev=");
audit_log_untrustedstring(ab, inode->i_sb->s_id);
audit_log_format(ab, " ino=%lu", inode->i_ino);
}
break;
}
case LSM_AUDIT_DATA_INODE: {
struct dentry *dentry;
struct inode *inode;
inode = a->u.inode;
dentry = d_find_alias(inode);
if (dentry) {
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab,
dentry->d_name.name);
dput(dentry);
}
audit_log_format(ab, " dev=");
audit_log_untrustedstring(ab, inode->i_sb->s_id);
audit_log_format(ab, " ino=%lu", inode->i_ino);
break;
}
case LSM_AUDIT_DATA_TASK:
tsk = a->u.tsk;
if (tsk) {
pid_t pid = task_pid_nr(tsk);
if (pid) {
audit_log_format(ab, " pid=%d comm=", pid);
audit_log_untrustedstring(ab, tsk->comm);
}
}
break;
case LSM_AUDIT_DATA_NET:
if (a->u.net->sk) {
struct sock *sk = a->u.net->sk;
struct unix_sock *u;
int len = 0;
char *p = NULL;
switch (sk->sk_family) {
case AF_INET: {
struct inet_sock *inet = inet_sk(sk);
print_ipv4_addr(ab, inet->inet_rcv_saddr,
inet->inet_sport,
"laddr", "lport");
print_ipv4_addr(ab, inet->inet_daddr,
inet->inet_dport,
"faddr", "fport");
break;
}
#if IS_ENABLED(CONFIG_IPV6)
case AF_INET6: {
struct inet_sock *inet = inet_sk(sk);
print_ipv6_addr(ab, &sk->sk_v6_rcv_saddr,
inet->inet_sport,
"laddr", "lport");
print_ipv6_addr(ab, &sk->sk_v6_daddr,
inet->inet_dport,
"faddr", "fport");
break;
}
#endif
case AF_UNIX:
u = unix_sk(sk);
if (u->path.dentry) {
audit_log_d_path(ab, " path=", &u->path);
break;
}
if (!u->addr)
break;
len = u->addr->len-sizeof(short);
p = &u->addr->name->sun_path[0];
audit_log_format(ab, " path=");
if (*p)
audit_log_untrustedstring(ab, p);
else
audit_log_n_hex(ab, p, len);
break;
}
}
switch (a->u.net->family) {
case AF_INET:
print_ipv4_addr(ab, a->u.net->v4info.saddr,
a->u.net->sport,
"saddr", "src");
print_ipv4_addr(ab, a->u.net->v4info.daddr,
a->u.net->dport,
"daddr", "dest");
break;
case AF_INET6:
print_ipv6_addr(ab, &a->u.net->v6info.saddr,
a->u.net->sport,
"saddr", "src");
print_ipv6_addr(ab, &a->u.net->v6info.daddr,
a->u.net->dport,
"daddr", "dest");
break;
}
if (a->u.net->netif > 0) {
struct net_device *dev;
/* NOTE: we always use init's namespace */
dev = dev_get_by_index(&init_net, a->u.net->netif);
if (dev) {
audit_log_format(ab, " netif=%s", dev->name);
dev_put(dev);
}
}
break;
#ifdef CONFIG_KEYS
case LSM_AUDIT_DATA_KEY:
audit_log_format(ab, " key_serial=%u", a->u.key_struct.key);
if (a->u.key_struct.key_desc) {
audit_log_format(ab, " key_desc=");
audit_log_untrustedstring(ab, a->u.key_struct.key_desc);
}
break;
#endif
case LSM_AUDIT_DATA_KMOD:
audit_log_format(ab, " kmod=");
audit_log_untrustedstring(ab, a->u.kmod_name);
break;
} /* switch (a->type) */
}
/**
* common_lsm_audit - generic LSM auditing function
* @a: auxiliary audit data
* @pre_audit: lsm-specific pre-audit callback
* @post_audit: lsm-specific post-audit callback
*
* setup the audit buffer for common security information
* uses callback to print LSM specific information
*/
void common_lsm_audit(struct common_audit_data *a,
void (*pre_audit)(struct audit_buffer *, void *),
void (*post_audit)(struct audit_buffer *, void *))
{
struct audit_buffer *ab;
if (a == NULL)
return;
/* we use GFP_ATOMIC so we won't sleep */
ab = audit_log_start(current->audit_context, GFP_ATOMIC | __GFP_NOWARN,
AUDIT_AVC);
if (ab == NULL)
return;
if (pre_audit)
pre_audit(ab, a);
dump_common_audit_data(ab, a);
if (post_audit)
post_audit(ab, a);
audit_log_end(ab);
}

52
security/min_addr.c Normal file
View file

@ -0,0 +1,52 @@
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/security.h>
#include <linux/sysctl.h>
/* amount of vm to protect from userspace access by both DAC and the LSM*/
unsigned long mmap_min_addr;
/* amount of vm to protect from userspace using CAP_SYS_RAWIO (DAC) */
unsigned long dac_mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR;
/* amount of vm to protect from userspace using the LSM = CONFIG_LSM_MMAP_MIN_ADDR */
/*
* Update mmap_min_addr = max(dac_mmap_min_addr, CONFIG_LSM_MMAP_MIN_ADDR)
*/
static void update_mmap_min_addr(void)
{
#ifdef CONFIG_LSM_MMAP_MIN_ADDR
if (dac_mmap_min_addr > CONFIG_LSM_MMAP_MIN_ADDR)
mmap_min_addr = dac_mmap_min_addr;
else
mmap_min_addr = CONFIG_LSM_MMAP_MIN_ADDR;
#else
mmap_min_addr = dac_mmap_min_addr;
#endif
}
/*
* sysctl handler which just sets dac_mmap_min_addr = the new value and then
* calls update_mmap_min_addr() so non MAP_FIXED hints get rounded properly
*/
int mmap_min_addr_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int ret;
if (write && !capable(CAP_SYS_RAWIO))
return -EPERM;
ret = proc_doulongvec_minmax(table, write, buffer, lenp, ppos);
update_mmap_min_addr();
return ret;
}
static int __init init_mmap_min_addr(void)
{
update_mmap_min_addr();
return 0;
}
pure_initcall(init_mmap_min_addr);

7
security/sdp/Makefile Normal file
View file

@ -0,0 +1,7 @@
#
# Makefile for DEK and DLP modules
#
obj-$(CONFIG_SDP) += dek.o dek_aes.o sdp_mm.o pub_crypto_emul.o dek_sysfs.o cache_cleanup.o kek_pack.o fs_handler.o
obj-$(CONFIG_DLP) += sdp_dlp.o

70
security/sdp/cache_cleanup.c Executable file
View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
*
* Sensitive Data Protection
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <sdp/common.h>
#include <sdp/cache_cleanup.h>
#if SDP_CACHE_CLEANUP_DEBUG
static void sdp_page_dump(unsigned char *buf, int len, const char* str)
{
unsigned int i;
char s[512];
s[0] = 0;
for(i=0;i<len && i<32;++i) {
char tmp[8];
sprintf(tmp, " %02x", buf[i]);
strcat(s, tmp);
}
if (len > 32) {
char tmp[8];
sprintf(tmp, " ...");
strcat(s, tmp);
}
printk("%s [%s len=%d]\n", s, str, len);
}
#else
static void sdp_page_dump(unsigned char *buf, int len, const char* str) {
return;
}
#endif
void sdp_page_cleanup(struct page *page)
{
if(page && page->mapping) {
if(mapping_sensitive(page->mapping)) {
void *d;
#if SDP_CACHE_CLEANUP_DEBUG
printk("%s : deleting [%s] sensitive page.\n",
__func__, page->mapping->host->i_sb->s_type->name);
//dump_stack();
#endif
d = kmap_atomic(page);
if(d) {
sdp_page_dump((unsigned char *)d, PAGE_SIZE, "freeing");
clear_page(d);
sdp_page_dump((unsigned char *)d, PAGE_SIZE, "freed");
kunmap_atomic(d);
}
}
}
}

1182
security/sdp/dek.c Normal file

File diff suppressed because it is too large Load diff

107
security/sdp/dek_aes.c Normal file
View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
*
* Sensitive Data Protection
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/err.h>
#include <sdp/dek_common.h>
#include <sdp/dek_aes.h>
static struct crypto_blkcipher *dek_aes_key_setup(kek_t *kek)
{
struct crypto_blkcipher *tfm = NULL;
tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
if (!IS_ERR(tfm)) {
crypto_blkcipher_setkey(tfm, kek->buf, kek->len);
} else {
printk("dek: failed to alloc blkcipher\n");
}
return tfm;
}
static void dek_aes_key_free(struct crypto_blkcipher *tfm)
{
crypto_free_blkcipher(tfm);
}
static int __dek_aes_encrypt(struct crypto_blkcipher *tfm, char *src, char *dst, int len) {
struct blkcipher_desc desc;
struct scatterlist src_sg, dst_sg;
int bsize = crypto_blkcipher_ivsize(tfm);
u8 iv[bsize];
memset(&iv, 0, sizeof(iv));
desc.tfm = tfm;
desc.info = iv;
desc.flags = 0;
sg_init_one(&src_sg, src, len);
sg_init_one(&dst_sg, dst, len);
return crypto_blkcipher_encrypt_iv(&desc, &dst_sg, &src_sg, len);
}
static int __dek_aes_decrypt(struct crypto_blkcipher *tfm, char *src, char *dst, int len) {
struct blkcipher_desc desc;
struct scatterlist src_sg, dst_sg;
int bsize = crypto_blkcipher_ivsize(tfm);
u8 iv[bsize];
memset(&iv, 0, sizeof(iv));
desc.tfm = tfm;
desc.info = iv;
desc.flags = 0;
sg_init_one(&src_sg, src, len);
sg_init_one(&dst_sg, dst, len);
return crypto_blkcipher_decrypt_iv(&desc, &dst_sg, &src_sg, len);
}
int dek_aes_encrypt(kek_t *kek, char *src, char *dst, int len) {
int rc;
struct crypto_blkcipher *tfm;
if(kek == NULL) return -EINVAL;
tfm = dek_aes_key_setup(kek);
if(tfm) {
rc = __dek_aes_encrypt(tfm, src, dst, len);
dek_aes_key_free(tfm);
return rc;
} else
return -ENOMEM;
}
int dek_aes_decrypt(kek_t *kek, char *src, char *dst, int len) {
int rc;
struct crypto_blkcipher *tfm;
if(kek == NULL) return -EINVAL;
tfm = dek_aes_key_setup(kek);
if(tfm) {
rc = __dek_aes_decrypt(tfm, src, dst, len);
dek_aes_key_free(tfm);
return rc;
} else
return -ENOMEM;
}

Some files were not shown because too many files have changed in this diff Show more