SmartAudio/lichee/linux-4.9/security/fivm/fivm_main.c

496 lines
10 KiB
C
Executable File

/*
* Copyright (C) 2014 Allwinner Ltd.
*
* Author:
* Ryan Chen <ryanchen@allwinnertech.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: fiv_main.c
*/
#include <linux/module.h>
#include <linux/file.h>
#include <linux/binfmts.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/crypto.h>
#include <linux/crc32.h>
#include <linux/bsearch.h>
#include <linux/spinlock.h>
#include <linux/fivm.h>
int fivm_debug;
#include "fivm.h"
static int fivm_initialized;
static struct FILE_SIG *file_sig_table;
static struct FILE_SIG_HEAD *file_sig_head;
static const char ROOT[] = "/system";
DEFINE_MUTEX(_fid_mutex);
#ifdef FIVM_DEBUG_TIMMING
#include <linux/time.h>
static void fivm_gettime(const char *str)
{
struct timeval t;
do_gettimeofday(&t);
printk("[FIVM timer]%s: %lu.%lu\n", str, t.tv_sec, t.tv_usec);
}
#else
static void fivm_gettime(const char *str)
{
return;
}
#endif
static char _p_dir[1024];
static int __walk_to_root(struct file *file, char *pathname,
struct fivm_path *search_fp)
{
struct dentry *p;
char *start, *f_path;
int rc = -1 ;
int loop, len;
mutex_lock(&_fid_mutex);
p = file->f_path.dentry;
start = _p_dir + sizeof(_p_dir) - 1;/*Reserved a null for string end*/
memset(_p_dir, 0, sizeof(_p_dir));
for (loop = 0; loop < 16 && p; loop++, p = p->d_parent) {
if (*(p->d_name.name) == '/')
break;
else {
len = strnlen(p->d_name.name, MAX_NAME_LEN);
start -= len;
memcpy(start, p->d_name.name, len);
*(--start) = '/';
}
}
dprintk("FIVM find root path : %s\n", start);
if (!memcmp(start, ROOT, sizeof(ROOT) - 1)) {
len = strnlen(start, FILE_NAME_LEN);
f_path = kmalloc(len, GFP_KERNEL);
if (!f_path) {
derr("Out of memory\n");
goto out;
}
memcpy(f_path, start, len);
search_fp->path = f_path;
search_fp->flag = FIVM_PART_PATH ;
rc = 0;
}
out:
mutex_unlock(&_fid_mutex);
return rc;
}
static int _find_dir(struct file *file, char *pathname,
struct fivm_path *search_fp)
{
if (unlikely(!pathname)) {
printk("Null file path\n");
return -1;
}
if (likely(*pathname == '/'))
do {
if (!memcmp(pathname, ROOT, sizeof(ROOT) - 1)) {
search_fp->path = pathname;
search_fp->flag = FIVM_FULL_PATH;
dprintk("Root File path: %s\n", pathname);
return 0;
}
} while (0);
else {
return __walk_to_root(file, pathname, search_fp);
}
return -1;
}
/*Incremental crc table*/
static int _cmp(const void *a, const void *b)
{
const int *fa = a;
const struct FILE_SIG *fb = b;
if (*fa < fb->crc)
return -1;
if (*fa > fb->crc)
return 1;
return 0;
}
static struct FILE_SIG *_find_file(char *file_name)
{
struct FILE_SIG *fs;
int crc, slen;
size_t num, size;
void *base;
base = file_sig_table;
num = file_sig_head->actual_cnt ;
size = sizeof(struct FILE_SIG);
slen = strnlen(file_name, FILE_NAME_LEN);
crc = ~crc32_le(~0, file_name, slen);
dprintk("Find_file debug: name %s, crc 0x%x", file_name, crc);
fs = bsearch(&crc, base, num, size, _cmp);
if (!fs) {
derr("Find file %s crc fail\n", file_name);
return NULL;
}
if (strncmp(file_name, fs->name, slen)) {
derr("Find file %s name fail\n", file_name);
return NULL;
}
return fs;
}
static void rel_fivm_path(struct fivm_path *fp)
{
if (fp->flag == FIVM_PART_PATH &&
fp->path)
kfree(fp->path);
}
static int process_verify(struct file *file, char *pathname)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct FILE_SIG *fs;
int rc = 0;
char digest[SHA_DIG_MAX];
struct fivm_path search_fp = {NULL, 0};
fivm_gettime("Enter process verication");
if (!fivm_initialized || !S_ISREG(inode->i_mode))
return 0;
rc = _find_dir(file, pathname, &search_fp);
if (rc != 0) {
return 0; /*Nothing to do, Just go on */
}
fivm_gettime("Find directory name ");
fs = _find_file(search_fp.path);
if (!fs) {
derr("Can't find file %s in sha table\n", search_fp.path);
goto out;
}
if (fs->flag) {
dprintk("Had verified\n");
goto out;
}
fivm_gettime("Find file name ");
memset(digest, 0, SHA_DIG_MAX);
rc = fivm_calc_hash(file, digest);
if (rc) {
derr("fivm_calc_hash fail\n");
goto out ;
}
fivm_gettime("Calc file digest ");
if (memcmp(digest, fs->sha, SHA_DIG_MAX)) {
derr("Sha compare fail\n");
derr("Local calculate:\n");
print_hex_dump_bytes("", DUMP_PREFIX_NONE,
digest, SHA_DIG_MAX);
derr("Store value:\n");
print_hex_dump_bytes("", DUMP_PREFIX_NONE,
fs->sha, SHA_DIG_MAX);
rc = -1;
goto out;
}
fs->flag = 1;
rc = 0;
out:
rel_fivm_path(&search_fp);
return rc;
}
/**
* fivm_file_mmap
* @file: pointer to the file to be verify
* @prot: contains the protection that will be applied by the kernel.
*
* Return 0 on success, an error code on failure.
*/
static int _fivm_mmap_verify(struct file *file, unsigned long prot)
{
int rc;
if (!file)
return 0;
rc = process_verify(file, (char *)file->f_path.dentry->d_name.name);
return rc;
}
/**
* fivm_open_verify - verify file against to the hash table
* @file: pointer to the file to be verify
* @mask:
*
* Return 0 on success, an error code on failure.
*/
static int _fivm_open_verify(struct file *file, const char *pathname, int mask)
{
int rc;
if (!file || !pathname)
return 0;
rc = process_verify(file, (char *)pathname);
return rc;
}
#ifdef FIVM_LKM_DEBUG
static int _file_meta_read(
char *filename,
char *buf,
ssize_t len,
int offset
)
{
struct file *fd;
int retlen = -1;
mm_segment_t old_fs;
if (!filename || !buf) {
derr("- filename/buf NULL\n");
return (-EINVAL);
}
old_fs = get_fs();
set_fs(KERNEL_DS);
fd = filp_open(filename, O_RDONLY, 0);
if (IS_ERR(fd)) {
derr(" -file open fail\n");
return -1;
}
do {if ((fd->f_op == NULL) || (fd->f_op->read == NULL)) {
derr(" -file can't to open!!\n");
break;
}
if (fd->f_pos != offset) {
if (fd->f_op->llseek) {
if (fd->f_op->llseek(fd, offset, 0) != offset) {
derr(" -failed to seek!!\n");
break;
}
} else {
fd->f_pos = offset;
}
}
retLen = fd->f_op->read(fd, buf, len, &fd->f_pos);
} while (false);
filp_close(fd, NULL);
set_fs(old_fs);
return retlen;
}
static const char *sig_table = "/dev/block/by-name/verity_block";
static int read_file_hash_table(void)
{
int cnt, rlen,
rc = -1,
offset;
file_sig_head = kzalloc(sizeof(*file_sig_head), GFP_KERNEL);
if (!file_sig_head) {
derr(" out of memory\n");
return -ENOMEM;
}
rlen = sizeof(struct FILE_SIG_HEAD);
offset = sizeof(struct FILE_LIST_HEAD);
rc = _file_meta_read(
(char *)sig_table,
(char *)file_sig_head,
rlen,
offset);
if (rc != rlen || file_sig_head->magic != FILE_SIG_MAGIC) {
derr("Read file %s fail\n", sig_table);
rc = -EIO;
goto out;
}
cnt = file_sig_head->actual_cnt;
if (cnt > DIR_MAX_FILE_NUM) {
derr("out of files count 0x%x\n", cnt);
rc = -1;
goto out;
}
rlen = sizeof(struct FILE_SIG)*cnt;
file_sig_table = kzalloc(rlen, GFP_KERNEL);
if (!file_sig_table) {
derr(" out of memory\n");
rc = -ENOMEM ;
goto out;
}
offset += sizeof(*file_sig_head);
rc = _file_meta_read(
(char *)sig_table,
(char *)file_sig_table,
rlen,
offset
);
if (rc != rlen) {
derr("Read file %s fail\n", sig_table);
rc = -EIO;
goto out;
}
rc = 0;
out:
if (file_sig_table)
kfree(file_sig_table);
if (file_sig_head)
kfree(file_sig_head);
return rc;
}
#endif
extern int fivm_register_func(
int (*mmap_verify)(struct file *, unsigned long),
int (*open_verify)(struct file *, const char *, int));
extern int fivm_unregister_func(void);
raw_spinlock_t fivm_hook_lock;
int fivm_enable(void)
{
#ifdef FIVM_LKM_DEBUG
int rc;
rc = read_file_hash_table();
if (rc < 0) {
derr("read hash table fail\n");
return -1;
}
#endif
raw_spin_lock(&fivm_hook_lock);
fivm_register_func(_fivm_mmap_verify, _fivm_open_verify);
fivm_initialized = 1;
raw_spin_unlock(&fivm_hook_lock);
pr_info("FIVM: enable fivm done\n");
return 0;
}
int fivm_disable(void)
{
raw_spin_lock(&fivm_hook_lock);
fivm_unregister_func();
fivm_initialized = 0;
raw_spin_unlock(&fivm_hook_lock);
return 0 ;
}
/*Get fivm table from user*/
int fivm_set(void *arg)
{
struct fivm_param param;
void *sh, *st;
unsigned int sh_size, st_size;
derr("fivm_set trace arg: %p\n", arg);
if (!access_ok(VERIFY_READ, arg, sizeof(param))) {
derr("fivm param error\n");
return -EFAULT;
}
if (copy_from_user(&param, (void __user *)arg, sizeof(param))) {
derr("fivm_set get param failed\n");
return -EFAULT;
}
sh = param.sig_head;
sh_size = param.sig_head_size;
st = param.sig_table;
st_size = param.sig_table_size;
if (sh_size != sizeof(*file_sig_head) ||
st_size > FILE_NAME_LEN * DIR_MAX_FILE_NUM) {
derr("fivm set buf size error %d:%d\n",
sh_size, st_size);
return -EFAULT;
}
file_sig_head = kzalloc(sh_size, GFP_KERNEL);
if (!file_sig_head || file_sig_table) {
derr("out of memory \n");
return -EFAULT;
}
if (copy_from_user(file_sig_head, (void __user *)sh, sh_size)) {
derr("fivm sig head buffer from user fail\n");
kfree(file_sig_head);
return -EFAULT;
}
if (st_size != file_sig_head->actual_cnt * sizeof(struct FILE_SIG)) {
derr("wrong fivm sig table count\n");
kfree(file_sig_head);
return -EFAULT;
}
file_sig_table = kzalloc(st_size, GFP_KERNEL);
if (!file_sig_table) {
derr("out of memory file_sig_table\n");
kfree(file_sig_head);
return -EFAULT;
}
if (copy_from_user(file_sig_table, (void __user *)st, st_size)) {
derr("fivm sig table buffer from user fail\n");
kfree(file_sig_head);
kfree(file_sig_table);
return -EFAULT;
}
if (fivm_debug == 1) {
print_hex_dump_bytes("fivm io sig head buf:", DUMP_PREFIX_NONE,
file_sig_head, sh_size);
print_hex_dump_bytes("fivm io sig table buf:", DUMP_PREFIX_NONE,
file_sig_table, 16 * sizeof(struct FILE_SIG));
}
return 0;
}
int fivm_init(void)
{
pr_info("FIVM init\n");
raw_spin_lock_init(&fivm_hook_lock);
return 0;
}
int fivm_cleanup(void)
{
fivm_disable();
if (file_sig_head)
kfree(file_sig_head);
if (file_sig_table)
kfree(file_sig_table);
return 0 ;
}