/* * Copyright (C) 2014 Allwinner Ltd. * * Author: * Ryan Chen * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 ; }