496 lines
10 KiB
C
Executable File
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(¶m, (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 ;
|
|
}
|