mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
62
security/integrity/Kconfig
Normal file
62
security/integrity/Kconfig
Normal 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
|
15
security/integrity/Makefile
Normal file
15
security/integrity/Makefile
Normal 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/
|
86
security/integrity/digsig.c
Normal file
86
security/integrity/digsig.c
Normal 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;
|
||||
}
|
105
security/integrity/digsig_asymmetric.c
Normal file
105
security/integrity/digsig_asymmetric.c
Normal 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;
|
||||
}
|
44
security/integrity/evm/Kconfig
Normal file
44
security/integrity/evm/Kconfig
Normal 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.
|
||||
|
7
security/integrity/evm/Makefile
Normal file
7
security/integrity/evm/Makefile
Normal 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
|
53
security/integrity/evm/evm.h
Normal file
53
security/integrity/evm/evm.h
Normal 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
|
262
security/integrity/evm/evm_crypto.c
Normal file
262
security/integrity/evm/evm_crypto.c
Normal 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;
|
||||
}
|
494
security/integrity/evm/evm_main.c
Normal file
494
security/integrity/evm/evm_main.c
Normal 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");
|
27
security/integrity/evm/evm_posix_acl.c
Normal file
27
security/integrity/evm/evm_posix_acl.c
Normal 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;
|
||||
}
|
104
security/integrity/evm/evm_secfs.c
Normal file
104
security/integrity/evm/evm_secfs.c
Normal 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
172
security/integrity/iint.c
Normal 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);
|
133
security/integrity/ima/Kconfig
Normal file
133
security/integrity/ima/Kconfig
Normal 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.
|
10
security/integrity/ima/Makefile
Normal file
10
security/integrity/ima/Makefile
Normal 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
|
259
security/integrity/ima/ima.h
Normal file
259
security/integrity/ima/ima.h
Normal 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
|
338
security/integrity/ima/ima_api.c
Normal file
338
security/integrity/ima/ima_api.c
Normal 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;
|
||||
}
|
400
security/integrity/ima/ima_appraise.c
Normal file
400
security/integrity/ima/ima_appraise.c
Normal 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;
|
||||
}
|
603
security/integrity/ima/ima_crypto.c
Normal file
603
security/integrity/ima/ima_crypto.c
Normal 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;
|
||||
}
|
382
security/integrity/ima/ima_fs.c
Normal file
382
security/integrity/ima/ima_fs.c
Normal 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;
|
||||
}
|
125
security/integrity/ima/ima_init.c
Normal file
125
security/integrity/ima/ima_init.c
Normal 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();
|
||||
}
|
360
security/integrity/ima/ima_main.c
Normal file
360
security/integrity/ima/ima_main.c
Normal 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");
|
745
security/integrity/ima/ima_policy.c
Normal file
745
security/integrity/ima/ima_policy.c
Normal 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);
|
||||
}
|
150
security/integrity/ima/ima_queue.c
Normal file
150
security/integrity/ima/ima_queue.c
Normal 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;
|
||||
}
|
170
security/integrity/ima/ima_template.c
Normal file
170
security/integrity/ima/ima_template.c
Normal 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));
|
||||
}
|
342
security/integrity/ima/ima_template_lib.c
Normal file
342
security/integrity/ima/ima_template_lib.c
Normal 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;
|
||||
}
|
49
security/integrity/ima/ima_template_lib.h
Normal file
49
security/integrity/ima/ima_template_lib.h
Normal 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 */
|
175
security/integrity/integrity.h
Normal file
175
security/integrity/integrity.h
Normal 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;
|
65
security/integrity/integrity_audit.c
Normal file
65
security/integrity/integrity_audit.c
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue