* Copyright (C) 2017-2019 Huawei Technologies Duesseldorf GmbH
*
* Author: Roberto Sassu <roberto.sassu@huawei.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_digest_list.c
* Functions to manage digest lists.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#define MAX_DB_SIZE (64 * 1024 * 1024)
#define DEFAULT_DB_SIZE (CONFIG_IMA_DIGEST_DB_MEGABYTES * 1024 * 1024)
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/file.h>
#include <linux/namei.h>
#include <linux/xattr.h>
#include <linux/sched/mm.h>
#include <linux/magic.h>
#include "ima.h"
#include "ima_digest_list.h"
int ima_digest_db_max_size __ro_after_init = DEFAULT_DB_SIZE;
int ima_digest_db_size;
struct ima_h_table ima_digests_htable = {
.len = ATOMIC_LONG_INIT(0),
.queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
};
static int __init digest_db_max_size_setup(char *str)
{
int size;
char *retptr;
size = memparse(str, &retptr);
if (size < 0 || size > MAX_DB_SIZE || *retptr != '\0') {
pr_err("digest DB size should range from 0M to 64M\n");
return 0;
}
ima_digest_db_max_size = size;
pr_info_once("parse reserve digest DB memory: %s\n", str);
return 1;
}
__setup("ima_digest_db_size=", digest_db_max_size_setup);
static int __init digest_list_pcr_setup(char *str)
{
int pcr, ret;
ret = kstrtouint(str, 10, &pcr);
if (ret) {
pr_err("Invalid PCR number %s\n", str);
return 1;
}
if (pcr == CONFIG_IMA_MEASURE_PCR_IDX) {
pr_err("Default PCR cannot be used for digest lists\n");
return 1;
}
ima_digest_list_pcr = pcr;
ima_digest_list_actions |= IMA_MEASURE;
if (*str == '+')
ima_plus_standard_pcr = true;
return 1;
}
__setup("ima_digest_list_pcr=", digest_list_pcr_setup);
* Get/add/del functions *
*************************/
struct ima_digest *ima_lookup_digest(u8 *digest, enum hash_algo algo,
enum compact_types type)
{
struct ima_digest *d = NULL;
int digest_len = hash_digest_size[algo];
unsigned int key = ima_hash_key(digest);
rcu_read_lock();
hlist_for_each_entry_rcu(d, &ima_digests_htable.queue[key], hnext)
if (d->algo == algo && d->type == type &&
!memcmp(d->digest, digest, digest_len))
break;
rcu_read_unlock();
return d;
}
static int ima_add_digest_data_entry(u8 *digest, enum hash_algo algo,
enum compact_types type, u16 modifiers)
{
struct ima_digest *d;
int digest_len = hash_digest_size[algo];
unsigned int key = ima_hash_key(digest);
d = ima_lookup_digest(digest, algo, type);
if (d) {
d->modifiers |= modifiers;
if (d->count < (u16)(~((u16)0)))
d->count++;
return -EEXIST;
}
d = kmalloc(sizeof(*d) + digest_len, GFP_KERNEL);
if (d == NULL)
return -ENOMEM;
ima_digest_db_size += sizeof(struct ima_digest);
ima_digest_db_size += digest_len;
d->algo = algo;
d->type = type;
d->modifiers = modifiers;
d->count = 1;
memcpy(d->digest, digest, digest_len);
hlist_add_head_rcu(&d->hnext, &ima_digests_htable.queue[key]);
atomic_long_inc(&ima_digests_htable.len);
return 0;
}
static void ima_del_digest_data_entry(u8 *digest, enum hash_algo algo,
enum compact_types type)
{
struct ima_digest *d;
d = ima_lookup_digest(digest, algo, type);
if (!d)
return;
if (--d->count > 0)
return;
ima_digest_db_size -= sizeof(struct ima_digest);
ima_digest_db_size -= hash_digest_size[algo];
hlist_del_rcu(&d->hnext);
atomic_long_dec(&ima_digests_htable.len);
kfree(d);
}
* Compact list parser *
***********************/
struct compact_list_hdr {
u8 version;
u8 _reserved;
u16 type;
u16 modifiers;
u16 algo;
u32 count;
u32 datalen;
} __packed;
int ima_parse_compact_list(loff_t size, void *buf, int op)
{
u8 *digest;
void *bufp = buf, *bufendp = buf + size;
struct compact_list_hdr *hdr;
size_t digest_len;
int ret = 0, i;
if (!(ima_digest_list_actions & ima_policy_flag))
return -EACCES;
while (bufp < bufendp) {
if (bufp + sizeof(*hdr) > bufendp) {
pr_err("compact list, invalid data\n");
return -EINVAL;
}
hdr = bufp;
if (hdr->version != 1) {
pr_err("compact list, unsupported version\n");
return -EINVAL;
}
if (ima_canonical_fmt) {
hdr->type = le16_to_cpu(hdr->type);
hdr->modifiers = le16_to_cpu(hdr->modifiers);
hdr->algo = le16_to_cpu(hdr->algo);
hdr->count = le32_to_cpu(hdr->count);
hdr->datalen = le32_to_cpu(hdr->datalen);
}
if (hdr->algo >= HASH_ALGO__LAST)
return -EINVAL;
digest_len = hash_digest_size[hdr->algo];
if (hdr->type >= COMPACT__LAST) {
pr_err("compact list, invalid type %d\n", hdr->type);
return -EINVAL;
}
bufp += sizeof(*hdr);
for (i = 0; i < hdr->count; i++) {
if (bufp + digest_len > bufendp) {
pr_err("compact list, invalid data\n");
return -EINVAL;
}
digest = bufp;
bufp += digest_len;
if (op == DIGEST_LIST_OP_ADD)
ret = ima_add_digest_data_entry(digest,
hdr->algo, hdr->type, hdr->modifiers);
else if (op == DIGEST_LIST_OP_DEL)
ima_del_digest_data_entry(digest, hdr->algo,
hdr->type);
if (ret < 0 && ret != -EEXIST)
return ret;
}
if (i != hdr->count ||
bufp != (void *)hdr + sizeof(*hdr) + hdr->datalen) {
pr_err("compact list, invalid data\n");
return -EINVAL;
}
}
return bufp - buf;
}
* Digest list usage check *
***************************/
bool ima_check_measured_appraised(struct file *file)
{
struct integrity_iint_cache *iint;
if (!ima_digest_list_actions)
return true;
if (file_inode(file)->i_sb->s_magic == SECURITYFS_MAGIC ||
S_ISDIR(file_inode(file)->i_mode))
return true;
iint = integrity_iint_find(file_inode(file));
if (!iint) {
pr_err("%s not processed\n", file_dentry(file)->d_name.name);
return false;
}
mutex_lock(&iint->mutex);
if ((ima_digest_list_actions & IMA_MEASURE) &&
!(iint->flags & IMA_MEASURED)) {
pr_err("%s not measured\n", file_dentry(file)->d_name.name);
mutex_unlock(&iint->mutex);
return false;
}
if ((ima_digest_list_actions & IMA_APPRAISE) &&
(!(iint->flags & IMA_APPRAISED) ||
!test_bit(IMA_DIGSIG, &iint->atomic_flags))) {
pr_err("%s not appraised\n", file_dentry(file)->d_name.name);
mutex_unlock(&iint->mutex);
return false;
}
mutex_unlock(&iint->mutex);
return true;
}
struct ima_digest *ima_digest_allow(struct ima_digest *digest, int action)
{
if (!(ima_digest_list_actions & action))
return NULL;
return digest;
}
* Digest list loading at kernel init *
**************************************/
struct readdir_callback {
struct dir_context ctx;
struct path *path;
};
static int __init load_digest_list(struct dir_context *__ctx, const char *name,
int namelen, loff_t offset, u64 ino,
unsigned int d_type)
{
struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx);
struct path *dir = ctx->path;
struct dentry *dentry;
struct file *file;
u8 *xattr_value = NULL;
char *type_start, *format_start, *format_end;
void *datap = NULL;
loff_t size;
int ret;
if (!strcmp(name, ".") || !strcmp(name, ".."))
return 0;
type_start = strchr(name, '-');
if (!type_start)
return 0;
format_start = strchr(type_start + 1, '-');
if (!format_start)
return 0;
format_end = strchr(format_start + 1, '-');
if (!format_end)
return 0;
if (format_end - format_start - 1 != strlen("compact") ||
strncmp(format_start + 1, "compact", format_end - format_start - 1))
return 0;
dentry = lookup_one_len(name, dir->dentry, strlen(name));
if (IS_ERR(dentry))
return 0;
size = vfs_getxattr(dentry, XATTR_NAME_EVM, NULL, 0);
if (size < 0) {
size = vfs_getxattr_alloc(dentry, XATTR_NAME_IMA,
(char **)&xattr_value, 0, GFP_NOFS);
if (size < 0 || xattr_value[0] != EVM_IMA_XATTR_DIGSIG)
goto out;
}
file = file_open_root(dir, name, O_RDONLY, 0);
if (IS_ERR(file)) {
pr_err("Unable to open file: %s (%ld)", name, PTR_ERR(file));
goto out;
}
ret = kernel_read_file(file, 0, &datap, INT_MAX, NULL,
READING_DIGEST_LIST);
if (ret < 0) {
pr_err("Unable to read file: %s (%d)", name, ret);
goto out_fput;
}
size = ret;
if (size > ima_digest_db_max_size - ima_digest_db_size) {
pr_err_once("digest DB is full: %d\n", ima_digest_db_size);
goto out_vfree;
}
if (!ima_check_measured_appraised(file))
goto out_vfree;
ret = ima_parse_compact_list(size, datap, DIGEST_LIST_OP_ADD);
if (ret < 0)
pr_err("Unable to parse file: %s (%d)", name, ret);
out_vfree:
vfree(datap);
out_fput:
fput(file);
out:
kfree(xattr_value);
return 0;
}
static void ima_exec_parser(void)
{
char *argv[4] = {NULL}, *envp[1] = {NULL};
argv[0] = (char *)CONFIG_IMA_PARSER_BINARY_PATH;
argv[1] = "add";
argv[2] = (char *)CONFIG_IMA_DIGEST_LISTS_DIR;
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
}
void __init ima_load_digest_lists(void)
{
struct path path;
struct file *file;
int ret;
struct readdir_callback buf = {
.ctx.actor = load_digest_list,
};
if (!(ima_digest_list_actions & ima_policy_flag))
return;
ret = kern_path(CONFIG_IMA_DIGEST_LISTS_DIR, 0, &path);
if (ret)
return;
file = dentry_open(&path, O_RDONLY, current_cred());
if (IS_ERR(file))
goto out;
buf.path = &path;
iterate_dir(file, &buf.ctx);
fput(file);
out:
path_put(&path);
ima_exec_parser();
}
* Parser check *
****************/
bool ima_check_current_is_parser(void)
{
struct integrity_iint_cache *parser_iint;
struct file *parser_file;
struct mm_struct *mm;
mm = get_task_mm(current);
if (!mm)
return false;
parser_file = get_mm_exe_file(mm);
mmput(mm);
if (!parser_file)
return false;
parser_iint = integrity_iint_find(file_inode(parser_file));
fput(parser_file);
if (!parser_iint)
return false;
if (!(parser_iint->flags & IMA_COLLECTED))
return false;
return ima_lookup_digest(parser_iint->ima_hash->digest,
parser_iint->ima_hash->algo, COMPACT_PARSER);
}
struct task_struct *parser_task;
void ima_set_parser(void)
{
parser_task = current;
}
void ima_unset_parser(void)
{
parser_task = NULL;
}
bool ima_current_is_parser(void)
{
return (current == parser_task);
}