firmware/br-ext-chip-allwinner/board/v83x/kernel/patches/00000-security_fivm_fivm_ma...

500 lines
11 KiB
Diff

diff -drupN a/security/fivm/fivm_main.c b/security/fivm/fivm_main.c
--- a/security/fivm/fivm_main.c 1970-01-01 03:00:00.000000000 +0300
+++ b/security/fivm/fivm_main.c 2022-06-12 05:28:14.000000000 +0300
@@ -0,0 +1,495 @@
+/*
+ * 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 ;
+}