diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index b6e074ac0227..3f45dc6b6b47 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -132,5 +132,6 @@ config INTEGRITY_AUDIT source "security/integrity/ima/Kconfig" source "security/integrity/evm/Kconfig" +source "security/integrity/dim/Kconfig" endif # if INTEGRITY diff --git a/security/integrity/Makefile b/security/integrity/Makefile index d0ffe37dc1d6..b7965e65344b 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -20,3 +20,4 @@ integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \ platform_certs/keyring_handler.o obj-$(CONFIG_IMA) += ima/ obj-$(CONFIG_EVM) += evm/ +obj-$(CONFIG_DIM) += dim/ diff --git a/security/integrity/dim/Kconfig b/security/integrity/dim/Kconfig new file mode 100644 index 000000000000..ab2f960fcba1 --- /dev/null +++ b/security/integrity/dim/Kconfig @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Dynamic Integrity Measurement Architecture +# +config DIM + bool "DIM (Dynamic Integrity Measurement)" + depends on INTEGRITY + default y + help + The Dynamic Integrity Measurement(DIM) architechture maintains a list + of hash values of executables and running processes. If an attacker + manages to change the executables being measured, we can tell. + + If your system has a TPM chip, then DIM can extend the hash value to + the TPM hardware, so that the TPM can prove to a third party whether + or not critical executables have been modified. + If unsure, say N. + +if DIM + +config DIM_CORE + tristate "DIM core module support" + default m + help + Enable the dim_core module of DIM architechture. + + This option enables the dim_core security module, which provides + integrity measurement for running processes. If unsure, say M. + +config DIM_HASH_SUPPORT_SM3 + bool "Enable SM3 hash algorithm support for DIM" + depends on DIM_CORE + default y + help + Enable support for the SM3 hash algorithm in DIM. + + If this option is enabled, the DIM module will be built with + support for the SM3 hash algorithm. If unsure, say N. + +config DIM_MONITOR + tristate "DIM monitor module support" + depends on DIM_CORE + default m + help + Enable the dim_monitor module of DIM architechture. + + This option enables the dim_monitor security module, which provides + integrity measurement for dim_core. If unsure, say N. + +endif # If DIM diff --git a/security/integrity/dim/Makefile b/security/integrity/dim/Makefile new file mode 100644 index 000000000000..53277cbcbb95 --- /dev/null +++ b/security/integrity/dim/Makefile @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. +# Makefile for DIM Linux Security Modules. +# +ifeq ($(CONFIG_DIM), y) + +# DIM_CORE Module +obj-$(CONFIG_DIM_CORE) += dim_core.o + +dim_core-y += core/dim_core_main.o +dim_core-y += core/dim_core_fs.o +dim_core-y += core/dim_core_mem_pool.o +dim_core-y += core/dim_core_measure.o +dim_core-y += core/dim_core_symbol.o +dim_core-y += core/dim_core_sig.o + +dim_core-y += core/tasks/dim_core_measure_kernel.o +dim_core-y += core/tasks/dim_core_measure_module.o +dim_core-y += core/tasks/dim_core_measure_process/dim_vm_hash.o +dim_core-y += core/tasks/dim_core_measure_process/dim_core_measure_process.o +dim_core-y += core/tasks/dim_core_measure_process/dim_core_measure_process_vma.o + +dim_core-y += core/policy/dim_core_policy.o +dim_core-y += core/policy/dim_core_policy_complex.o + +dim_core-y += core/static_baseline/dim_core_static_baseline.o +dim_core-y += core/static_baseline/dim_core_static_baseline_complex.o + +dim_core-y += common/dim_entry.o +dim_core-y += common/dim_utils.o +dim_core-y += common/dim_baseline.o +dim_core-y += common/dim_hash.o +dim_core-y += common/dim_measure_log.o +dim_core-y += common/dim_tpm.o +dim_core-y += common/dim_symbol.o +dim_core-y += common/dim_safe_func.o +dim_core-y += measure/dim_measure.o +dim_core-y += measure/dim_measure_baseline.o +dim_core-y += measure/dim_measure_task.o +dim_core-y += measure/dim_measure_utils.o +dim_core-y += measure/dim_measure_status.o + +# DIM_MONITOR Module +obj-$(CONFIG_DIM_MONITOR) += dim_monitor.o + +dim_monitor-y += monitor/dim_monitor_main.o +dim_monitor-y += monitor/dim_monitor_fs.o +dim_monitor-y += monitor/dim_monitor_measure.o +dim_monitor-y += monitor/dim_monitor_symbol.o + +dim_monitor-y += monitor/measure_task/dim_monitor_measure_data.o +dim_monitor-y += monitor/measure_task/dim_monitor_measure_text.o + +dim_monitor-y += common/dim_entry.o +dim_monitor-y += common/dim_hash.o +dim_monitor-y += common/dim_utils.o +dim_monitor-y += common/dim_measure_log.o +dim_monitor-y += common/dim_baseline.o +dim_monitor-y += common/dim_tpm.o +dim_monitor-y += common/dim_symbol.o +dim_monitor-y += common/dim_safe_func.o +dim_monitor-y += measure/dim_measure.o +dim_monitor-y += measure/dim_measure_baseline.o +dim_monitor-y += measure/dim_measure_task.o +dim_monitor-y += measure/dim_measure_utils.o +dim_monitor-y += measure/dim_measure_status.o + +subdir-ccflags-y := -I$(srctree)/$(src)/core +subdir-ccflags-y += -I$(srctree)/$(src)/core/static_baseline +subdir-ccflags-y += -I$(srctree)/$(src)/core/tasks +subdir-ccflags-y += -I$(srctree)/$(src)/core/tasks/dim_core_measure_process +subdir-ccflags-y += -I$(srctree)/$(src)/core/policy +subdir-ccflags-y += -I$(srctree)/$(src)/monitor +subdir-ccflags-y += -I$(srctree)/$(src)/monitor/measure_task +subdir-ccflags-y += -I$(srctree)/$(src)/common +subdir-ccflags-y += -I$(srctree)/$(src)/measure + +endif diff --git a/security/integrity/dim/common/dim_baseline.c b/security/integrity/dim/common/dim_baseline.c new file mode 100644 index 000000000000..17c58dde0396 --- /dev/null +++ b/security/integrity/dim/common/dim_baseline.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_rb.h" +#include "dim_baseline.h" +#include "dim_utils.h" +#include "dim_safe_func.h" + +static int dim_baseline_compare(struct dim_baseline *x, + struct dim_baseline *y) +{ + int ret = 0; + + if (x->type != y->type) + return x->type > y->type ? 1 : -1; + + ret = dim_strcmp(x->name, y->name); + if (ret != 0) + return ret; + + /* in rb_tree search code, x is the find data, y is the target data, + if the digest of y is zero, means we want to search digest by + baseline name and type. */ + return dim_digest_is_zero(&y->digest) ? 0 : + dim_digest_compare(&x->digest, &y->digest); +} + +/* +static int dim_baseline_rb_find(struct rb_root *root, + struct dim_baseline *data, + struct dim_baseline **find_data) +*/ +dim_rb_find(dim_baseline); +/* +static int dim_baseline_rb_add(struct rb_root *root, + struct dim_baseline *data, + struct dim_baseline **find_data) +*/ +dim_rb_add(dim_baseline); + +int dim_baseline_search_digest(struct dim_baseline_tree *root, const char *name, + int type, struct dim_digest *digest) +{ + int ret = 0; + struct dim_baseline *find = NULL; + struct dim_baseline search = { .name = name, .type = type }; + + if (root == NULL || name == NULL || digest == NULL || + !dim_baseline_type_is_valid(type)) + return -EINVAL; + + /* using zero digest means searching the digest */ + memset(&search.digest, 0, sizeof(struct dim_digest)); + + read_lock(&root->lock); + ret = dim_baseline_rb_find(&root->rb_root, &search, &find); + read_unlock(&root->lock); + if (ret < 0) + return ret; + + return dim_digest_copy(digest, &find->digest); +} + +bool dim_baseline_match(struct dim_baseline_tree *root, const char *name, + int type, struct dim_digest *digest) +{ + bool matched = false; + struct dim_baseline search = { .name = name, .type = type }; + + if (root == NULL || name == NULL || digest == NULL || + !dim_baseline_type_is_valid(type) || + dim_digest_copy(&search.digest, digest) < 0) + return false; + + read_lock(&root->lock); + matched = (dim_baseline_rb_find(&root->rb_root, &search, NULL) == 0); + read_unlock(&root->lock); + return matched; +} + +int dim_baseline_add(struct dim_baseline_tree *root, const char *name, + int type, struct dim_digest *digest) +{ + int ret = 0; + int buf_len = 0; + struct dim_baseline *baseline = NULL; + + if (root == NULL || root->malloc == NULL || root->free == NULL || + !dim_baseline_type_is_valid(type) || name == NULL || digest == NULL) + return -EINVAL; + + baseline = root->malloc(sizeof(struct dim_baseline)); + if (baseline == NULL) + return -ENOMEM; + + buf_len = strlen(name) + 1; + baseline->name = root->malloc(buf_len); + if (baseline->name == NULL) { + ret = -ENOMEM; + goto err; + } + + baseline->type = type; + ret = dim_digest_copy(&baseline->digest, digest); + if (ret < 0) + goto err; + + strncpy((char *)baseline->name, name, buf_len - 1); + ((char *)baseline->name)[buf_len - 1] = '\0'; + + write_lock(&root->lock); + ret = dim_baseline_rb_add(&root->rb_root, baseline, NULL); + write_unlock(&root->lock); + if (ret < 0) + goto err; + + return 0; +err: + if (baseline->name != NULL) + root->free((char *)baseline->name); + + root->free(baseline); + return ret; +} + +void dim_baseline_destroy_tree(struct dim_baseline_tree *root) +{ + struct dim_baseline *pos = NULL; + struct dim_baseline *n = NULL; + + if (root == NULL || root->free == NULL) + return; + + write_lock(&root->lock); + rbtree_postorder_for_each_entry_safe(pos, n, &root->rb_root, rb_node) { + root->free((void *)pos->name); + root->free(pos); + } + + root->rb_root = RB_ROOT; + write_unlock(&root->lock); +} + +int dim_baseline_init_tree(malloc_func malloc, free_func free, + struct dim_baseline_tree *root) +{ + if (root == NULL) + return -EINVAL; + + rwlock_init(&root->lock); + root->rb_root = RB_ROOT; + /* use kmalloc by default */ + root->malloc = malloc == NULL ? dim_kzalloc_gfp : malloc; + root->free = free == NULL ? dim_kfree : free; + return 0; +} diff --git a/security/integrity/dim/common/dim_baseline.h b/security/integrity/dim/common/dim_baseline.h new file mode 100644 index 000000000000..42922593be35 --- /dev/null +++ b/security/integrity/dim/common/dim_baseline.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_BASELINE_H +#define __DIM_BASELINE_H + +#include +#include "dim_hash.h" + +typedef void *(*malloc_func)(size_t); +typedef void (*free_func)(const void*); + +enum dim_baseline_type { + DIM_BASELINE_USER, /* baseline of user process */ + DIM_BASELINE_KERNEL, /* baseline of kernel or kernel modules */ + DIM_BASELINE_DATA, + DIM_BASELINE_TRAMPOLINE, + DIM_BASELINE_LAST, +}; + +static const char *const dim_baseline_name[DIM_BASELINE_LAST] = { + [DIM_BASELINE_USER] = "USER", + [DIM_BASELINE_KERNEL] = "KERNEL", + [DIM_BASELINE_DATA] = "DATA", + [DIM_BASELINE_TRAMPOLINE] = "TRAMPOLINE", +}; + +struct dim_baseline_tree { + struct rb_root rb_root; /* rb tree of baseline nodes */ + rwlock_t lock; + malloc_func malloc; + free_func free; +}; + +/* dim baseline node */ +struct dim_baseline { + struct rb_node rb_node; + const char *name; + int type; /* enum dim_baseline_type */ + struct dim_digest digest; +}; + +static inline bool dim_baseline_type_is_valid(int type) +{ + return (type < DIM_BASELINE_LAST && type >= 0); +} + +static inline int dim_baseline_get_type(const char *name) +{ + int idx = match_string(dim_baseline_name, DIM_BASELINE_LAST, name); + return idx < 0 ? DIM_BASELINE_LAST : idx; +} + +int dim_baseline_init_tree(malloc_func malloc, free_func free, + struct dim_baseline_tree *root); +void dim_baseline_destroy_tree(struct dim_baseline_tree *root); +int dim_baseline_search_digest(struct dim_baseline_tree *root, const char *name, + int type, struct dim_digest *digest); +bool dim_baseline_match(struct dim_baseline_tree *root, const char *name, + int type, struct dim_digest *digest); +int dim_baseline_add(struct dim_baseline_tree *root, const char *name, + int type, struct dim_digest *digest); + +#endif diff --git a/security/integrity/dim/common/dim_entry.c b/security/integrity/dim/common/dim_entry.c new file mode 100644 index 000000000000..f2454ceb2a9b --- /dev/null +++ b/security/integrity/dim/common/dim_entry.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include + +#include "dim_entry.h" + +#define WAIT_TIME_MAX 1000 + +int dim_entry_create(struct dim_entry *entry, struct dentry *parent) +{ + int ret = 0; + + if (entry == NULL || entry->name == NULL) + return -EINVAL; + + entry->dentry = securityfs_create_file(entry->name, entry->mode, + parent, NULL, entry->fops); + if (IS_ERR(entry->dentry)) { + ret = PTR_ERR(entry->dentry); + entry->dentry = NULL; + return ret; + } + + return 0; +} + +void dim_entry_remove(struct dim_entry *entry) +{ + int time_ms = 0; + + if (entry != NULL && entry->dentry != NULL) { + while (d_is_dir(entry->dentry) && + !simple_empty(entry->dentry) && + time_ms < WAIT_TIME_MAX) { + time_ms++; + msleep(1); + } + securityfs_remove(entry->dentry); + entry->dentry = NULL; + } +} + +int dim_entry_create_list(struct dim_entry **list, + unsigned int len, + struct dentry *parent) +{ + int ret = 0; + int i = 0; + + if (list == NULL) + return -EINVAL; + + for (i = 0; i < len; i++) { + ret = dim_entry_create(list[i], parent); + if (ret < 0) { + dim_entry_remove_list(list, len); + return ret; + } + } + + return 0; +} + +void dim_entry_remove_list(struct dim_entry **list, unsigned int len) +{ + int i = 0; + + for (i = 0; i < len; i++) + dim_entry_remove(list[i]); +} diff --git a/security/integrity/dim/common/dim_entry.h b/security/integrity/dim/common/dim_entry.h new file mode 100644 index 000000000000..bb023b63369b --- /dev/null +++ b/security/integrity/dim/common/dim_entry.h @@ -0,0 +1,198 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_ENTRY_H +#define __DIM_ENTRY_H + +#include +#include +#include + +#include "dim_measure_log.h" + +#define DIM_ENTRY_DIR_MASK (S_IFDIR | S_IXUSR | S_IRUSR) +#define DIM_ENTRY_RW_MASK (S_IWUSR | S_IRUSR) +#define DIM_ENTRY_W_MASK (S_IWUSR) +#define DIM_ENTRY_R_MASK (S_IRUSR) +#define DIM_FS_TMP_BUF_SIZE 512 + +struct dim_entry { + const char *name; + umode_t mode; + const struct file_operations *fops; + struct dentry *dentry; +}; + +/* the file interface for trigger by 'echo 1 > file_path' */ +#define dim_trigger_entry(sname, fname, function) \ +static ssize_t sname##_trigger(struct file *file, \ + const char __user *buf, \ + size_t count, loff_t *ppos) \ +{ \ + int val = 0; \ + int ret = 0; \ + \ + if (*ppos != 0 || count > 2) \ + return -EINVAL; \ + \ + ret = kstrtoint_from_user(buf, count, 10, &val); \ + if (ret < 0 || val != 1) \ + return ret < 0 ? ret : -EINVAL; \ + \ + ret = function(); \ + if (ret < 0) \ + return ret; \ + \ + return count; \ +} \ + \ +static const struct file_operations sname##_ops = { \ + .owner = THIS_MODULE, \ + .write = sname##_trigger, \ + .llseek = generic_file_llseek, \ +}; \ + \ +static struct dim_entry sname##_entry = { \ + .name = #fname, \ + .mode = DIM_ENTRY_W_MASK, \ + .fops = &sname##_ops, \ +}; + +/* the file interface for reading measure log */ +#define dim_measure_log_entry(sname, fname, root_ptr) \ +static void *measure_log_read_start(struct seq_file *m, loff_t *pos) \ +{ \ + read_lock(&(root_ptr)->lock); \ + return seq_list_start(&(root_ptr)->list_root, *pos); \ +} \ + \ +static void *measure_log_read_next(struct seq_file *m, \ + void *v, loff_t *pos) \ +{ \ + return seq_list_next(v, &(root_ptr)->list_root, pos); \ +} \ + \ +static void measure_log_read_stop(struct seq_file *m, void *v) \ +{ \ + read_unlock(&(root_ptr)->lock); \ +} \ + \ +static int measure_log_read_show(struct seq_file *m, void *v) \ +{ \ + struct dim_measure_log *log = \ + list_entry(v, struct dim_measure_log, node_order); \ + \ + return dim_measure_log_seq_show(m, log); \ +} \ + \ +const struct seq_operations sname##_seqops = { \ + .start = measure_log_read_start, \ + .next = measure_log_read_next, \ + .stop = measure_log_read_stop, \ + .show = measure_log_read_show, \ +}; \ + \ +static int sname##_open(struct inode *inode, struct file *file) \ +{ \ + return seq_open(file, &sname##_seqops); \ +} \ + \ +static const struct file_operations sname##_ops = { \ + .owner = THIS_MODULE, \ + .open = sname##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_release, \ +}; \ + \ +static struct dim_entry sname##_entry = { \ + .name = #fname, \ + .mode = DIM_ENTRY_R_MASK, \ + .fops = &sname##_ops, \ +}; + +/* the file interface for print string */ +#define dim_string_print_entry(sname, fname, function) \ +static ssize_t sname##_read(struct file *file, \ + char __user *buf, \ + size_t count, loff_t *ppos) \ +{ \ + char tmpbuf[DIM_FS_TMP_BUF_SIZE]; \ + ssize_t len; \ + \ + len = scnprintf(tmpbuf, \ + DIM_FS_TMP_BUF_SIZE, \ + "%s\n", \ + function()); \ + \ + return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); \ +} \ + \ +static const struct file_operations sname##_ops = { \ + .owner = THIS_MODULE, \ + .read = sname##_read, \ + .llseek = generic_file_llseek, \ +}; \ + \ +static struct dim_entry sname##_entry = { \ + .name = #fname, \ + .mode = DIM_ENTRY_R_MASK, \ + .fops = &sname##_ops, \ +}; + +/* the file interface for reading and writing uint parameter */ +#define dim_uint_rw_entry(sname, fname, read_func, write_func) \ +static ssize_t sname##_read(struct file *file, \ + char __user *buf, \ + size_t count, loff_t *ppos) \ +{ \ + long len = 0; \ + long val = 0; \ + char tmpbuf[DIM_FS_TMP_BUF_SIZE]; \ + \ + val = read_func(); \ + if (val < 0) \ + return val; \ + \ + len = scnprintf(tmpbuf, DIM_FS_TMP_BUF_SIZE, "%ld\n", val); \ + return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); \ +} \ + \ +static ssize_t sname##_write(struct file *file, \ + const char __user *buf, \ + size_t count, loff_t *ppos) \ +{ \ + int ret = 0; \ + unsigned int val; \ + \ + ret = kstrtouint_from_user(buf, count, 10, &val); \ + if (ret < 0) \ + return -EINVAL; \ + \ + ret = write_func(val); \ + return ret < 0 ? ret : count; \ +} \ + \ +static const struct file_operations sname##_ops = { \ + .owner = THIS_MODULE, \ + .read = sname##_read, \ + .write = sname##_write, \ + .llseek = generic_file_llseek, \ +}; \ + \ +static struct dim_entry sname##_entry = { \ + .name = #fname, \ + .mode = DIM_ENTRY_RW_MASK, \ + .fops = &sname##_ops, \ +}; + +int dim_entry_create(struct dim_entry *entry, struct dentry *parent); +void dim_entry_remove(struct dim_entry *entry); +int dim_entry_create_list(struct dim_entry **list, + unsigned int len, + struct dentry *parent); +void dim_entry_remove_list(struct dim_entry **list, unsigned int len); + +#endif + diff --git a/security/integrity/dim/common/dim_hash.c b/security/integrity/dim/common/dim_hash.c new file mode 100644 index 000000000000..6a17082e7688 --- /dev/null +++ b/security/integrity/dim/common/dim_hash.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_hash.h" +#include "dim_utils.h" + +static const char *allow_hash[] = { + "sha256", +#ifdef CONFIG_DIM_HASH_SUPPORT_SM3 + "sm3", +#endif +}; + +int dim_hash_init(const char *algo_name, struct dim_hash *hash) +{ + int ret = 0; + + if (algo_name == NULL || hash == NULL || + match_string(allow_hash, DIM_ARRAY_LEN(allow_hash), algo_name) < 0) + return -EINVAL; + + hash->algo = dim_hash_algo(algo_name); + if (hash->algo == HASH_ALGO__LAST) + return -EINVAL; + + hash->tfm = crypto_alloc_shash(algo_name, 0, 0); + if (IS_ERR(hash->tfm)) { + ret = PTR_ERR(hash->tfm); + hash->tfm = NULL; + } + + hash->name = algo_name; + return ret; +} + +void dim_hash_destroy(struct dim_hash *hash) +{ + if (hash == NULL) + return; + + crypto_free_shash(hash->tfm); + hash->tfm = NULL; +} + +int dim_hash_calculate(const void *data, unsigned int len, + struct dim_hash *alg, + struct dim_digest *digest) +{ + int ret = 0; + SHASH_DESC_ON_STACK(shash, alg->tfm); + + if (data == NULL || alg == NULL || digest == NULL || alg->tfm == NULL) + return -EINVAL; + + digest->algo = alg->algo; + shash->tfm = alg->tfm; + ret = crypto_shash_init(shash); + if (ret < 0) + return ret; + + ret = crypto_shash_update(shash, data, len); + if (ret < 0) + return ret; + + return crypto_shash_final(shash, digest->data); +} diff --git a/security/integrity/dim/common/dim_hash.h b/security/integrity/dim/common/dim_hash.h new file mode 100644 index 000000000000..a0554f01b99b --- /dev/null +++ b/security/integrity/dim/common/dim_hash.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_HASH_H +#define __DIM_HASH_H + +#include +#include + +#define DIM_MAX_DIGEST_SIZE 32 /* now is the size of SHA256 */ + +struct dim_hash { + const char *name; /* algorithm name */ + struct crypto_shash *tfm; + int algo; /* enum hash_algo */ +}; + +struct dim_digest { + int algo; /* enum hash_algo */ + char data[DIM_MAX_DIGEST_SIZE]; +}; + +static inline int dim_hash_algo(const char *name) +{ + int idx = match_string(hash_algo_name, HASH_ALGO__LAST, name); + return idx < 0 ? HASH_ALGO__LAST : idx; +} + +static inline const char *dim_hash_name(int algo) +{ + return (algo < 0 || algo >= HASH_ALGO__LAST) ? + NULL : hash_algo_name[algo]; +} + +static inline int dim_digest_size(int algo) +{ + return (algo < 0 || algo >= HASH_ALGO__LAST) ? + 0 : hash_digest_size[algo]; +} + +static inline bool dim_digest_is_zero(struct dim_digest *digest) +{ + struct dim_digest z_digest = { 0 }; + return memcmp(&z_digest, digest, sizeof(struct dim_digest)) == 0; +} + +static inline int dim_digest_compare(struct dim_digest *x, + struct dim_digest *y) +{ + if (x->algo != y->algo) + return x->algo > y->algo ? 1 : -1; + + return memcmp(x->data, y->data, dim_digest_size(x->algo)); +} + +static inline int dim_digest_copy(struct dim_digest *dst, + struct dim_digest *src) +{ + dst->algo = src->algo; + memcpy(dst->data, src->data, dim_digest_size(dst->algo)); + return 0; +} + +int dim_hash_init(const char *algo_name, struct dim_hash *hash); +void dim_hash_destroy(struct dim_hash *hash); +int dim_hash_calculate(const void *data, unsigned int len, + struct dim_hash *alg, + struct dim_digest *digest); + +#endif diff --git a/security/integrity/dim/common/dim_measure_log.c b/security/integrity/dim/common/dim_measure_log.c new file mode 100644 index 000000000000..59654a595898 --- /dev/null +++ b/security/integrity/dim/common/dim_measure_log.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_rb.h" +#include "dim_tpm.h" +#include "dim_safe_func.h" +#include "dim_measure_log.h" + +/* +static int dim_measure_name_rb_add(struct rb_root *root, + struct dim_baseline *data, + struct dim_baseline **find_data) +*/ +dim_rb_add(dim_measure_name); + +static int cal_measure_log_digest(const char *name, + struct dim_measure_log *info, + struct dim_hash *hash) +{ + /* templet hash is hash( + "file hash algorithm string size + file digest size" + + "file hash algorithm string" + + "file digest" + + "file path string size" + + "file path"), and the "\0" of algorithm/path string should + be calculated in also. + */ + int ret, size; + const char *algo_name = dim_hash_name(info->digest.algo); + int digest_size = dim_digest_size(info->digest.algo); + SHASH_DESC_ON_STACK(shash, hash->tfm); + + if (algo_name == NULL) + return -EINVAL; + + shash->tfm = hash->tfm; + ret = crypto_shash_init(shash); + if (ret < 0) + return ret; + + size = strlen(algo_name) + strlen(":") + 1 + digest_size; + if ((ret = crypto_shash_update(shash, (char *)&size, sizeof(size))) || + (ret = crypto_shash_update(shash, algo_name, strlen(algo_name))) || + (ret = crypto_shash_update(shash, ":", strlen(":") + 1)) || + (ret = crypto_shash_update(shash, info->digest.data, digest_size))) + return ret; + + size = strlen(name) + 1; + if ((ret = crypto_shash_update(shash, (char *)&size, sizeof(size))) || + (ret = crypto_shash_update(shash, name, size))) /* + "\0" */ + return ret; + + info->log_digest.algo = hash->algo; + return crypto_shash_final(shash, info->log_digest.data); +} + +int dim_measure_log_seq_show(struct seq_file *m, struct dim_measure_log *info) +{ + char log_digest_buf[(DIM_MAX_DIGEST_SIZE << 1) + 1] = { 0 }; + char digest_buf[(DIM_MAX_DIGEST_SIZE << 1) + 1] = { 0 }; + + bin2hex(log_digest_buf, info->log_digest.data, + dim_digest_size(info->log_digest.algo)); + + bin2hex(digest_buf, info->digest.data, + dim_digest_size(info->digest.algo)); + + seq_printf(m, "%d %s %s:%s %s %s\n", + info->pcr, + log_digest_buf, + dim_hash_name(info->digest.algo), + digest_buf, + dim_measure_log_name(info), + dim_measure_log_type_to_name(info->type)); + return 0; +} + +static int measure_info_insert(struct dim_measure_name *name, + struct dim_measure_log *info) +{ + struct list_head *list_search_from = NULL; + struct dim_measure_log *pos = NULL; + int cnt = 0; + + /* For error type log, search from last baseline */ + list_search_from = info->type == LOG_TAMPERED ? + name->log_cur->next : name->log_root.next; + + pos = list_entry(list_search_from, struct dim_measure_log, node); + list_for_each_entry_from(pos, &name->log_root, node) { + if (is_same_dim_measure_log(pos, info)) + return -EEXIST; + + cnt++; + } + + if (cnt > LOG_NUMBER_FILE_MAX && info->type == LOG_TAMPERED) + return -ENOSPC; + + list_add_tail(&info->node, &name->log_root); + return 0; +} + +static void measure_log_destroy_info(struct dim_measure_log *info) +{ + dim_kfree(info); +} + +static void measure_log_destroy_name(struct dim_measure_name *name) +{ + struct dim_measure_log *pos = NULL; + struct dim_measure_log *n = NULL; + + /* free all measure info */ + list_for_each_entry_safe(pos, n, &name->log_root, node) + measure_log_destroy_info(pos); + /* free self */ + dim_kfree(name->name); + dim_kfree(name); +} + +static int measure_log_create_name(const char *name_str, + struct dim_measure_name **name) +{ + struct dim_measure_name *new = NULL; + + new = dim_kzalloc_gfp(sizeof(struct dim_measure_name)); + if (new == NULL) + return -ENOMEM; + + new->name = dim_kstrdup_gfp(name_str); + if (new->name == NULL) { + dim_kfree(new); + return -ENOMEM; + } + + INIT_LIST_HEAD(&new->log_root); + new->log_cur = &new->log_root; + *name = new; + return 0; +} + +static int measure_log_create_info(char pcr, struct dim_digest *digest, + int flag, struct dim_measure_log **info) +{ + int ret = 0; + struct dim_measure_log *new = NULL; + + new = dim_kzalloc_gfp(sizeof(struct dim_measure_log)); + if (new == NULL) + return -ENOMEM; + + new->pcr = pcr; + new->type = flag; + ret = dim_digest_copy(&new->digest, digest); + if (ret < 0) { + dim_kfree(new); + return ret; + } + + *info = new; + return 0; +} + +static int measure_log_add_info(struct dim_measure_log_tree *root, + const char *name_str, + struct dim_measure_log *info) +{ + int ret = 0; + struct dim_measure_name *name = NULL; + struct dim_measure_name *name_find = NULL; + + ret = cal_measure_log_digest(name_str, info, root->hash); + if (ret < 0) + return ret; + + ret = measure_log_create_name(name_str, &name); + if (ret < 0) + return ret; + + write_lock(&root->lock); + ret = dim_measure_name_rb_add(&root->rb_root, name, &name_find); + if (ret == -EEXIST && name_find != NULL) { /* name node exist */ + measure_log_destroy_name(name); + info->name_head = name_find; + } else if (ret < 0) { /* unknown error */ + measure_log_destroy_name(name); + write_unlock(&root->lock); + return ret; + } else { /* name node insert ok */ + info->name_head = name; + } + + ret = measure_info_insert(info->name_head, info); + if (ret < 0) { + write_unlock(&root->lock); + return ret; + } + + list_add_tail(&info->node_order, &root->list_root); + root->count++; + write_unlock(&root->lock); + + return root->tpm == NULL && root->pcr != 0 ? 0 : + dim_tpm_pcr_extend(root->tpm, root->pcr, + &info->log_digest); +} + +static bool measure_log_is_full(struct dim_measure_log_tree *root) +{ + bool ret = false; + + read_lock(&root->lock); + ret = ((root->cap > 0) && (root->count >= root->cap)); + read_unlock(&root->lock); + return ret; +} + +int dim_measure_log_add(struct dim_measure_log_tree *root, + const char *name_str, + struct dim_digest *digest, int flag) +{ + int ret = 0; + struct dim_measure_log *info = NULL; + + if (root == NULL || name_str == NULL || + !is_valid_dim_measure_log_type(flag) || digest == NULL) + return -EINVAL; + + if (measure_log_is_full(root)) + return -ENOSPC; + + ret = measure_log_create_info(root->pcr, digest, flag, &info); + if (ret < 0) + return ret; + + ret = measure_log_add_info(root, name_str, info); + if (ret < 0) + measure_log_destroy_info(info); + + return ret; +} + +void dim_measure_log_refresh(struct dim_measure_log_tree *root) +{ + struct dim_measure_name *pos = NULL; + struct dim_measure_name *n = NULL; + + write_lock(&root->lock); + rbtree_postorder_for_each_entry_safe(pos, n, &root->rb_root, rb_node) + pos->log_cur = pos->log_root.prev; + + write_unlock(&root->lock); +} + +void dim_measure_log_destroy_tree(struct dim_measure_log_tree *root) +{ + struct dim_measure_name *pos = NULL; + struct dim_measure_name *n = NULL; + + write_lock(&root->lock); + rbtree_postorder_for_each_entry_safe(pos, n, &root->rb_root, rb_node) + measure_log_destroy_name(pos); + + INIT_LIST_HEAD(&root->list_root); + root->hash = NULL; + root->rb_root = RB_ROOT; + write_unlock(&root->lock); +} + +int dim_measure_log_init_tree(struct dim_measure_log_tree *root, + struct dim_hash *hash, + struct dim_tpm *tpm, + unsigned int cap, + unsigned int pcr) +{ + if (root == NULL || hash == NULL) + return -EINVAL; + + rwlock_init(&root->lock); + INIT_LIST_HEAD(&root->list_root); + root->hash = hash; + root->rb_root = RB_ROOT; + root->pcr = pcr; + root->tpm = tpm; + root->cap = cap; + return 0; +} diff --git a/security/integrity/dim/common/dim_measure_log.h b/security/integrity/dim/common/dim_measure_log.h new file mode 100644 index 000000000000..fcf53aae48d8 --- /dev/null +++ b/security/integrity/dim/common/dim_measure_log.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_MEASURE_LOG_H +#define __DIM_MEASURE_LOG_H + +#include +#include + +#include "dim_hash.h" +#include "dim_safe_func.h" + +#define DIM_NG "dim-ng" +#define LOG_MAX_LENGTH_PCR 3 +#define LOG_NUMBER_FILE_MAX 10 + +enum dim_measure_log_type { + LOG_NO_SATIC_BASELINE, + LOG_STATIC_BASELINE, + LOG_DYNAMIC_BASELINE, + LOG_TAMPERED, + LOG_MATCHED, + LOG_LAST, +}; + +static const char *dim_measure_log_type_name[LOG_LAST] = { + [LOG_NO_SATIC_BASELINE] = "[no static baseline]", + [LOG_STATIC_BASELINE] = "[static baseline]", + [LOG_DYNAMIC_BASELINE] = "[dynamic baseline]", + [LOG_TAMPERED] = "[tampered]", + [LOG_MATCHED] = "[matched]", +}; + +struct dim_measure_log_tree { + struct rb_root rb_root; /* rb tree root for searching measure log */ + struct list_head list_root; /* list root for printing logs in order */ + struct dim_hash *hash; /* algorithm for calculating log hash */ + struct dim_tpm *tpm; + unsigned int pcr; + rwlock_t lock; + unsigned int count; /* number of log */ + unsigned int cap; /* capacity of log */ +}; + +struct dim_measure_name { + struct rb_node rb_node; + struct list_head log_root; /* total dim_measure_log list */ + struct list_head *log_cur; /* current dim_measure_log list */ + const char *name; +}; + +struct dim_measure_log { + struct list_head node; + struct list_head node_order; + struct dim_measure_name *name_head; + char pcr; + int type; /* enum log_type */ + struct dim_digest digest; /* measure digest */ + struct dim_digest log_digest; /* measure log digest */ +}; + +static inline int dim_measure_name_compare(struct dim_measure_name *x, + struct dim_measure_name *y) +{ + return dim_strcmp(x->name, y->name); +} + +static inline const char *dim_measure_log_type_to_name(int type) +{ + return (type < 0 || type >= LOG_LAST) ? NULL : + dim_measure_log_type_name[type]; +} + +static inline const char *dim_measure_log_name(struct dim_measure_log *log) +{ + return log->name_head->name; +} + +static inline bool is_valid_dim_measure_log_type(int type) +{ + return type < LOG_LAST && type >= 0; +} + +static inline bool is_same_dim_measure_log(struct dim_measure_log *x, + struct dim_measure_log *y) +{ + if (x->type != y->type) + return false; + + return dim_digest_compare(&x->digest, &y->digest) == 0; +} + +int dim_measure_log_init_tree(struct dim_measure_log_tree *root, + struct dim_hash *hash, struct dim_tpm *tpm, + unsigned int cap, unsigned int pcr); +void dim_measure_log_destroy_tree(struct dim_measure_log_tree *root); +int dim_measure_log_add(struct dim_measure_log_tree *root, + const char *name_str, + struct dim_digest *digest, int flag); +int dim_measure_log_seq_show(struct seq_file *m, struct dim_measure_log *log); +void dim_measure_log_refresh(struct dim_measure_log_tree *root); + +#endif diff --git a/security/integrity/dim/common/dim_rb.h b/security/integrity/dim/common/dim_rb.h new file mode 100644 index 000000000000..fea2df5bab96 --- /dev/null +++ b/security/integrity/dim/common/dim_rb.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_RB_H +#define __DIM_RB_H + +#define dim_rb_find(name) \ +static int name##_rb_find(struct rb_root *root, struct name *data, \ + struct name **find_data) \ +{ \ + int ret = 0; \ + struct rb_node *cur = root->rb_node; \ + struct name *find = NULL; \ + \ + while (cur != NULL) { \ + find = rb_entry(cur, struct name, rb_node); \ + ret = name##_compare(find, data); \ + if (ret == 0) { \ + if (find_data != NULL) \ + *find_data = find; \ + return 0; \ + } \ + \ + cur = ret < 0 ? cur->rb_left : cur->rb_right; \ + } \ + \ + return -ENOENT; \ +}; + +#define dim_rb_add(name) \ +static int name##_rb_add(struct rb_root *root, struct name *data, \ + struct name **find_data) \ +{ \ + int ret = 0; \ + struct rb_node **cur = &(root->rb_node); \ + struct rb_node *parent = NULL; \ + struct name *find = NULL; \ + \ + while (*cur != NULL) { \ + find = rb_entry(*cur, struct name, rb_node); \ + ret = name##_compare(find, data); \ + if (ret == 0) { \ + if (find_data != NULL) \ + *find_data = find; \ + return -EEXIST; \ + } \ + \ + parent = *cur; \ + cur = ret < 0 ? &(*cur)->rb_left : &(*cur)->rb_right; \ + } \ + \ + rb_link_node(&data->rb_node, parent, cur); \ + rb_insert_color(&data->rb_node, root); \ + return 0; \ +}; + +#endif diff --git a/security/integrity/dim/common/dim_safe_func.c b/security/integrity/dim/common/dim_safe_func.c new file mode 100644 index 000000000000..f13168cae8cc --- /dev/null +++ b/security/integrity/dim/common/dim_safe_func.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_safe_func.h" + +#ifdef DIM_DEBUG_MEMORY_LEAK +atomic_t dim_alloc_num = ATOMIC_INIT(0);; + +void dim_check_memory_leak(void) +{ + unsigned int n = atomic_read(&dim_alloc_num); + if (n != 0) + dim_warn("warning: detect %u memory leakage\n", n); + else + dim_info("not detect memory leakage\n"); +} +#endif \ No newline at end of file diff --git a/security/integrity/dim/common/dim_safe_func.h b/security/integrity/dim/common/dim_safe_func.h new file mode 100644 index 000000000000..8cba7b2c6456 --- /dev/null +++ b/security/integrity/dim/common/dim_safe_func.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_SAFE_FUNC_H +#define __DIM_SAFE_FUNC_H + +#include +#include +#include +#include + +#include "dim_utils.h" + +#ifdef DIM_DEBUG_MEMORY_LEAK +extern atomic_t dim_alloc_num; + +static inline void dim_alloc_debug_inc(void) +{ + atomic_inc(&dim_alloc_num); +} + +static inline void dim_alloc_debug_dec(void) +{ + atomic_dec(&dim_alloc_num); +} + +static inline void dim_print_alloc_num(const char *s) +{ + dim_info("%s: dim_alloc_num=%d\n", s, atomic_read(&dim_alloc_num)); +} + +void dim_check_memory_leak(void); +#endif + +static inline void *dim_kzalloc_gfp(size_t size) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + void *data = kzalloc(size, GFP_KERNEL); + if (data != NULL) + dim_alloc_debug_inc(); + return data; +#else + return kzalloc(size, GFP_KERNEL); +#endif +} + +static inline void *dim_kcalloc_gfp(size_t n, size_t size) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + void *data = kcalloc(n, size, GFP_KERNEL); + if (data != NULL) + dim_alloc_debug_inc(); + return data; +#else + return kcalloc(n, size, GFP_KERNEL); +#endif +} + +static inline void *dim_krealloc_atom(const void *p, size_t new_size) +{ + return krealloc(p, new_size, GFP_ATOMIC); +} + +static inline void *dim_kmemdup_gfp(const void *src, size_t len) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + void *data = kmemdup(src, len, GFP_KERNEL); + if (data != NULL) + dim_alloc_debug_inc(); + return data; +#else + return kmemdup(src, len, GFP_KERNEL); +#endif +} + +static inline void dim_kfree(const void *objp) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + if (objp != NULL) + dim_alloc_debug_dec(); +#endif + kfree(objp); +} + +static inline void *dim_vzalloc(size_t size) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + void *data = vzalloc(size); + if (data != NULL) + dim_alloc_debug_inc(); + return data; +#else + return vzalloc(size); +#endif +} + +static inline void dim_vfree(void *data) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + if (data != NULL) + dim_alloc_debug_dec(); +#endif + vfree(data); +} + +static inline char *dim_kstrdup_gfp(const char *s) +{ +#ifdef DIM_DEBUG_MEMORY_LEAK + void *data = kstrdup(s, GFP_KERNEL); + if (data != NULL) + dim_alloc_debug_inc(); + return data; +#else + return kstrdup(s, GFP_KERNEL); +#endif +} + +static inline int dim_strcmp(const char *cs, const char *ct) +{ + if (cs == NULL || ct == NULL) + return -1; + + return strcmp(cs, ct); +} + +static inline int dim_strncmp(const char *cs, const char *ct, size_t count) +{ + if (cs == NULL || ct == NULL) + return -1; + + return strncmp(cs, ct, count); +} + +#endif diff --git a/security/integrity/dim/common/dim_symbol.c b/security/integrity/dim/common/dim_symbol.c new file mode 100644 index 000000000000..63824e6e8a90 --- /dev/null +++ b/security/integrity/dim/common/dim_symbol.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_safe_func.h" +#include "dim_symbol.h" + +static int find_kernel_symbol(unsigned long addr, + char *buf, + size_t *offset, + size_t *size) +{ + char tmp[KSYM_SYMBOL_LEN] = { 0 }; + + sprint_symbol(tmp, addr); + + memset(buf, 0, KSYM_NAME_LEN); + return sscanf(tmp, "%127[^+]+%lx/%lx", buf, offset, size) == 3 + ? 0 : -EINVAL; +} + +DIM_SYMBOL_LOOKUP_FUNC dim_get_symbol_lookup_func(void) +{ + unsigned long kaddr = (unsigned long)&sprint_symbol; + unsigned long prev = kaddr - 1; + unsigned long next = kaddr; + size_t offset, size; + int i, ret; + char symbol_name[KSYM_NAME_LEN] = { 0 }; + + for (i = 0; i < DIM_TRY_COUNT; i++) { + ret = find_kernel_symbol(kaddr, symbol_name, &offset, &size); + if (ret < 0 || offset > size) + break; + + if (dim_strcmp(symbol_name, DIM_KALLSYMS_LOOKUP_NAME) == 0) + return (DIM_SYMBOL_LOOKUP_FUNC)(kaddr - offset); + + if (kaddr == next) { + next = next + size - offset; + kaddr = prev; + } else { + prev = prev - offset - 1; + kaddr = next; + } + } + + return NULL; +} \ No newline at end of file diff --git a/security/integrity/dim/common/dim_symbol.h b/security/integrity/dim/common/dim_symbol.h new file mode 100644 index 000000000000..9ee8a22a398f --- /dev/null +++ b/security/integrity/dim/common/dim_symbol.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_SYMBOL_H +#define __DIM_SYMBOL_H + +#define DIM_KALLSYMS_LOOKUP_NAME "kallsyms_lookup_name" +#define DIM_TRY_COUNT 100 + +typedef void* (*DIM_SYMBOL_LOOKUP_FUNC)(const char *); + +DIM_SYMBOL_LOOKUP_FUNC dim_get_symbol_lookup_func(void); + +#endif \ No newline at end of file diff --git a/security/integrity/dim/common/dim_tpm.c b/security/integrity/dim/common/dim_tpm.c new file mode 100644 index 000000000000..35f3fac62360 --- /dev/null +++ b/security/integrity/dim/common/dim_tpm.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_safe_func.h" +#include "dim_tpm.h" + +int dim_tpm_init(struct dim_tpm *tpm, int algo) +{ + int ret = 0; + int i = 0; + + tpm->chip = tpm_default_chip(); + if (tpm->chip == NULL) + return -ENODEV; + + tpm->digests = dim_kcalloc_gfp(tpm->chip->nr_allocated_banks, + sizeof(struct tpm_digest)); + if (tpm->digests == NULL) { + ret = -ENOMEM; + goto err; + } + + tpm->bank = -1; + for (i = 0; i < tpm->chip->nr_allocated_banks; i++) { + tpm->digests[i].alg_id = tpm->chip->allocated_banks[i].alg_id; + if (tpm->chip->allocated_banks[i].crypto_id == algo) + tpm->bank = i; + + memset(tpm->digests[i].digest, 0xff, TPM_MAX_DIGEST_SIZE); + } + + if (tpm->bank == -1) { + ret = -ENOENT; /* fail to find matched TPM bank */ + goto err; + } + + return 0; +err: + put_device(&tpm->chip->dev); + if (tpm->digests != NULL) { + dim_kfree(tpm->digests); + tpm->digests = NULL; + } + + tpm->chip = NULL; + return ret; +} + +int dim_tpm_pcr_extend(struct dim_tpm *tpm, int pcr, struct dim_digest *digest) +{ + int size = 0; + + if (tpm == NULL || digest == NULL) + return -EINVAL; + + if (tpm->chip == NULL) + return 0; + + size = dim_digest_size(digest->algo); + if (size == 0 || size > TPM_MAX_DIGEST_SIZE) + return -EINVAL; + + memcpy(tpm->digests[tpm->bank].digest, digest->data, size); + return tpm_pcr_extend(tpm->chip, pcr, tpm->digests); +} + +void dim_tpm_destroy(struct dim_tpm *tpm) +{ + if (tpm == NULL || tpm->chip == NULL) + return; + + put_device(&tpm->chip->dev); + dim_kfree(tpm->digests); + tpm->chip = NULL; + tpm->digests = NULL; +} diff --git a/security/integrity/dim/common/dim_tpm.h b/security/integrity/dim/common/dim_tpm.h new file mode 100644 index 000000000000..c4c715fa2d4d --- /dev/null +++ b/security/integrity/dim/common/dim_tpm.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_TPM_H +#define __DIM_TPM_H + +#include +#include "dim_hash.h" + +#define DIM_PCR_MAX 128 + +struct dim_tpm { + struct tpm_chip *chip; + struct tpm_digest *digests; + int bank; +}; + +int dim_tpm_init(struct dim_tpm *tpm, int algo); +int dim_tpm_pcr_extend(struct dim_tpm *tpm, int pcr, struct dim_digest *digest); +void dim_tpm_destroy(struct dim_tpm *tpm); + +#endif \ No newline at end of file diff --git a/security/integrity/dim/common/dim_utils.c b/security/integrity/dim/common/dim_utils.c new file mode 100644 index 000000000000..6746d88b7e44 --- /dev/null +++ b/security/integrity/dim/common/dim_utils.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include + +#include "dim_safe_func.h" +#include "dim_utils.h" + +#define DIM_MAX_LINE_BUF (8 * 1024) + +int dim_get_absolute_path(const char *path, const char **result) +{ + int ret = 0; + struct path p; + char *buf = NULL; + char *apath = NULL; + + if (path == NULL) + return -EINVAL; + + ret = kern_path(path, LOOKUP_FOLLOW, &p); + if (ret < 0) + return ret; + + buf = dim_kzalloc_gfp(PATH_MAX); + if (buf == NULL) { + ret = -ENOMEM; + goto out; + } + + apath = d_path(&p, buf, PATH_MAX); + if (IS_ERR(apath)) { + ret = PTR_ERR(apath); + goto out; + } + + *result = dim_kstrdup_gfp(apath); + if (*result == NULL) { + ret = -ENOMEM; + goto out; + } +out: + path_put(&p); + dim_kfree(buf); + return ret; +} + +int dim_parse_line_buf(char *buf, loff_t len, int (*line_parser)(char *, int, void *), void *data) +{ + int ret = 0; + int i = 0; + int line_no = 1; + char *line = buf; + char *line_buf = NULL; + size_t line_len = 0; + + if (len == 0) + return -EINVAL; + + for (i = 0; i < len; i++) { + if (buf[i] != '\n' && i != len - 1) + continue; + + if (buf[i] == '\n') { + buf[i] = '\0'; + ret = line_parser(line, line_no, data); + line = &buf[i + 1]; + } else { + line_len = buf + i - line + 1; + if (line_len + 1 > DIM_MAX_LINE_BUF) { + dim_err("failed to alloc memory for line buff\n"); + return -ENOMEM; + } + + line_buf = dim_kzalloc_gfp(line_len + 1); + if (line_buf == NULL) + return -ENOMEM; + + memcpy(line_buf, line, line_len); + ret = line_parser(line_buf, line_no, data); + } + + if (ret < 0) { + /* + * if the parser returns -E2BIG, means the line number + * is too large, the excess lines will be ignored. + */ + ret = (ret == -E2BIG) ? 0 : ret; + goto out; + } + + line_no++; + } +out: + if (line_buf != NULL) + dim_kfree(line_buf); + + return ret; +} diff --git a/security/integrity/dim/common/dim_utils.h b/security/integrity/dim/common/dim_utils.h new file mode 100644 index 000000000000..db43546715f5 --- /dev/null +++ b/security/integrity/dim/common/dim_utils.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_UTILS_H +#define __DIM_UTILS_H + +#include +#include + +#define DIM_ARRAY_LEN(ARR) (sizeof(ARR) / sizeof(ARR[0])) + +#define dim_fmt(fmt) "%s: " fmt + +#define dim_err(fmt, ...) pr_err(dim_fmt(fmt), THIS_MODULE->name, ##__VA_ARGS__) +#define dim_warn(fmt, ...) pr_warn(dim_fmt(fmt), THIS_MODULE->name, ##__VA_ARGS__) +#define dim_info(fmt, ...) pr_info(dim_fmt(fmt), THIS_MODULE->name, ##__VA_ARGS__) +#define dim_devel(fmt, ...) + +int dim_get_absolute_path(const char *path, const char **result); +int dim_parse_line_buf(char *buf, loff_t len, int (*line_parser)(char *, int, void *), void *data); + +#endif diff --git a/security/integrity/dim/core/dim_core_fs.c b/security/integrity/dim/core/dim_core_fs.c new file mode 100644 index 000000000000..4a82e53206fd --- /dev/null +++ b/security/integrity/dim/core/dim_core_fs.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_utils.h" + +#include "dim_core_measure.h" +#include "dim_core_fs.h" + +/* + * measure trigger interface + * dim_entry struct: dim_measure_entry + * file entry name: measure + * function: dim_core_measure_blocking() + */ +dim_trigger_entry(dim_measure, measure, dim_core_measure_blocking); + +/* + * baseline_init trigger interface + * dim_entry struct: dim_baseline_init_entry + * file entry name: baseline_init + * function: dim_core_baseline_blocking(0) + */ +dim_trigger_entry(dim_baseline_init, baseline_init, + dim_core_baseline_blocking); + +/* + * measure log read interface + * dim_entry struct: dim_measure_log_entry + * file entry name: runtime_status + * status to read: dim_measure_log_tree + */ +dim_measure_log_entry(dim_measure_log, ascii_runtime_measurements, + &dim_core_handle.log); + +/* + * status print interface + * dim_entry struct: dim_status_entry + * file entry name: runtime_status + * print function: dim_core_status_print + */ +dim_string_print_entry(dim_status, runtime_status, dim_core_status_print); + +/* + * measure interval set and read interface + * dim_entry struct: dim_interval_entry + * file entry name: interval + * read function: dim_core_interval_get + * write function: dim_core_interval_set + */ +dim_uint_rw_entry(dim_interval, interval, dim_core_interval_get, + dim_core_interval_set); + +/* + * measure action set and read interface + * dim_entry struct: dim_tampered_action_entry + * file entry name: tampered_action + * read function: dim_core_measure_action_get + * write function: dim_core_measure_action_set + */ +dim_uint_rw_entry(dim_tampered_action, tampered_action, + dim_core_measure_action_get, dim_core_measure_action_set); + +/* + * dim directory + */ +static struct dim_entry dim_core_dir = { + .name = "dim", + .mode = DIM_ENTRY_DIR_MASK, + .fops = NULL, + .dentry = NULL, +}; + +static struct dim_entry *dim_core_files[] = { + &dim_measure_entry, + &dim_baseline_init_entry, + &dim_measure_log_entry, + &dim_status_entry, + &dim_interval_entry, + &dim_tampered_action_entry, +}; + +void dim_core_destroy_fs(void) +{ + unsigned int len = DIM_ARRAY_LEN(dim_core_files); + dim_entry_remove_list(dim_core_files, len); + dim_entry_remove(&dim_core_dir); +} + +int dim_core_create_fs(void) +{ + int ret = 0; + unsigned int len = DIM_ARRAY_LEN(dim_core_files); + + ret = dim_entry_create(&dim_core_dir, NULL); + if (ret < 0) { + dim_err("failed to create dim dir entry: %d\n", ret); + return ret; + } + + ret = dim_entry_create_list(dim_core_files, len, dim_core_dir.dentry); + if (ret < 0) + dim_entry_remove(&dim_core_dir); + + return ret; +} + +struct dim_entry *dim_root_entry(void) +{ + return &dim_core_dir; +} +EXPORT_SYMBOL_GPL(dim_root_entry); diff --git a/security/integrity/dim/core/dim_core_fs.h b/security/integrity/dim/core/dim_core_fs.h new file mode 100644 index 000000000000..4a18a1f8708d --- /dev/null +++ b/security/integrity/dim/core/dim_core_fs.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_FS_H +#define __DIM_CORE_FS_H + +#include "dim_entry.h" + +void dim_core_destroy_fs(void); +int dim_core_create_fs(void); +struct dim_entry *dim_root_entry(void); + +#endif diff --git a/security/integrity/dim/core/dim_core_main.c b/security/integrity/dim/core/dim_core_main.c new file mode 100644 index 000000000000..d4cc870eefa2 --- /dev/null +++ b/security/integrity/dim/core/dim_core_main.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_safe_func.h" + +#include "dim_core_policy.h" +#include "dim_core_symbol.h" +#include "dim_core_fs.h" +#include "dim_core_measure.h" +#include "dim_core_mem_pool.h" +#include "dim_core_sig.h" + +/* common measurement configuration */ +static struct dim_measure_cfg cfg = { + .alg_name = DIM_CORE_HASH_DEFAULT, + .log_cap = DIM_CORE_LOG_CAP_DEFAULT, +}; + +module_param_named(measure_log_capacity, cfg.log_cap, uint, 0); +MODULE_PARM_DESC(measure_log_capacity, "Max number of measure log"); + +module_param_named(measure_schedule, cfg.schedule_ms, uint, 0); +MODULE_PARM_DESC(measure_schedule, "Schedule time (ms) for each measure object"); + +module_param_named(measure_hash, cfg.alg_name, charp, 0); +MODULE_PARM_DESC(measure_hash, "Hash algorithm for measurement"); + +module_param_named(measure_pcr, cfg.pcr, uint, 0); +MODULE_PARM_DESC(measure_pcr, "TPM PCR index to extend measure log"); + +/* special measurement configuration for dim_core */ +static unsigned int measure_interval = 0; +static bool signature = false; + +module_param(measure_interval, uint, 0); +MODULE_PARM_DESC(measure_interval, "Interval time (min) for automatic measurement"); + +module_param(signature, bool, 0); +MODULE_PARM_DESC(signature, "Require signature for policy and static baseline"); + +static int __init dim_core_init(void) +{ + int ret; + + ret = dim_core_kallsyms_init(); + if (ret < 0) { + dim_err("failed to initialize dim kernel symbol: %d\n", ret); + goto err; + } + + ret = dim_mem_pool_init(); + if (ret < 0) { + dim_err("failed to initialize dim memory pool: %d\n", ret); + goto err; + } + + if (signature) { + ret = dim_core_sig_init(); + if (ret < 0) { + dim_err("failed to initialize dim signature: %d\n", ret); + goto err; + } + } + + ret = dim_core_measure_init(&cfg, measure_interval); + if (ret < 0) { + dim_err("failed to initialize dim measurement: %d\n", ret); + goto err; + } + + ret = dim_core_create_fs(); + if (ret < 0) { + dim_err("failed to create dim fs entry: %d\n", ret); + goto err; + } + + return 0; +err: + dim_core_destroy_fs(); + dim_core_measure_destroy(); + dim_mem_pool_destroy(); + + if (signature) + dim_core_sig_destroy(); + + return ret; +} + +static void __exit dim_core_exit(void) +{ + dim_core_destroy_fs(); + dim_core_measure_destroy(); + dim_mem_pool_destroy(); + + if (signature) + dim_core_sig_destroy(); + +#ifdef DIM_DEBUG_MEMORY_LEAK + dim_check_memory_leak(); +#endif +} + +module_init(dim_core_init); +module_exit(dim_core_exit); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/dim/core/dim_core_measure.c b/security/integrity/dim/core/dim_core_measure.c new file mode 100644 index 000000000000..caf767cc6e9a --- /dev/null +++ b/security/integrity/dim/core/dim_core_measure.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_core_policy.h" +#include "dim_core_mem_pool.h" +#include "dim_core_static_baseline.h" +#include "dim_core_measure_task.h" +#include "dim_core_measure.h" + +/* measurement tasks */ +static struct dim_measure_task *dim_core_tasks[] = { + &dim_core_measure_task_user_text, + &dim_core_measure_task_kernel_text, + &dim_core_measure_task_module_text, +}; + +/* the global measurement handle */ +struct dim_measure dim_core_handle = { + .task_list = LIST_HEAD_INIT(dim_core_handle.task_list), +}; + +/* lock to prevent trigger multiple measurement */ +DEFINE_MUTEX(dim_core_measure_lock); + +/* dim measurement work */ +static struct workqueue_struct *dim_work_queue = NULL; +static struct delayed_work dim_measure_work; +static struct work_struct dim_baseline_work; + +/* special measurement parameters for dim_core */ +static atomic_t measure_interval = ATOMIC_INIT(0); +static atomic_t measure_action = ATOMIC_INIT(0); + +/* interface to print measure status string */ +const char *dim_core_status_print(void) +{ + return dim_measure_status_print(&dim_core_handle); +} + +/* interface to get tampered action */ +long dim_core_measure_action_get(void) +{ + return atomic_read(&measure_action); +} + +/* interface to set measure action */ +int dim_core_measure_action_set(unsigned int act) +{ + if (act >= DIM_MEASURE_ACTION_MAX) + return -ERANGE; + + atomic_set(&measure_action, act); + return 0; +} + +/* interface to get measure interval */ +long dim_core_interval_get(void) +{ + return atomic_read(&measure_interval); +} + +/* interface to set measure interval */ +int dim_core_interval_set(unsigned int min) +{ + unsigned long jiffies = 0; + + if (min > DIM_INTERVAL_MAX || + (unsigned long)min * DIM_MINUTE_TO_SEC > MAX_SEC_IN_JIFFIES) + return -ERANGE; + + atomic_set(&measure_interval, min); + if (min == 0) { + dim_info("cancel dim timed measure work\n"); + cancel_delayed_work_sync(&dim_measure_work); + } else { + jiffies = nsecs_to_jiffies64((unsigned long)min * + DIM_MINUTE_TO_NSEC); + dim_info("modify dim measure interval to %u min " + "(jittfies = 0x%lx)\n", min, jiffies); + mod_delayed_work(dim_work_queue, &dim_measure_work, jiffies); + } + + return 0; +} + +static int baseline_prepare(struct dim_measure *m) +{ + int ret = 0; + + if (m == NULL) + return -EINVAL; + + /* 1. reload dim policy */ + ret = dim_core_policy_load(); + if (ret < 0) { + dim_err("failed to load dim core policy: %d\n", ret); + return ret; + } + + /* 2. clear dim baseline */ + dim_baseline_destroy_tree(&m->static_baseline); + dim_baseline_destroy_tree(&m->dynamic_baseline); + + /* 3. reload dim baseline */ + ret = dim_core_static_baseline_load(m); + if (ret < 0) { + dim_err("failed to load dim static baseline: %d\n", ret); + dim_core_policy_destroy(); + return ret; + } + + /* 4. refresh measure log */ + dim_measure_log_refresh(&m->log); + return 0; +} + +static void queue_delayed_measure_work(void) +{ + unsigned long jiffies = 0; + unsigned int interval = atomic_read(&measure_interval); + + if (interval == 0) + return; + + jiffies = nsecs_to_jiffies64((unsigned long)interval * + DIM_MINUTE_TO_NSEC); + queue_delayed_work(dim_work_queue, &dim_measure_work, jiffies); +} + +static void measure_work_cb(struct work_struct *work) +{ + dim_measure_task_measure(DIM_MEASURE, &dim_core_handle); + queue_delayed_measure_work(); +} + +static void baseline_work_cb(struct work_struct *work) +{ + dim_measure_task_measure(DIM_BASELINE, &dim_core_handle); + /* if baseline is failed, dont perform measurement */ + if (dim_measure_status_error(&dim_core_handle)) + return; + + queue_delayed_measure_work(); +} + +/* trigger a measurement and wait for it to complete */ +int dim_core_measure_blocking(void) +{ + int ret = 0; + + if (!mutex_trylock(&dim_core_measure_lock)) + return -EBUSY; + + /* clean the running work */ + flush_delayed_work(&dim_measure_work); + cancel_delayed_work_sync(&dim_measure_work); + /* queue and flush measure work */ + queue_delayed_work(dim_work_queue, &dim_measure_work, 0); + flush_delayed_work(&dim_measure_work); + + /* check error status */ + if (dim_measure_status_error(&dim_core_handle)) + ret = -EFAULT; + + mutex_unlock(&dim_core_measure_lock); + return ret; +} + +/* trigger a dynamic baseline and wait for it to complete */ +int dim_core_baseline_blocking(void) +{ + int ret = 0; + + if (!mutex_trylock(&dim_core_measure_lock)) + return -EBUSY; + + /* clean the running work */ + flush_delayed_work(&dim_measure_work); + cancel_delayed_work_sync(&dim_measure_work); + + /* queue and flush baseline work */ + queue_work(dim_work_queue, &dim_baseline_work); + flush_work(&dim_baseline_work); + + /* check error status */ + if (dim_measure_status_error(&dim_core_handle)) + ret = -EFAULT; + + mutex_unlock(&dim_core_measure_lock); + return ret; +} + +int dim_core_measure_init(struct dim_measure_cfg *cfg, unsigned int interval) +{ + int ret = 0; + + /* set the special baseline memory functions */ + cfg->dyn_malloc = dim_mem_pool_alloc; + cfg->dyn_free = dim_mem_pool_free; + + /* init the measurement handle */ + ret = dim_measure_init(&dim_core_handle, cfg); + if (ret < 0) { + dim_err("failed to init measurement handle\n"); + return ret; + } + + /* set the baseline prepare function */ + dim_core_handle.baseline_prepare = baseline_prepare; + + /* register all measurement tasks */ + ret = dim_measure_tasks_register(&dim_core_handle, dim_core_tasks, + DIM_ARRAY_LEN(dim_core_tasks)); + if (ret < 0) { + dim_err("failed to register measure tasks: %d\n", ret); + goto err; + } + + /* init the measurement working thread */ + dim_work_queue = create_singlethread_workqueue("dim_core"); + if (dim_work_queue == NULL) { + ret = -ENOMEM; + dim_err("failed to create dim work queue: %d\n", ret); + goto err; + } + + /* init the measurement work */ + INIT_WORK(&dim_baseline_work, baseline_work_cb); + INIT_DELAYED_WORK(&dim_measure_work, measure_work_cb); + + /* if the interval is set, start to do baseline and measure */ + if (interval) { + ret = dim_core_baseline_blocking(); + if (ret < 0) { + dim_err("failed to do baseline init: %d\n", ret); + goto err; + } + + ret = dim_core_interval_set(interval); + if (ret < 0) + dim_warn("failed to set measure interval: %d\n", ret); + } + + return 0; +err: + dim_measure_destroy(&dim_core_handle); + if (dim_work_queue != NULL) { + destroy_workqueue(dim_work_queue); + dim_work_queue = NULL; + } + + return ret; +} + +void dim_core_measure_destroy(void) +{ + mutex_lock(&dim_core_measure_lock); + if (dim_work_queue != NULL) { + /* 1. wait the measure work to finish */ + flush_delayed_work(&dim_measure_work); + cancel_delayed_work_sync(&dim_measure_work); + /* 2. do clean job */ + destroy_workqueue(dim_work_queue); + } + + dim_measure_destroy(&dim_core_handle); + dim_core_policy_destroy(); + mutex_unlock(&dim_core_measure_lock); +} diff --git a/security/integrity/dim/core/dim_core_measure.h b/security/integrity/dim/core/dim_core_measure.h new file mode 100644 index 000000000000..a91d0b3ff8f1 --- /dev/null +++ b/security/integrity/dim/core/dim_core_measure.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_MEASURE_H +#define __DIM_CORE_MEASURE_H + +#include "dim_measure.h" + +/* default configuration */ +#define DIM_CORE_HASH_DEFAULT "sha256" +#define DIM_CORE_LOG_CAP_DEFAULT 100000 + +/* max measure interval = 1 year */ +#define DIM_INTERVAL_MAX (365 * 24 * 60) +#define DIM_MINUTE_TO_SEC (60UL) +#define DIM_MINUTE_TO_NSEC (60UL * 1000 * 1000 * 1000) + +enum dim_measure_action { + DIM_MEASURE_ACTION_DISABLE, + DIM_MEASURE_ACTION_ENABLE, + DIM_MEASURE_ACTION_MAX, +}; + +extern struct dim_measure dim_core_handle; + +/* global init and destroy */ +int dim_core_measure_init(struct dim_measure_cfg *cfg, unsigned int interval); +void dim_core_measure_destroy(void); + +/* control function for measurement parameters */ +const char *dim_core_status_print(void); +long dim_core_measure_action_get(void); +int dim_core_measure_action_set(unsigned int act); +long dim_core_interval_get(void); +int dim_core_interval_set(unsigned int p); +long dim_core_tampered_action_get(void); +int dim_core_tampered_action_set(unsigned int p); + +/* measurement trigger functions */ +int dim_core_measure_blocking(void); +int dim_core_baseline_blocking(void); + +#endif diff --git a/security/integrity/dim/core/dim_core_mem_pool.c b/security/integrity/dim/core/dim_core_mem_pool.c new file mode 100644 index 000000000000..a9f017740a94 --- /dev/null +++ b/security/integrity/dim/core/dim_core_mem_pool.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_safe_func.h" +#include "dim_utils.h" + +#include "dim_core_mem_pool.h" + +static struct gen_pool *dim_pool = NULL; + +static int dim_mem_pool_expand(unsigned int order) +{ + int ret = -ENOMEM; + struct page *pages = NULL; + unsigned long pages_addr = 0; + size_t size = (1 << order) << PAGE_SHIFT; + + pages = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); + if (pages == NULL) { + dim_err("failed to allocate pages for memory pool\n"); + return -ENOMEM; + } + + pages_addr = (unsigned long)page_address(pages); + dim_devel("alloc_pages: addr = 0x%lx, order = %d\n", pages_addr, order); + + ret = gen_pool_add(dim_pool, pages_addr, size, -1); + if (ret < 0) { + dim_err("failed to add pages to memory pool: %d\n", ret); + return ret; + } + + dim_devel("dim_mem_pool_expand: %lu\n", size); + return 0; +} + +int dim_mem_pool_init(void) +{ + int ret = 0; + + dim_pool = gen_pool_create(DIM_MIN_ALLOC_ORDER, -1); + if (dim_pool == NULL) { + dim_err("failed to generate memory pool\n"); + return -ENOMEM; + } + + ret = dim_mem_pool_expand(DIM_EXPEND_ALLOC_PAGE_ORDER); + if (ret < 0) { + gen_pool_destroy(dim_pool); + dim_pool = NULL; + } + + return ret; +} + +static void free_chunk(struct gen_pool *pool, + struct gen_pool_chunk *chunk, + void *data __always_unused) +{ + if (chunk == NULL) + return; + + dim_devel("free_pages: addr = 0x%lx, order = %d\n", + chunk->start_addr, DIM_EXPEND_ALLOC_PAGE_ORDER); + free_pages(chunk->start_addr, DIM_EXPEND_ALLOC_PAGE_ORDER); +} + +void dim_mem_pool_destroy(void) +{ + if (dim_pool == NULL) + return; + + if (gen_pool_avail(dim_pool) != gen_pool_size(dim_pool)) { + dim_err("dim_mem_pool_destroy failed, memory leak detected\n"); + return; + } + + gen_pool_for_each_chunk(dim_pool, free_chunk, NULL); + gen_pool_destroy(dim_pool); + dim_pool = NULL; +} + +void *dim_mem_pool_alloc(size_t size) +{ + int ret = 0; + struct dim_pool_mem *data = NULL; + size_t mem_size = size + sizeof(struct dim_pool_mem); + + if (size > DIM_MAX_ALLOC_SIZE || mem_size > DIM_MAX_ALLOC_SIZE) { + dim_err("memory pool over allocate size: %lu", size); + return NULL; + } + + data = (struct dim_pool_mem *)gen_pool_alloc(dim_pool, mem_size); + if (data != NULL) + goto out; + + dim_devel("gen_pool_alloc failed, try dim_mem_pool_expand\n"); + ret = dim_mem_pool_expand(DIM_EXPEND_ALLOC_PAGE_ORDER); + if (ret < 0) { + dim_err("failed to expand memory pool: %d\n", ret); + return NULL; + } + + data = (struct dim_pool_mem *)gen_pool_alloc(dim_pool, mem_size); + if (data == NULL) + return NULL; +out: + #ifdef DIM_DEBUG_MEMORY_LEAK + dim_alloc_debug_inc(); + #endif + data->size = mem_size; + return data->data; +} + +void dim_mem_pool_free(const void *data) +{ + struct dim_pool_mem *mem = NULL; + + if (!gen_pool_has_addr(dim_pool, (uintptr_t)data, 1)) { + dim_err("addr 0x%lx is not in the memory pool\n", + (uintptr_t)data); + return; + } + + mem = container_of(data, struct dim_pool_mem, data); + if (!gen_pool_has_addr(dim_pool, (uintptr_t)mem, mem->size)) { + dim_err("addr 0x%lx (size %lu) is not in the memory pool\n", + (uintptr_t)mem, mem->size); + return; + } + + gen_pool_free(dim_pool, (uintptr_t)mem, mem->size); + + #ifdef DIM_DEBUG_MEMORY_LEAK + dim_alloc_debug_dec(); + #endif +} + +void dim_mem_pool_walk_chunk(pool_chunk_visitor f, void *data) +{ + gen_pool_for_each_chunk(dim_pool, f, data); +} +EXPORT_SYMBOL_GPL(dim_mem_pool_walk_chunk); diff --git a/security/integrity/dim/core/dim_core_mem_pool.h b/security/integrity/dim/core/dim_core_mem_pool.h new file mode 100644 index 000000000000..5c4cdea6ba28 --- /dev/null +++ b/security/integrity/dim/core/dim_core_mem_pool.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_MEM_POOL +#define __DIM_CORE_MEM_POOL + +#include + +/* Mininum allocation: 2 ^ 3 = 8 bytes */ +#define DIM_MIN_ALLOC_ORDER 3 +/* Expand allocation: 2 ^ 2 * 4k = 16k */ +#define DIM_EXPEND_ALLOC_PAGE_ORDER 2 +#define DIM_MAX_ALLOC_SIZE ((1 << DIM_EXPEND_ALLOC_PAGE_ORDER) << PAGE_SHIFT) + +struct dim_pool_mem { + size_t size; + char data[0]; +}; + +typedef void (*pool_chunk_visitor)(struct gen_pool *, + struct gen_pool_chunk *, + void *); + +int dim_mem_pool_init(void); +void dim_mem_pool_destroy(void); +void *dim_mem_pool_alloc(size_t size); +void dim_mem_pool_free(const void *data); +void dim_mem_pool_walk_chunk(pool_chunk_visitor f, void *data); + +#endif diff --git a/security/integrity/dim/core/dim_core_sig.c b/security/integrity/dim/core/dim_core_sig.c new file mode 100644 index 000000000000..07e11d8cc9c9 --- /dev/null +++ b/security/integrity/dim/core/dim_core_sig.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dim_hash.h" +#include "dim_utils.h" +#include "dim_safe_func.h" + +#include "dim_core_sig.h" + +static struct key *dim_core_keyring = NULL; +static struct key *dim_core_key = NULL; +static struct dim_hash dim_core_sig_hash = { 0 }; + +static char *add_suffix(const char *str, const char *suffix) +{ + int len = 0; + char *buf = NULL; + + len = strlen(str) + strlen(suffix) + 1; + buf = dim_kzalloc_gfp(len); + if (buf == NULL) + return NULL; + + sprintf(buf, "%s%s", str, suffix); + return buf; +} + +static int read_file_root(struct path *root, const char *name, void **buf) +{ + int ret = 0; + struct file *file = NULL; + + if (root == NULL) { + ret = kernel_read_file_from_path(name, 0, buf, + DIM_CORE_MAX_FILE_SIZE, + NULL, READING_UNKNOWN); +#ifdef DIM_DEBUG_MEMORY_LEAK + if (*buf != NULL) + dim_alloc_debug_inc(); +#endif + return ret; + } + + file = file_open_root(root, name, O_RDONLY, 0); + if (IS_ERR(file)) + return PTR_ERR(file); + + ret = kernel_read_file(file, 0, buf, DIM_CORE_MAX_FILE_SIZE, + NULL, READING_UNKNOWN); +#ifdef DIM_DEBUG_MEMORY_LEAK + if (*buf != NULL) + dim_alloc_debug_inc(); +#endif + (void)filp_close(file, NULL); + return ret; +} + +static int dim_core_sig_verify(const char *buf, loff_t buf_len, + const char *sbuf, loff_t sbuf_len) +{ + int ret = 0; + struct dim_digest digest = { 0 }; + /* Currently only support RSA-SHA256 */ + struct public_key_signature key_sig = { + .pkey_algo = "rsa", + .hash_algo = "sha256", + .encoding = "pkcs1" + }; + + if (buf == NULL || sbuf == NULL) + return -EINVAL; + + ret = dim_hash_calculate(buf, buf_len, &dim_core_sig_hash, &digest); + if (ret < 0) + return ret; + + key_sig.s = (char *)sbuf; + key_sig.s_size = sbuf_len; + key_sig.digest = digest.data; + key_sig.digest_size = dim_digest_size(digest.algo); + + return verify_signature(dim_core_key, &key_sig); +} + +int dim_read_verify_file(struct path *root, const char *name, void **buf) +{ + int ret = 0; + char *sig_name = NULL; + void *file_buf = NULL; + void *sig_buf = NULL; + size_t file_size = 0; + size_t sig_size = 0; + + if (name == NULL || buf == NULL) + return -EINVAL; + + sig_name = add_suffix(name, DIM_CORE_SIG_FILE_SUFFIX); + if (sig_name == NULL) + return -ENOMEM; + + ret = read_file_root(root, name, &file_buf); + if (ret < 0) + goto out; + + file_size = ret; + ret = 0; + + if (dim_core_key == NULL) + goto out; /* no need to verify signature */ + + ret = read_file_root(root, sig_name, &sig_buf); + if (ret < 0) + goto out; + + sig_size = ret; + ret = dim_core_sig_verify(file_buf, file_size, sig_buf, sig_size); +out: + dim_kfree(sig_name); + dim_vfree(sig_buf); + if (ret < 0) + dim_vfree(file_buf); + if (ret == 0) { + *buf = file_buf; + ret = file_size; + } + + return ret; +} + +int dim_core_sig_init(void) +{ + ssize_t ret = 0; + void *data = NULL; + key_ref_t key; + + dim_core_keyring = keyring_alloc(DIM_CORE_KEYRING_NAME, KUIDT_INIT(0), + KGIDT_INIT(0), current_cred(), + DIM_CORE_KEYRING_PERM, + KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); + if (IS_ERR(dim_core_keyring)) { + ret = PTR_ERR(dim_core_keyring); + dim_err("failed to allocate DIM keyring: %ld\n", ret); + return ret; + } + + ret = kernel_read_file_from_path(DIM_CORE_CERT_PATH, 0, &data, + DIM_CORE_MAX_FILE_SIZE, NULL, + READING_X509_CERTIFICATE); + if (ret < 0) { + dim_err("failed to read DIM cert file: %ld\n", ret); + goto err; + } + + key = key_create_or_update(make_key_ref(dim_core_keyring, 1), + "asymmetric", NULL, data, ret, + DIM_CORE_KEY_PERM, KEY_ALLOC_NOT_IN_QUOTA); + if (IS_ERR(key)) { + ret = PTR_ERR(key); + dim_err("failed to load DIM cert: %ld\n", ret); + goto err; + } + + ret = dim_hash_init("sha256", &dim_core_sig_hash); + if (ret < 0) { + dim_err("failed to init dim signature hash: %ld\n", ret); + goto err; + } + + key_ref_put(key); + dim_core_key = key_ref_to_ptr(key); + dim_info("load DIM cert: %s\n", dim_core_key->description); + ret = 0; +err: + dim_vfree(data); + if (ret < 0) { + key_put(dim_core_keyring); + dim_core_keyring = NULL; + } + + return ret; +} + +void dim_core_sig_destroy(void) +{ + if (dim_core_keyring != NULL) + key_put(dim_core_keyring); + + dim_hash_destroy(&dim_core_sig_hash); +} diff --git a/security/integrity/dim/core/dim_core_sig.h b/security/integrity/dim/core/dim_core_sig.h new file mode 100644 index 000000000000..45dd0bb2912f --- /dev/null +++ b/security/integrity/dim/core/dim_core_sig.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_SIG_H +#define __DIM_CORE_SIG_H + +#include + +#define DIM_CORE_MAX_FILE_SIZE (10 * 1024 * 1024) +#define DIM_CORE_KEYRING_NAME "_dim" +#define DIM_CORE_CERT_PATH "/etc/keys/x509_dim.der" +#define DIM_CORE_SIG_FILE_SUFFIX ".sig" +#define DIM_CORE_KEYRING_PERM ((KEY_POS_ALL & ~KEY_POS_SETATTR) \ + | KEY_USR_VIEW | KEY_USR_READ \ + | KEY_USR_WRITE | KEY_USR_SEARCH) +#define DIM_CORE_KEY_PERM ((KEY_POS_ALL & ~KEY_POS_SETATTR) \ + | KEY_USR_VIEW | KEY_USR_READ) + +int dim_core_sig_init(void); +void dim_core_sig_destroy(void); +int dim_read_verify_file(struct path *root, const char *name, void **buf); + +#endif diff --git a/security/integrity/dim/core/dim_core_symbol.c b/security/integrity/dim/core/dim_core_symbol.c new file mode 100644 index 000000000000..38c9f021966c --- /dev/null +++ b/security/integrity/dim/core/dim_core_symbol.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include + +#include "dim_symbol.h" +#include "dim_utils.h" + +#include "dim_core_symbol.h" + +struct dim_core_kallsyms dim_core_kernel_symbol; + +int dim_core_kallsyms_init(void) +{ + struct dim_core_kallsyms *k = &dim_core_kernel_symbol; + DIM_SYMBOL_LOOKUP_FUNC dim_kallsyms_lookup_name = NULL; + + if (memset(k, 0, + sizeof(struct dim_core_kallsyms)) != k) + return -EINVAL; + + dim_kallsyms_lookup_name = dim_get_symbol_lookup_func(); + if (dim_kallsyms_lookup_name == NULL) { + dim_err("failed to get symbol_lookup_func\n"); + return -EINVAL; + } + k->stext = (char *)dim_kallsyms_lookup_name("_stext"); + k->etext = (char *)dim_kallsyms_lookup_name("_etext"); + + k->start_jump_table = (struct jump_entry *) + dim_kallsyms_lookup_name("__start___jump_table"); + k->stop_jump_table = (struct jump_entry *) + dim_kallsyms_lookup_name("__stop___jump_table"); + k->jump_label_lock = (DIM_JUMP_LABEL_LOCK) + dim_kallsyms_lookup_name("jump_label_lock"); + k->jump_label_unlock = (DIM_JUMP_LABEL_UNLOCK) + dim_kallsyms_lookup_name("jump_label_unlock"); + k->walk_process_tree = (DIM_WALK_PROCESS_TREE) + dim_kallsyms_lookup_name("walk_process_tree"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + k->find_module = (DIM_FIND_MODULE) + dim_kallsyms_lookup_name("find_module"); + k->find_get_task_by_vpid = (DIM_FIND_GET_TASK_BY_VPID) + dim_kallsyms_lookup_name("find_get_task_by_vpid"); +#endif + + return (k->stext == NULL || k->etext == NULL || +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + k->find_module == NULL || k->find_get_task_by_vpid == NULL || +#endif + k->start_jump_table == NULL || k->stop_jump_table == NULL || + k->jump_label_lock == NULL || k->jump_label_lock == NULL || + k->walk_process_tree == NULL) ? -ENOENT : 0; +} diff --git a/security/integrity/dim/core/dim_core_symbol.h b/security/integrity/dim/core/dim_core_symbol.h new file mode 100644 index 000000000000..5f3ee4b391a5 --- /dev/null +++ b/security/integrity/dim/core/dim_core_symbol.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_SYMBOL_H +#define __DIM_CORE_SYMBOL_H + +#include +#include +#include + +typedef void (*DIM_JUMP_LABEL_LOCK)(void); +typedef void (*DIM_JUMP_LABEL_UNLOCK)(void); +typedef void (*DIM_WALK_PROCESS_TREE)(struct task_struct *, + proc_visitor, void *); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) +typedef struct module *(*DIM_FIND_MODULE)(const char *); +typedef struct task_struct *(*DIM_FIND_GET_TASK_BY_VPID)(pid_t); +#endif +#ifndef JUMP_LABEL_NOP_SIZE +typedef int (*DIM_ARCH_JUMP_ENTRY_SIZE)(struct jump_entry *); +#endif + + +struct dim_core_kallsyms { + char *stext; + char *etext; + struct jump_entry *start_jump_table; + struct jump_entry *stop_jump_table; + DIM_JUMP_LABEL_LOCK jump_label_lock; + DIM_JUMP_LABEL_LOCK jump_label_unlock; + DIM_WALK_PROCESS_TREE walk_process_tree; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + DIM_FIND_MODULE find_module; + DIM_FIND_GET_TASK_BY_VPID find_get_task_by_vpid; +#endif +#ifndef JUMP_LABEL_NOP_SIZE + DIM_ARCH_JUMP_ENTRY_SIZE arch_jump_entry_size; +#endif +}; + +extern struct dim_core_kallsyms dim_core_kernel_symbol; + +int dim_core_kallsyms_init(void); + +#endif diff --git a/security/integrity/dim/core/policy/dim_core_policy.c b/security/integrity/dim/core/policy/dim_core_policy.c new file mode 100644 index 000000000000..adcdabe0f776 --- /dev/null +++ b/security/integrity/dim/core/policy/dim_core_policy.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dim_rb.h" +#include "dim_utils.h" +#include "dim_safe_func.h" + +#include "dim_core_sig.h" +#include "dim_core_policy.h" + +static struct rb_root policy_root = RB_ROOT; + +static int dim_policy_compare(struct dim_policy *x, struct dim_policy *y) +{ + if (x->obj != y->obj) + return x->obj - y->obj; + + switch (x->obj) { + case DIM_POLICY_OBJ_BPRM_TEXT: + return dim_strcmp(x->path, y->path); + case DIM_POLICY_OBJ_MODULE_TEXT: + return dim_strcmp(x->name, y->name); + case DIM_POLICY_OBJ_KERNEL_TEXT: + return 0; + default: + break; + } + + return -1; +} + +/* +static int dim_policy_rb_find(struct rb_root *root, + struct dim_policy *data, + struct dim_policy **find_data) +*/ +dim_rb_find(dim_policy); +/* +static int dim_policy_rb_add(struct rb_root *root, + struct dim_policy *data, + struct dim_policy **find_data) +*/ +dim_rb_add(dim_policy); + +void policy_destroy(struct dim_policy *policy) +{ + if (policy == NULL) + return; + + dim_kfree(policy->name); + dim_kfree(policy->path); + dim_kfree(policy); +} + +static int policy_check_add_bprm_text(struct dim_policy *policy) +{ + int ret = 0; + const char *apath = NULL; + struct dim_policy *p = NULL; + + /* check the policy is valid */ + if (policy->path == NULL) { + dim_err("path must be set for BPRM_TEXT policy\n"); + return -EINVAL; + } + + if (strlen(policy->path) + 1 > PATH_MAX) { + dim_err("path must be shorter than %d\n", PATH_MAX); + return -ENAMETOOLONG; + } + + if (policy->name != NULL) + dim_warn("name is ignored for BPRM_TEXT policy\n"); + + /* firstly, add the current node */ + ret = dim_policy_rb_add(&policy_root, policy, NULL); + if (ret < 0) + return ret; + + /* secondly, try to add another node with absolute path. beacuse + sometimes users may not sure whether to write /usr/bin/bash + or /bin/bash in policy */ + ret = dim_get_absolute_path(policy->path, &apath); + if (ret < 0) { + dim_warn("failed to get absolute path of %s in policy: %d\n", + policy->path, ret); + return 0; + } + + if (dim_strcmp(apath, policy->path) == 0) { + /* the two paths are same, no need to add another policy */ + dim_kfree(apath); + return 0; + } + + p = dim_kmemdup_gfp(policy, sizeof(struct dim_policy)); + if (p == NULL) { + dim_kfree(apath); + return -ENOMEM; + } + + /* set the absolute path and add the policy node */ + p->path = apath; + ret = dim_policy_rb_add(&policy_root, p, NULL); + if (ret < 0) + policy_destroy(p); + + /* the EEXIST error must be processed here */ + return ret == -EEXIST ? 0 : ret; +} + +static int policy_check_add_module_text(struct dim_policy *policy) +{ + if (policy->name == NULL) { + dim_err("name must be set for MODULE_TEXT policy\n"); + return -EINVAL; + } + + if (strlen(policy->name) + 1 > NAME_MAX) { + dim_err("name must be shorter than %d\n", NAME_MAX); + return -ENAMETOOLONG; + } + + if (policy->path != NULL) + dim_warn("path is ignored for MODULE_TEXT policy\n"); + + if (policy->action != DIM_POLICY_ACTION_LOG) + dim_warn("action is ignored for MODULE_TEXT policy\n"); + + return dim_policy_rb_add(&policy_root, policy, NULL); +} + +static int policy_check_add_kernel_text(struct dim_policy *policy) +{ + if (policy->name != NULL || policy->path != NULL || + policy->action != DIM_POLICY_ACTION_LOG) + dim_warn("all parameters are ignored for KERNEL_TEXT policy\n"); + + return dim_policy_rb_add(&policy_root, policy, NULL); +} + +static int policy_check_add(struct dim_policy *policy) +{ + switch (policy->obj) + { + case DIM_POLICY_OBJ_BPRM_TEXT: + return policy_check_add_bprm_text(policy); + case DIM_POLICY_OBJ_MODULE_TEXT: + return policy_check_add_module_text(policy); + case DIM_POLICY_OBJ_KERNEL_TEXT: + return policy_check_add_kernel_text(policy); + default: + break; + } + + return -EINVAL; +} + +int dim_core_policy_load(void) +{ + int ret = 0; + void *buf = NULL; + loff_t buf_len = 0; + + if (!RB_EMPTY_ROOT(&policy_root)) + dim_core_policy_destroy(); + + ret = dim_read_verify_file(NULL, DIM_POLICY_PATH, &buf); + if (ret < 0 || buf == NULL) { + dim_err("failed to read policy file: %d\n", ret); + return ret; + } + + buf_len = ret; + ret = dim_policy_parse(buf, buf_len, policy_check_add); + if (ret < 0) { + dim_err("failed to parse policy: %d\n", ret); + dim_core_policy_destroy(); + } + + dim_vfree(buf); + return ret; +} + +void dim_core_policy_destroy(void) +{ + struct dim_policy *pos = NULL; + struct dim_policy *n = NULL; + + rbtree_postorder_for_each_entry_safe(pos, n, &policy_root, rb_node) + policy_destroy(pos); + + policy_root = RB_ROOT; +} + +static int policy_find(int obj, int key __always_unused, const char *val, + struct dim_policy **find) +{ + struct dim_policy policy = { 0 }; + + /* now the key parameter is unused */ + switch (obj) { + case DIM_POLICY_OBJ_BPRM_TEXT: + policy.path = val; + break; + case DIM_POLICY_OBJ_MODULE_TEXT: + policy.name = val; + break; + case DIM_POLICY_OBJ_KERNEL_TEXT: + break; + default: + return -EINVAL; + } + + policy.obj = obj; + return dim_policy_rb_find(&policy_root, &policy, find); +} + +bool dim_core_policy_match(int obj, int key, const char *val) +{ + if (val == NULL) + return false; + + return policy_find(obj, key, val, NULL) == 0; +} + +int dim_core_policy_get_action(int obj, int key, const char *val) +{ + int ret = 0; + struct dim_policy *find = NULL; + + if (val == NULL) + return DIM_POLICY_ACTION_LAST; + + ret = policy_find(obj, key, val, &find); + if (ret < 0) + return DIM_POLICY_ACTION_LAST; + + return find->action; +} + +int dim_core_policy_walk(int (*f)(struct dim_policy *, void *), void *data) +{ + int ret = 0; + struct dim_policy *pos = NULL; + struct dim_policy *n = NULL; + + rbtree_postorder_for_each_entry_safe(pos, n, &policy_root, rb_node) { + ret = f(pos, data); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/security/integrity/dim/core/policy/dim_core_policy.h b/security/integrity/dim/core/policy/dim_core_policy.h new file mode 100644 index 000000000000..3b38d138895f --- /dev/null +++ b/security/integrity/dim/core/policy/dim_core_policy.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_POLICY_H +#define __DIM_CORE_POLICY_H + +#include + +/* the policy filepath */ +#define DIM_POLICY_PATH "/etc/dim/policy" +/* max number of lines for parsing */ +#define DIM_POLICY_LINE_MAX 100000 + +/* measurement object of policy */ +enum dim_policy_obj { + DIM_POLICY_OBJ_BPRM_TEXT, + DIM_POLICY_OBJ_MODULE_TEXT, + DIM_POLICY_OBJ_KERNEL_TEXT, + DIM_POLICY_OBJ_LAST, +}; + +/* key of measurement condition */ +enum dim_policy_key { + DIM_POLICY_KEY_OBJ, + DIM_POLICY_KEY_NAME, + DIM_POLICY_KEY_PATH, + DIM_POLICY_KEY_ACTION, + DIM_POLICY_KEY_LAST, +}; + +/* action to perform when dim detected a tampering */ +enum dim_policy_action { + /* add to measure log (default) */ + DIM_POLICY_ACTION_LOG, + /* kill the tampered user process */ + DIM_POLICY_ACTION_KILL, + DIM_POLICY_ACTION_LAST, +}; + +struct dim_policy { + struct rb_node rb_node; + int obj; /* enum dim_policy_obj */ + const char *path; /* user process path */ + const char *name; /* module name */ + int action; /* enum dim_policy_action */ +}; + +/* callback funtion to walk dim policy nodes */ +typedef int (*dim_policy_visitor)(struct dim_policy *, void *); + +/* callback funtion to add a policy item when parsing policy file */ +typedef int (*policy_add_func)(struct dim_policy *); + +/* parse dim policy in complex format */ +int policy_parse_complex_format(char *buf, size_t buf_len, + policy_add_func policy_add); +#define dim_policy_parse policy_parse_complex_format + +/* export for implementing the policy parser */ +void policy_destroy(struct dim_policy *policy); + +int dim_core_policy_load(void); +void dim_core_policy_destroy(void); +bool dim_core_policy_match(int obj, int key, const char *val); +int dim_core_policy_walk(dim_policy_visitor f, void *data); +int dim_core_policy_get_action(int obj, int key, const char *val); + +#endif diff --git a/security/integrity/dim/core/policy/dim_core_policy_complex.c b/security/integrity/dim/core/policy/dim_core_policy_complex.c new file mode 100644 index 000000000000..cba2dd7ca5ab --- /dev/null +++ b/security/integrity/dim/core/policy/dim_core_policy_complex.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_rb.h" +#include "dim_utils.h" +#include "dim_safe_func.h" + +#include "dim_core_policy.h" + +/* policy key */ +#define DIM_POLICY_MEASURE "measure" + +/* measure obj=MODULE_TEXT path={PATH_MAX} action=kill\n */ +#define DIM_POLICY_MAX_KEY_FIELDS 3 +#define DIM_POLICY_OBJ_MAX_LEN 15 +#define DIM_POLICY_KEY_MAX_LEN 5 +#define DIM_POLICY_ACTION_MAX_LEN 11 +#define DIM_POLICY_MAX_LEN (strlen(DIM_POLICY_MEASURE) + 1 + \ + DIM_POLICY_OBJ_MAX_LEN + 1 + \ + DIM_POLICY_KEY_MAX_LEN + 1 + PATH_MAX + 1 + \ + DIM_POLICY_ACTION_MAX_LEN + 1) + +static const char *dim_policy_key_str[DIM_POLICY_KEY_LAST] = { + [DIM_POLICY_KEY_OBJ] = "obj=", + [DIM_POLICY_KEY_NAME] = "name=", + [DIM_POLICY_KEY_PATH] = "path=", + [DIM_POLICY_KEY_ACTION] = "action=", +}; + +static const char *dim_policy_obj_str[DIM_POLICY_OBJ_LAST] = { + [DIM_POLICY_OBJ_BPRM_TEXT] = "BPRM_TEXT", + [DIM_POLICY_OBJ_MODULE_TEXT] = "MODULE_TEXT", + [DIM_POLICY_OBJ_KERNEL_TEXT] = "KERNEL_TEXT", +}; + +static const char *dim_policy_action_str[DIM_POLICY_KEY_LAST] = { + [DIM_POLICY_ACTION_LOG] = "log", + [DIM_POLICY_ACTION_KILL] = "kill", +}; + +static const char *policy_get_string_value(const char *s) +{ + return dim_kstrdup_gfp(s); +} + +static int policy_get_action(const char *s) +{ + return match_string(dim_policy_action_str, DIM_POLICY_ACTION_LAST, s); +} + +static int policy_get_obj(const char *s) +{ + return match_string(dim_policy_obj_str, DIM_POLICY_OBJ_LAST, s); +} + +static int policy_get_key(const char *s, const char **val) +{ + unsigned int i = 0; + unsigned int len = 0; + + for (; i < DIM_POLICY_KEY_LAST; i++) { + len = strlen(dim_policy_key_str[i]); + if (dim_strncmp(s, dim_policy_key_str[i], len) == 0) { + *val = s + len; + return i; + } + } + + return -EINVAL; +} + +static int policy_parse_key_value(char *s, struct dim_policy *policy) +{ + char *p = NULL; + int key = 0; + int filed_num = 0; + const char *val = NULL; + + while ((p = strsep(&s, " ")) != NULL) { + key = policy_get_key(p, &val); + if (key < 0 || val == NULL) + return -EINVAL; + + if (++filed_num > DIM_POLICY_MAX_KEY_FIELDS) + return -EINVAL; + + switch (key) + { + case DIM_POLICY_KEY_OBJ: + policy->obj = policy_get_obj(val); + if (policy->obj < 0) + return -EINVAL; + break; + case DIM_POLICY_KEY_NAME: + policy->name = policy_get_string_value(val); + if (policy->name == NULL) + return -ENOMEM; + break; + case DIM_POLICY_KEY_PATH: + policy->path = policy_get_string_value(val); + if (policy->path == NULL) + return -ENOMEM; + break; + case DIM_POLICY_KEY_ACTION: + policy->action = policy_get_action(val); + if (policy->action < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int parse_line(char *line_str, struct dim_policy *policy) +{ + int ret = 0; + char *p = NULL; + + if ((p = strsep(&line_str, " ")) == NULL || + dim_strcmp(p, DIM_POLICY_MEASURE) != 0) { + dim_err("invalid policy prefix, must start with %s\n", + DIM_POLICY_MEASURE); + return -EINVAL; + } + + ret = policy_parse_key_value(line_str, policy); + if (ret < 0) { + dim_err("fail to parse policy key and value: %d\n", ret); + return ret; + } + + return 0; +} + +static int policy_parse_line(char* line, int line_no, void *data) +{ + int ret = 0; + struct dim_policy *policy = NULL; + policy_add_func policy_add = data; + + if (line_no > DIM_POLICY_LINE_MAX) { + dim_warn("more than %d policy items will be ignored\n", + DIM_POLICY_LINE_MAX); + return -E2BIG; + } + + if (strlen(line) == 0 || line[0] == '#') + return 0; /* ignore blank line and comment */ + + if (strlen(line) > DIM_POLICY_MAX_LEN) { + dim_err("overlength line %d\n", line_no); + return -EINVAL; + } + + policy = dim_kzalloc_gfp(sizeof(struct dim_policy)); + if (policy == NULL) + return -ENOMEM; + + ret = parse_line(line, policy); + if (ret < 0) { + policy_destroy(policy); + dim_err("failed to parse policy at line %d: %d\n", line_no, ret); + return ret; + } + + ret = policy_add(policy); + if (ret < 0) { + policy_destroy(policy); + /* ignore the repeat add */ + if (ret != -EEXIST) + dim_err("failed to add policy at line %d: %d\n", line_no, ret); + return ret == -EEXIST ? 0 : ret; + } + + return 0; +} + +int policy_parse_complex_format(char *buf, size_t buf_len, + policy_add_func policy_add) +{ + if (buf == NULL || buf_len == 0 || policy_add == NULL) + return -EINVAL; + + return dim_parse_line_buf(buf, buf_len, policy_parse_line, policy_add); +} diff --git a/security/integrity/dim/core/static_baseline/dim_core_static_baseline.c b/security/integrity/dim/core/static_baseline/dim_core_static_baseline.c new file mode 100644 index 000000000000..7ae5171620e6 --- /dev/null +++ b/security/integrity/dim/core/static_baseline/dim_core_static_baseline.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include "dim_utils.h" +#include "dim_hash.h" + +#include "dim_core_sig.h" +#include "dim_core_policy.h" +#include "dim_core_measure.h" +#include "dim_core_static_baseline.h" + +#define BASELINE_FILE_SUFFIX ".hash" +#define BASELINE_FILE_SUFFIX_LEN 5 + +static bool baseline_match_policy(const char *name, int type) +{ + const char *kr = init_uts_ns.name.release; + unsigned int kr_len = strlen(kr); + unsigned int name_len = strlen(name); + const char *mod_name = NULL; + + if (type != DIM_BASELINE_KERNEL) + return dim_core_policy_match(DIM_POLICY_OBJ_BPRM_TEXT, + DIM_POLICY_KEY_PATH, name); + + if (dim_strcmp(name, kr) == 0) + return dim_core_policy_match(DIM_POLICY_OBJ_KERNEL_TEXT, + DIM_POLICY_KEY_NAME, kr); + + if (name_len <= kr_len + 2 || /* / */ + dim_strncmp(kr, name, kr_len) != 0 || + *(name + kr_len) != '/') + return false; + + mod_name = name + kr_len + 1; + return dim_core_policy_match(DIM_POLICY_OBJ_MODULE_TEXT, + DIM_POLICY_KEY_NAME, mod_name); +} + +static int baseline_check_add(const char *name, int type, + struct dim_digest *digest, + struct dim_measure *m) +{ + int ret = 0; + const char *real_path = NULL; + + if (type == DIM_BASELINE_KERNEL) + return dim_measure_static_baseline_add(m, name, type, digest); + + /* for process, try to add the absolute path */ + ret = dim_get_absolute_path(name, &real_path); + if (ret < 0) { + dim_warn("failed to get absolute path of %s in static baeline: %d\n", + name, ret); + return dim_measure_static_baseline_add(m, name, type, digest); + } + + ret = dim_measure_static_baseline_add(m, real_path, type, digest); + dim_kfree(real_path); + return ret; +} + +struct name_entry { + char name[NAME_MAX]; + struct list_head list; +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) +static int +#else +static bool +#endif +baseline_fill_dir(struct dir_context *__ctx, + const char *name, + int name_len, + loff_t offset, + unsigned long long ino, + unsigned d_type) +{ + struct baseline_parse_ctx *ctx = container_of(__ctx, typeof(*ctx), ctx); + struct name_entry *entry = NULL; + + /* baseline file must end with '.hash' */ + if (d_type != DT_REG || name_len >= NAME_MAX || + name_len <= BASELINE_FILE_SUFFIX_LEN || + strncmp(name + name_len - BASELINE_FILE_SUFFIX_LEN, + BASELINE_FILE_SUFFIX, BASELINE_FILE_SUFFIX_LEN)) + goto out; /* ignore invalid files */ + + entry = dim_kzalloc_gfp(sizeof(struct name_entry)); + if (entry == NULL) + goto out; + + strncpy(entry->name, name, name_len); + list_add( &entry->list, &ctx->name_list); +out: +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + return 0; /* ignore fail */ +#else + return true; +#endif +} + +int dim_core_static_baseline_load(struct dim_measure *m) +{ + int ret = 0; + struct path kpath; + struct file *file = NULL; + struct name_entry *entry = NULL; + struct name_entry *tmp = NULL; + void *buf = NULL; + unsigned long buf_len = 0; + struct baseline_parse_ctx ctx = { + .m = m, + .ctx.actor = baseline_fill_dir, + .add = baseline_check_add, + .match = baseline_match_policy, + .name_list = LIST_HEAD_INIT(ctx.name_list) + }; + + if (m == NULL) + return -EINVAL; + + ret = kern_path(DIM_STATIC_BASELINE_ROOT, LOOKUP_DIRECTORY, &kpath); + if (ret < 0) { + dim_err("failed to get dim baseline root path: %d", ret); + return ret; + } + + file = filp_open(DIM_STATIC_BASELINE_ROOT, O_RDONLY | O_DIRECTORY, 0); + if (IS_ERR(file)) { + ret = PTR_ERR(file); + dim_err("failed to open %s: %d\n", DIM_STATIC_BASELINE_ROOT, ret); + path_put(&kpath); + return ret; + } + + (void)iterate_dir(file, &ctx.ctx); + filp_close(file, NULL); + + list_for_each_entry_safe(entry, tmp, &ctx.name_list, list) { + ret = dim_read_verify_file(&kpath, entry->name, &buf); + if (ret < 0 || buf == NULL) { + dim_err("failed to read and verify %s: %d\n", entry->name, ret); + dim_kfree(entry); + continue; + } + + buf_len = ret; + ret = dim_baseline_parse(buf, buf_len, &ctx); + if (ret < 0) + dim_err("failed to parse baseline file %s: %d\n", entry->name, ret); + + dim_vfree(buf); + dim_kfree(entry); + } + + path_put(&kpath); + return 0; +} diff --git a/security/integrity/dim/core/static_baseline/dim_core_static_baseline.h b/security/integrity/dim/core/static_baseline/dim_core_static_baseline.h new file mode 100644 index 000000000000..e0d1df982494 --- /dev/null +++ b/security/integrity/dim/core/static_baseline/dim_core_static_baseline.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_STATIC_BASELINE_H +#define __DIM_CORE_STATIC_BASELINE_H + +#include + +#include "dim_measure.h" + +/* directory to store the static baseline files */ +#define DIM_STATIC_BASELINE_ROOT "/etc/dim/digest_list" + +/* callback function to check if a baseline is matched the policy */ +typedef bool (*baseline_match_func)(const char *name, int type); + +/* callback function to add baseline to measurement handle */ +typedef int (*baseline_add_func)(const char *name, int type, + struct dim_digest *digest, + struct dim_measure *m); + +/* the context used in directory walking and file parsing */ +struct baseline_parse_ctx { + /* context for directory walking */ + struct dir_context ctx; + /* entry to store the filenames in directory */ + struct list_head name_list; + struct dim_measure *m; + baseline_match_func match; + baseline_add_func add; +}; + +/* function implemented to parse the static baseline file in complex format */ +int baseline_parse_complex_format(char *buf, size_t buf_len, + struct baseline_parse_ctx *ctx); +#define dim_baseline_parse baseline_parse_complex_format + +/* build or rebuild the static baseline into the measurement handle */ +int dim_core_static_baseline_load(struct dim_measure *m); + +#endif diff --git a/security/integrity/dim/core/static_baseline/dim_core_static_baseline_complex.c b/security/integrity/dim/core/static_baseline/dim_core_static_baseline_complex.c new file mode 100644 index 000000000000..8ff7c8616257 --- /dev/null +++ b/security/integrity/dim/core/static_baseline/dim_core_static_baseline_complex.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_utils.h" +#include "dim_core_static_baseline.h" + +#define DIM_STATIC_BASELINE_LINE_MAX 10000 + +#define DIM_STATIC_BASELINE_PREFIX "dim" +/* dim KERNEL sha256:{digest} {PATH_MAX}\n*/ +#define DIM_STATIC_BASELINE_LEN_MAX (strlen(DIM_STATIC_BASELINE_PREFIX) + 1 + \ + NAME_MAX + 1 + NAME_MAX + 1 + \ + PATH_MAX + 1 + 1) + +static int parse_line(char* line, int line_no, void *data) +{ + int type = 0; + size_t len = 0; + char *p = NULL; + char *line_str = line; + struct dim_digest digest = { 0 }; + struct baseline_parse_ctx *ctx = data; + + if (line_no > DIM_STATIC_BASELINE_LINE_MAX) { + dim_warn("more than %d baseline items will be ignored\n", + DIM_STATIC_BASELINE_LINE_MAX); + return -E2BIG; + } + + if (strlen(line) == 0 || line[0] == '#') + return 0; /* ignore blank line and comment */ + + if (strlen(line) > DIM_STATIC_BASELINE_LEN_MAX) { + dim_err("overlength item at line %d\n", line_no); + return 0; /* ignore baseline parsing failed */ + } + + if ((p = strsep(&line_str, " ")) == NULL || + dim_strcmp(p, DIM_STATIC_BASELINE_PREFIX) != 0) { + dim_warn("invalid baseline prefix at line %d\n", line_no); + return 0; + } + + if ((p = strsep(&line_str, " ")) == NULL || + (type = dim_baseline_get_type(p)) == DIM_BASELINE_LAST) { + dim_warn("invalid baseline type at line %d\n", line_no); + return 0; + } + + if ((p = strsep(&line_str, ":")) == NULL || + (digest.algo = dim_hash_algo(p)) == HASH_ALGO__LAST) { + dim_warn("invalid baseline algo at line %d\n", line_no); + return 0; + } + + if ((p = strsep(&line_str, " ")) == NULL || + strlen(p) != (dim_digest_size(digest.algo) << 1) || + hex2bin(digest.data, p, dim_digest_size(digest.algo)) < 0) { + dim_warn("invalid baseline digest at line %d\n", line_no); + return 0; + } + + if (line_str == NULL) { + dim_warn("no baseline name at line %d\n", line_no); + return 0; + } + + len = strlen(line_str); + if (len == 0 || len > PATH_MAX) { + dim_warn("invalid baseline name at line %d\n", line_no); + return 0; + } + + if (!ctx->match(line_str, type)) + return 0; + + return ctx->add(line_str, type, &digest, ctx->m); +} + +int baseline_parse_complex_format(char *buf, size_t buf_len, + struct baseline_parse_ctx *ctx) +{ + if (buf == NULL || buf_len == 0 || ctx == NULL || ctx->m == NULL || + ctx->match == NULL || ctx->add == NULL) + return -EINVAL; + + return dim_parse_line_buf(buf, buf_len, parse_line, ctx); +} diff --git a/security/integrity/dim/core/tasks/dim_core_measure_kernel.c b/security/integrity/dim/core/tasks/dim_core_measure_kernel.c new file mode 100644 index 000000000000..bb7fd74c8608 --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_kernel.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#include "dim_measure.h" + +#include "dim_core_symbol.h" +#include "dim_core_measure.h" +#include "dim_core_policy.h" + +#include "dim_core_measure_task.h" + +/* max size of x86 */ +#define DIM_JUMP_LABEL_NOP_SIZE_MAX 5 + +static int code_cmp(const void *a, const void *b) +{ + return *(unsigned long *)a > *(unsigned long *)b ? 1 : 0; +} + +static int sort_jump_table(struct jump_entry *sjump, + unsigned int jump_counts, + unsigned long **code) +{ + unsigned int i; + unsigned long *buf = NULL; + + buf = dim_vzalloc(sizeof(unsigned long) * jump_counts); + if (buf == NULL) + return -ENOMEM; + + dim_core_kernel_symbol.jump_label_lock(); + for (i = 0; i < jump_counts; i++) + buf[i] = jump_entry_code(&sjump[i]); + dim_core_kernel_symbol.jump_label_unlock(); + + sort(buf, jump_counts, sizeof(unsigned long), code_cmp, NULL); + *code = buf; + return 0; +} + +static int do_calc_kernel_digest(uintptr_t saddr, + uintptr_t eaddr, + uintptr_t *jcode_sort, + unsigned int jcode_cnt, + struct dim_hash *hash, + struct dim_digest *digest) +{ + int ret = 0; + unsigned int i; + uintptr_t jump_code; + uintptr_t cur_addr = saddr; + SHASH_DESC_ON_STACK(shash, hash->tfm); + + shash->tfm = hash->tfm; + if (shash->tfm == NULL) + return -EINVAL; + + ret = crypto_shash_init(shash); + if (ret < 0) + return ret; + + /* if jump label is not enabled, jcode_cnt is 0 */ + for (i = 0; i < jcode_cnt && cur_addr < eaddr; i++) { + jump_code = jcode_sort[i]; + if (jump_code < cur_addr) /* jump_code can be 0 */ + continue; + + if (jump_code >= eaddr) /* no more valid jump code */ + break; + + /* skip addresses that may be changed */ + if (jump_code > cur_addr) { + ret = crypto_shash_update(shash, (char *)cur_addr, + jump_code - cur_addr); + if (ret < 0) + return ret; + } +#ifdef JUMP_LABEL_NOP_SIZE + cur_addr = jump_code + JUMP_LABEL_NOP_SIZE; +#else + cur_addr = jump_code + DIM_JUMP_LABEL_NOP_SIZE_MAX; +#endif + } + + if (cur_addr < eaddr) { + ret = crypto_shash_update(shash, (char *)cur_addr, + eaddr - cur_addr); + if (ret < 0) + return ret; + } + + return crypto_shash_final(shash, digest->data); +} + +static int calc_kernel_digest(struct dim_hash *hash, struct dim_digest *digest) +{ + int ret = 0; + uintptr_t stext = 0; + uintptr_t etext = 0; + struct jump_entry *sjump = NULL; + struct jump_entry *ejump = NULL; + uintptr_t *jcode_sort = NULL; + unsigned int jcode_cnt = 0; + + stext = (uintptr_t)dim_core_kernel_symbol.stext; + etext = (uintptr_t)dim_core_kernel_symbol.etext; + sjump = dim_core_kernel_symbol.start_jump_table; + ejump = dim_core_kernel_symbol.stop_jump_table; + if (sjump != NULL && ejump != NULL && sjump < ejump) { + jcode_cnt = ((uintptr_t)ejump - (uintptr_t)sjump) / + sizeof(struct jump_entry); + ret = sort_jump_table(sjump, jcode_cnt, &jcode_sort); + if (ret < 0) { + dim_err("failed to sort kernel jump table: %d\n", ret); + return ret; + } + } else { + jcode_sort = NULL; + jcode_cnt = 0; + } + + ret = do_calc_kernel_digest(stext, etext, jcode_sort, + jcode_cnt, hash, digest); + if (ret < 0) + dim_err("failed to calculate kernel digest: %d\n", ret); + + dim_vfree(jcode_sort); + return ret; +} + +static int kernel_text_measure(int mode, struct dim_measure *m) +{ + int ret = 0; + const char *kr = init_uts_ns.name.release; + struct dim_digest digest = {0}; + + if (m == NULL) + return -EINVAL; + + digest.algo = m->hash.algo; + + if (!dim_core_policy_match(DIM_POLICY_OBJ_KERNEL_TEXT, + DIM_POLICY_KEY_NAME, kr)) + return 0; + + ret = calc_kernel_digest(&m->hash, &digest); + if (ret < 0) { + dim_err("failed to calculate kernel digest: %d\n", ret); + return ret; + } + + ret = dim_measure_process_dynamic_result(m, mode, kr, &digest, NULL); + if (ret < 0) + dim_err("failed to check kernel digest: %d\n", ret); + + return ret; +} + +struct dim_measure_task dim_core_measure_task_kernel_text = { + .name = "dim_core_measure_task_kernel_text", + .init = NULL, + .destroy = NULL, + .measure = kernel_text_measure, +}; diff --git a/security/integrity/dim/core/tasks/dim_core_measure_module.c b/security/integrity/dim/core/tasks/dim_core_measure_module.c new file mode 100644 index 000000000000..613e0e593873 --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_module.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include + +#include "dim_utils.h" +#include "dim_hash.h" +#include "dim_baseline.h" +#include "dim_measure_log.h" + +#include "dim_core_measure.h" +#include "dim_core_policy.h" +#include "dim_core_symbol.h" + +#include "dim_core_measure_task.h" + +struct module_text_measure_ctx { + struct dim_measure *m; + int mode; +}; + +static int calculate_module_digest(const char *name, + struct dim_hash *hash, + struct dim_digest *digest) +{ + int ret = 0; + struct module *mod = NULL; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + mutex_lock(&module_mutex); + mod = find_module(name); +#else + rcu_read_lock_sched(); + mod = dim_core_kernel_symbol.find_module(name); +#endif + if (mod == NULL || mod->state != MODULE_STATE_LIVE || + !try_module_get(mod)) + mod = NULL; /* target module not exist or is not alive */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + mutex_unlock(&module_mutex); +#else + rcu_read_unlock_sched(); +#endif + if (mod == NULL) + return -ENOENT; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + ret = dim_hash_calculate(mod->core_layout.base, + mod->core_layout.text_size, + hash, digest); +#else + ret = dim_hash_calculate(mod->mem[MOD_TEXT].base, + mod->mem[MOD_TEXT].size, + hash, digest); +#endif + module_put(mod); + return ret; +} + +static int measure_module(struct dim_policy *policy, void *data) +{ + int ret = 0; + struct module_text_measure_ctx *ctx = data; + const char *mod_name = NULL; + struct dim_digest digest = { 0 }; + + if (policy == NULL || policy->obj != DIM_POLICY_OBJ_MODULE_TEXT || + policy->name == NULL) + return 0; + + mod_name = policy->name; + + /* if module is not inserted in baseline_init stage, ignore it */ + if (ctx->mode == DIM_MEASURE && + dim_measure_dynamic_baseline_search(ctx->m, mod_name, + DIM_BASELINE_KERNEL, &digest) < 0) + return 0; + + digest.algo = ctx->m->hash.algo; + ret = calculate_module_digest(mod_name, &ctx->m->hash, &digest); + if (ret < 0) { + dim_err("fail to calculate digest of module %s: %d\n", + mod_name, ret); + return ret == -ENOENT ? 0 : ret; + } + + ret = dim_measure_process_dynamic_result(ctx->m, ctx->mode, + mod_name, &digest, NULL); + if (ret < 0) + dim_err("failed to check module digest: %d\n", ret); + + return 0; +} + +static int module_text_measure(int mode, struct dim_measure *m) +{ + struct module_text_measure_ctx ctx = { + .m = m, + .mode = mode, + }; + + if (m == NULL) + return -EINVAL; + + return dim_core_policy_walk(measure_module, &ctx); +} + +struct dim_measure_task dim_core_measure_task_module_text = { + .name = "dim_core_measure_task_module_text", + .init = NULL, + .destroy = NULL, + .measure = module_text_measure, +}; diff --git a/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process.c b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process.c new file mode 100644 index 000000000000..2ea19803965f --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process.c @@ -0,0 +1,298 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dim_hash.h" +#include "dim_measure_log.h" +#include "dim_baseline.h" + +#include "dim_core_symbol.h" +#include "dim_core_policy.h" +#include "dim_core_measure.h" + +#include "dim_core_measure_task.h" +#include "dim_core_measure_process.h" + +static struct vm_area_struct *next_module_text_vma(struct vm_area_struct *vma) +{ + struct vm_area_struct *v = NULL; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + for (v = vma->vm_next; v != NULL && + !(vma_is_file_text(v) && !vma_file_is_same(v, vma)); + v = v->vm_next) {} +#else + VMA_ITERATOR(vmi, vma->vm_mm, vma->vm_end); + for_each_vma(vmi, v) { + if (vma_is_file_text(v) && !vma_file_is_same(v, vma)) + break; + } +#endif + return v; +} + +static int kill_task(struct task_struct *p, void * __always_unused data) +{ + if (p == current) { + /* dont kill the current process */ + dim_warn("don't kill the current process\n"); + return 0; + } + + send_sig(SIGKILL, p, 1); + return 1; +} + +static int kill_task_tree(struct task_struct *tsk) +{ + if (tsk->pid == 1) { + /* dont kill the init process */ + dim_warn("the pid of tampered task is 1, don't kill it\n"); + return 0; + } + + if (tsk == current) { + /* dont kill the current process */ + dim_warn("don't kill the current process\n"); + return 0; + } + + dim_core_kernel_symbol.walk_process_tree(tsk, kill_task, NULL); + send_sig(SIGKILL, tsk, 1); + return 0; +} + +static bool vm_file_match_policy(struct file *vm_file, + struct task_measure_ctx *ctx) +{ + struct dim_digest dig = { 0 }; + + /* get the module path string */ + ctx->path = d_path(&vm_file->f_path, ctx->path_buf, PATH_MAX); + if (IS_ERR(ctx->path)) { + dim_warn("failed to get path of vma: %ld\n", PTR_ERR(ctx->path)); + ctx->path = NULL; + return false; + } + + if (ctx->mode == DIM_BASELINE) + return dim_core_policy_match(DIM_POLICY_OBJ_BPRM_TEXT, + DIM_POLICY_KEY_PATH, ctx->path); + + return dim_measure_dynamic_baseline_search(ctx->m, ctx->path, + DIM_BASELINE_USER, &dig) == 0; +} + +static int check_process_digest(struct dim_digest *digest, + void *data) +{ + int ret = 0; + int log_flag = 0; + int action = 0; + struct task_measure_ctx *ctx = data; + + if (digest == NULL || data == NULL) + return -EINVAL; + + ret = dim_measure_process_static_result(ctx->m, ctx->mode, ctx->path, + digest, &log_flag); + if (ret < 0) { + dim_err("failed to check user digest: %d\n", ret); + return ret; + } + + if (log_flag != LOG_TAMPERED || ctx->mode != DIM_MEASURE || + dim_core_measure_action_get() == DIM_MEASURE_ACTION_DISABLE) + return 0; + + /* now the process is tampered, check if action need to be taken */ + action = dim_core_policy_get_action(DIM_POLICY_OBJ_BPRM_TEXT, + DIM_POLICY_KEY_PATH, ctx->path); + if (action == DIM_POLICY_ACTION_KILL) { + dim_warn("kill action is set, process %s will be killed\n", + ctx->path); + ctx->task_kill = true; /* this task need to be killed */ + } + + return 0; +} + +static void measure_task_module(struct vm_area_struct *vma, + struct task_measure_ctx *ctx) +{ + int ret = 0; + + /* vma is the first file mapping text vma of the module, + so vm_file is not NULL */ + if (!vm_file_match_policy(get_vm_file(vma), ctx)) + return; /* no need to measure */ + + ctx->task_measure = true; + + /* now we only measure the text memory */ + ret = measure_process_text(vma, ctx); + if (ret < 0) + dim_err("failed to measure module file text: %d", ret); +} + +static int measure_task(struct task_struct *task, struct task_measure_ctx *ctx) +{ + int ret = 0; + struct mm_struct *mm = NULL; + struct vm_area_struct *v = NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + struct vma_iterator vmi = { 0 }; +#endif + mm = get_task_mm(task); + if (mm == NULL) + return 0; + + ret = down_read_killable(&mm->mmap_lock); + if (ret < 0) { + mmput(mm); + return ret; /* need to return if killed */ + } + + ctx->path = NULL; + ctx->task = task; + ctx->task_kill = false; + ctx->task_measure = false; + + /* find the first file mapping text vma, which is the + start vma of a module */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + for (v = mm->mmap; v != NULL && + !vma_is_file_text(v); v = v->vm_next) {} +#else + vma_iter_init(&vmi, mm, 0); + for_each_vma(vmi, v) { + if (vma_is_file_text(v)) + break; + } +#endif + if (v == NULL) { + dim_warn("no valid file text vma"); + ret = -ENOENT; + goto out; + } + + for (; v != NULL; v = next_module_text_vma(v)) + measure_task_module(v, ctx); +out: + up_read(&mm->mmap_lock); + mmput(mm); + + if (ctx->task_kill) { + ret = kill_task_tree(task); + if (ret < 0) + dim_err("failed to kill tampered task, pid = %d: %d\n", + task->pid, ret); + } + + /* do schedule if this task is measured */ + if (ctx->task_measure) + dim_measure_schedule(ctx->m); + + return 0; +} + +static int store_task_pids(pid_t **pid_buf, unsigned int *pid_cnt) +{ + struct task_struct *tsk = NULL; + pid_t *buf = NULL; + unsigned int cnt = 0; + unsigned int max_cnt = (PID_MAX_DEFAULT << 1); + + /* maximum processing of PID_MAX_DEFAULT * 2 pids */ + buf = dim_vzalloc(max_cnt); + if (buf == NULL) { + dim_err("failed to allocate memory for pid buffer\n"); + return -ENOMEM; + } + + cnt = 0; + rcu_read_lock(); + for_each_process(tsk) { + /* don't measure kernel thread */ + if (tsk->flags & PF_KTHREAD) + continue; + + buf[cnt++] = tsk->pid; + if (cnt >= max_cnt) { + dim_warn("pid number reaches the limit\n"); + break; + } + } + rcu_read_unlock(); + + *pid_buf = buf; + *pid_cnt = cnt; + return 0; +} + +static int walk_measure_tasks(struct task_measure_ctx *ctx) +{ + int ret = 0; + unsigned int i = 0; + unsigned int pid_cnt = 0; + pid_t *pid_buf = NULL; + struct task_struct *task = NULL; + + ret = store_task_pids(&pid_buf, &pid_cnt); + if (ret < 0) + return ret; + + for (i = 0; i < pid_cnt; i++) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + task = find_get_task_by_vpid(pid_buf[i]); +#else + task = dim_core_kernel_symbol.find_get_task_by_vpid(pid_buf[i]); +#endif + if (task == NULL) + continue; + + ret = measure_task(task, ctx); + put_task_struct(task); + if (ret < 0) { + dim_err("failed to measure task, pid = %d: %d", pid_buf[i], ret); + if (ret == -EINTR) + break; + } + } + + dim_vfree(pid_buf); + return 0; +} + +static int user_text_measure(int mode, struct dim_measure *m) +{ + int ret = 0; + struct task_measure_ctx *ctx = NULL; + + if (m == NULL) + return -EINVAL; + + ctx = dim_vzalloc(sizeof(struct task_measure_ctx)); + if (ctx == NULL) + return -ENOMEM; + + ctx->mode = mode; + ctx->m = m; + ctx->check = check_process_digest; + + ret = walk_measure_tasks(ctx); + dim_vfree(ctx); + return ret; +} + +struct dim_measure_task dim_core_measure_task_user_text = { + .name = "dim_core_measure_task_user_text", + .measure = user_text_measure, +}; diff --git a/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process.h b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process.h new file mode 100644 index 000000000000..b697904a988b --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_CORE_MEASURE_PROCESS_H +#define __DIM_CORE_MEASURE_PROCESS_H + +#include +#include + +/* callback funtion to check results when do measurement */ +typedef int (*process_digest_check_func) (struct dim_digest *digest, + void *ctx); + +/* the context used in user process measurement */ +struct task_measure_ctx { + struct dim_measure *m; + /* DIM_BASELINE or DIM_MEASURE */ + int mode; + char path_buf[PATH_MAX]; + /* current measured process name */ + const char *path; + /* current measured process */ + struct task_struct *task; + /* this process need to be killed */ + bool task_kill; + /* this process is measured */ + bool task_measure; + /* check function */ + process_digest_check_func check; +}; + +static inline struct file *get_vm_file(struct vm_area_struct *vma) +{ + return vma == NULL ? NULL : vma->vm_file; +} + +static inline bool vma_is_text(struct vm_area_struct *vma) +{ + return (vma->vm_flags & VM_READ) && (vma->vm_flags & VM_EXEC) && + !(vma->vm_flags & VM_WRITE); +} + +static inline bool vma_is_file_text(struct vm_area_struct *vma) +{ + return vma_is_text(vma) && get_vm_file(vma) != NULL; +} + +static inline bool vma_file_is_same(struct vm_area_struct *first, + struct vm_area_struct *second) +{ + return get_vm_file(first) == get_vm_file(second); +} + +static inline bool vma_can_merge(struct vm_area_struct *first, + struct vm_area_struct *second) +{ + return (first->vm_end == second->vm_start) && + (vma_file_is_same(first, second)); +} + +static inline bool vma_is_not_same_module(struct vm_area_struct *a, + struct vm_area_struct *b) +{ + struct file *fa = get_vm_file(a); + struct file *fb = get_vm_file(b); + return (fa != NULL && fb != NULL && fa != fb); +} + +#ifdef CONFIG_DIM_CORE_MEASURE_PROCESS_ELF +int measure_process_module_text_elf(struct vm_area_struct *vma, + struct task_measure_ctx *ctx); +#define measure_process_text measure_process_module_text_elf +#else +int measure_process_module_text_vma(struct vm_area_struct *vma, + struct task_measure_ctx *ctx); +#define measure_process_text measure_process_module_text_vma +#endif + +extern struct dim_measure_task dim_core_measure_task_user_text; + +#endif diff --git a/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process_vma.c b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process_vma.c new file mode 100644 index 000000000000..d3ba2417f860 --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_core_measure_process_vma.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include + +#include "dim_measure.h" +#include "dim_vm_hash.h" +#include "dim_core_measure_process.h" + +static struct vm_area_struct *find_text_vma_end(struct vm_area_struct *vma) +{ + struct vm_area_struct *v = NULL; + struct vm_area_struct *vma_end = vma; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + for (v = vma->vm_next; v != NULL && vma_is_file_text(v) && + vma_can_merge(vma_end, v); v = v->vm_next) + vma_end = v; +#else + VMA_ITERATOR(vmi, vma->vm_mm, vma->vm_end); + for_each_vma(vmi, v) { + if (!vma_is_file_text(v) || !vma_can_merge(vma_end, v)) + break; + + vma_end = v; + } +#endif + return vma_end; +} + +static struct vm_area_struct *next_file_text_vma(struct vm_area_struct *vma) +{ + struct vm_area_struct *v = NULL; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + for (v = vma->vm_next; v != NULL && + !vma_is_file_text(v); v = v->vm_next) {} +#else + VMA_ITERATOR(vmi, vma->vm_mm, vma->vm_end); + for_each_vma(vmi, v) { + if (vma_is_file_text(v)) + break; + } + + if (!vma_is_file_text(v)) + v = NULL; +#endif + return v; +} + +/* For file text segment, merge all file mapping text vma and measure */ +int measure_text_vma(struct vm_area_struct *vma, struct task_measure_ctx *ctx) +{ + int ret = 0; + struct vm_area_struct *v = vma; + struct vm_area_struct *v_end = NULL; + struct dim_digest digest = { + .algo = ctx->m->hash.algo + }; + SHASH_DESC_ON_STACK(shash, ctx->m->hash.tfm); + + shash->tfm = ctx->m->hash.tfm; + ret = crypto_shash_init(shash); + if (ret < 0) + return ret; + + /* now the vma is the first file text vma of a process module */ + while (v != NULL && vma_file_is_same(v, vma)) { + v_end = find_text_vma_end(v); + /* update all the continuous text vma */ + ret = dim_vm_hash_update_vmas(v, v_end, shash); + if (ret < 0) + return ret; + + v = next_file_text_vma(v_end); + } + + ret = crypto_shash_final(shash, digest.data); + if (ret < 0) + return ret; + + return ctx->check(&digest, ctx); +} + +int measure_process_module_text_vma(struct vm_area_struct *vma, + struct task_measure_ctx *ctx) +{ + if (vma == NULL || !vma_is_file_text(vma) || ctx == NULL + || ctx->m == NULL || ctx->check == NULL) + return -EINVAL; + + return measure_text_vma(vma, ctx); +} diff --git a/security/integrity/dim/core/tasks/dim_core_measure_process/dim_vm_hash.c b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_vm_hash.c new file mode 100644 index 000000000000..c3a888737341 --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_vm_hash.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include +#include + +#include "dim_utils.h" +#include "dim_safe_func.h" + +#include "dim_vm_hash.h" + +int dim_vm_hash_update_address(struct mm_struct *mm, + unsigned long addr_start, + unsigned long addr_len, + struct shash_desc *shash) +{ + int ret = 0; + unsigned long i = 0; + long ret_pages = 0; + void *page_ptr = NULL; + struct page **pages = NULL; + unsigned int update_size = PAGE_SIZE; + unsigned long nr_pages = DIV_ROUND_UP(addr_len, PAGE_SIZE); + + if (mm == NULL || addr_len == 0 || shash == NULL) + return -EINVAL; + + pages = dim_vzalloc(nr_pages * sizeof(struct page *)); + if (pages == NULL) + return -ENOMEM; + + ret_pages = get_user_pages_remote(mm, addr_start, nr_pages, +#if LINUX_VERSION_CODE <= KERNEL_VERSION(6,4,0) + 0, pages, NULL, NULL); +#else + 0, pages, NULL); +#endif + if (ret_pages < 0) { + dim_err("failed to get remote pages: %ld\n", ret_pages); + dim_vfree(pages); + return ret_pages; + } else if (ret_pages != nr_pages) { + dim_warn("failed to get all remote pages\n"); + } + + for (i = 0; i < ret_pages; i++) { + page_ptr = kmap(pages[i]); + if (page_ptr == NULL) { + dim_err("failed to kmap remote page\n"); + put_page(pages[i]); + continue; + } + + if (i == ret_pages - 1) + update_size = addr_len % PAGE_SIZE ? + addr_len % PAGE_SIZE : PAGE_SIZE; + + ret = crypto_shash_update(shash, page_ptr, update_size); + if (ret < 0) + dim_warn("failed to update hash: %d\n", ret); + + kunmap(pages[i]); + put_page(pages[i]); + } + + dim_vfree(pages); + return 0; +} + +/* calculate hash digest of continuous vma */ +int dim_vm_hash_update_vmas(struct vm_area_struct *vma_start, + struct vm_area_struct *vma_end, + struct shash_desc *shash) +{ + if (vma_start == NULL || vma_end == NULL || shash == NULL || + vma_start->vm_mm != vma_end->vm_mm || + vma_start->vm_start >= vma_end->vm_end) + return -EINVAL; + + return dim_vm_hash_update_address(vma_start->vm_mm, vma_start->vm_start, + vma_end->vm_end - vma_start->vm_start, shash); +} + +/* calculate hash digest of vma */ +int dim_vm_hash_calculate_vma(struct vm_area_struct *vma, + struct dim_hash *hash, + struct dim_digest *digest) +{ + int ret = 0; + /* check here to avoid code check warning */ + SHASH_DESC_ON_STACK(shash, hash == NULL ? NULL : hash->tfm); + + if (vma == NULL || hash == NULL || digest == NULL) + return -EINVAL; + + shash->tfm = hash->tfm; + ret = crypto_shash_init(shash); + if (ret < 0) + return ret; + + ret = dim_vm_hash_update_vmas(vma, vma, shash); + if (ret < 0) + return ret; + + return crypto_shash_final(shash, digest->data); +} diff --git a/security/integrity/dim/core/tasks/dim_core_measure_process/dim_vm_hash.h b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_vm_hash.h new file mode 100644 index 000000000000..7c996e9789e6 --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_process/dim_vm_hash.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_VM_HASH_H +#define __DIM_VM_HASH_H + +#include + +#include "dim_hash.h" + +int dim_vm_hash_update_address(struct mm_struct *mm, + unsigned long addr_start, + unsigned long addr_len, + struct shash_desc *shash); + +int dim_vm_hash_update_vmas(struct vm_area_struct *vma_start, + struct vm_area_struct *vma_end, + struct shash_desc *shash); + +int dim_vm_hash_calculate_vma(struct vm_area_struct *vma, + struct dim_hash *hash, + struct dim_digest *digest); + +#endif \ No newline at end of file diff --git a/security/integrity/dim/core/tasks/dim_core_measure_task.h b/security/integrity/dim/core/tasks/dim_core_measure_task.h new file mode 100644 index 000000000000..145e0f35170e --- /dev/null +++ b/security/integrity/dim/core/tasks/dim_core_measure_task.h @@ -0,0 +1,9 @@ +#ifndef __DIM_CORE_MEASURE_TASK_H +#define __DIM_CORE_MEASURE_TASK_H + +#include "dim_core_measure_process.h" + +extern struct dim_measure_task dim_core_measure_task_module_text; +extern struct dim_measure_task dim_core_measure_task_kernel_text; + +#endif diff --git a/security/integrity/dim/measure/dim_measure.c b/security/integrity/dim/measure/dim_measure.c new file mode 100644 index 000000000000..c40be0e5d33e --- /dev/null +++ b/security/integrity/dim/measure/dim_measure.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_measure.h" + +static int cfg_check(struct dim_measure_cfg *cfg) +{ + if (cfg->log_cap < MEASURE_LOG_CAP_MIN || + cfg->log_cap > MEASURE_LOG_CAP_MAX) { + dim_err("invalid log capacity: %d\n", cfg->log_cap); + return -ERANGE; + } + + if (cfg->schedule_ms > MEASURE_SCHEDULE_MAX) { + dim_err("invalid measure schedule: %d\n", cfg->schedule_ms); + return -ERANGE; + } + + if (cfg->pcr > DIM_PCR_MAX) { + dim_err("invalid TPM pcr number: %d\n", cfg->pcr); + return -ERANGE; + } + + return 0; +} + +int dim_measure_init(struct dim_measure *m, struct dim_measure_cfg *cfg) +{ + int ret = 0; + + if (m == NULL || cfg == NULL || cfg_check(cfg) < 0) + return -EINVAL; + + INIT_LIST_HEAD(&m->task_list); + + /* 1. init hash algorithm */ + ret = dim_hash_init(cfg->alg_name, &m->hash); + if (ret < 0) { + dim_err("failed to init hash algorithm: %d\n", ret); + goto err; + } + + /* 2. init TPM, dont break if init fail */ + if (cfg->pcr > 0) { + ret = dim_tpm_init(&m->tpm, HASH_ALGO_SHA256); + if (ret < 0) { + cfg->pcr = 0; + dim_warn("failed to init tpm chip: %d\n", ret); + } + } else { + memset(&m->tpm, 0, sizeof(struct dim_tpm)); + } + + /* 3. init baseline data (static and dynamic) */ + ret = dim_baseline_init_tree(cfg->sta_malloc, cfg->sta_free, + &m->static_baseline); + if (ret < 0) { + dim_err("failed to init static baseline root: %d\n", ret); + goto err; + } + + ret = dim_baseline_init_tree(cfg->dyn_malloc, cfg->dyn_free, + &m->dynamic_baseline); + if (ret < 0) { + dim_err("failed to init dynamic baseline root: %d\n", ret); + goto err; + } + + /* 4. init measure log */ + ret = dim_measure_log_init_tree(&m->log, &m->hash, &m->tpm, + cfg->log_cap, cfg->pcr); + if (ret < 0) { + dim_err("failed to init measure log: %d\n", ret); + goto err; + } + + /* 5. set measure schedule time */ + m->schedule_jiffies = cfg->schedule_ms == 0 ? 0 : + msecs_to_jiffies(cfg->schedule_ms); + + /* 6. set initial status */ + atomic_set(&m->status, MEASURE_STATUS_NO_BASELINE); + return 0; +err: + dim_measure_destroy(m); + return ret; +} + +void dim_measure_destroy(struct dim_measure *m) +{ + if (m == NULL) + return; + + mutex_lock(&m->measure_lock); + dim_measure_tasks_unregister_all(m); + dim_measure_log_destroy_tree(&m->log); + dim_baseline_destroy_tree(&m->static_baseline); + dim_baseline_destroy_tree(&m->dynamic_baseline); + dim_tpm_destroy(&m->tpm); + dim_hash_destroy(&m->hash); + mutex_unlock(&m->measure_lock); +} diff --git a/security/integrity/dim/measure/dim_measure.h b/security/integrity/dim/measure/dim_measure.h new file mode 100644 index 000000000000..f5140f03721c --- /dev/null +++ b/security/integrity/dim/measure/dim_measure.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_MEASURE_H +#define __DIM_MEASURE_H + +#include +#include + +#include "dim_baseline.h" +#include "dim_hash.h" +#include "dim_measure_log.h" +#include "dim_tpm.h" +#include "dim_utils.h" + +#define DIM_MEASURE 0 +#define DIM_BASELINE 1 + +/* limit of measure parameter */ +#define MEASURE_LOG_CAP_MAX (UINT_MAX) +#define MEASURE_LOG_CAP_MIN (100) +#define MEASURE_SCHEDULE_MAX (1000) + +/* status of measurement */ +enum dim_measure_status { + MEASURE_STATUS_OFF, + MEASURE_STATUS_NO_BASELINE, + MEASURE_STATUS_BASELINE_RUNNING, + MEASURE_STATUS_MEASURE_RUNNING, + MEASURE_STATUS_PROTECTED, + MEASURE_STATUS_ERROR, + MEASURE_STATUS_LAST, +}; + +/* the common configuration for measurement */ +struct dim_measure_cfg { + /* hash algorithm for measurement */ + char *alg_name; + /* schedule time (ms) after one valid measurement */ + unsigned int schedule_ms; + /* PCR number for TPM extending */ + unsigned int pcr; + /* max measure log number */ + unsigned int log_cap; + /* memory function for baseline store */ + malloc_func dyn_malloc; + free_func dyn_free; + malloc_func sta_malloc; + free_func sta_free; +}; + +/* the dim measurement global handle */ +struct dim_measure { + /* schedule time (jittfies) after one valid measurement */ + unsigned long schedule_jiffies; + /* lock to prevent concurrent measurement */ + struct mutex measure_lock; + /* measure hash algorithm */ + struct dim_hash hash; + /* TPM chip handle */ + struct dim_tpm tpm; + /* measure log */ + struct dim_measure_log_tree log; + /* measure baseline */ + struct dim_baseline_tree static_baseline; + struct dim_baseline_tree dynamic_baseline; + /* function called before doing baseline */ + int (*baseline_prepare)(struct dim_measure *m); + /* measure status */ + atomic_t status; + /* task list */ + struct list_head task_list; +}; + +/* the task definition for measurement function */ +struct dim_measure_task { + struct list_head node; + /* task name for log printing */ + const char *name; + /* init and destroy functions */ + int (*init)(void); + void (*destroy)(void); + /* measure function */ + int (*measure)(int mode, struct dim_measure *m); +}; + +/* functions for dim measure handle */ +int dim_measure_init(struct dim_measure *m, struct dim_measure_cfg *cfg); +void dim_measure_destroy(struct dim_measure *m); + +/* functions for measurement results processing */ +int dim_measure_process_static_result(struct dim_measure *m, int mode, + const char *name, + struct dim_digest *digest, + int *log_flag); +int dim_measure_process_dynamic_result(struct dim_measure *m, int mode, + const char *name, + struct dim_digest *digest, + int *log_flag); +int dim_measure_static_baseline_add(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest); +int dim_measure_dynamic_baseline_add(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest); +int dim_measure_static_baseline_search(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest); +int dim_measure_dynamic_baseline_search(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest); + +/* functions for dim measurement task */ +int dim_measure_tasks_register(struct dim_measure *m, + struct dim_measure_task **tasks, + unsigned int num); +void dim_measure_tasks_unregister_all(struct dim_measure *m); +void dim_measure_task_measure(int mode, struct dim_measure *m); + +/* functions for dim measurement status */ +const char *dim_measure_status_print(struct dim_measure *m); +bool dim_measure_status_error(struct dim_measure *m); + +/* tool functions used for implementing measure tasks */ +void dim_measure_schedule(struct dim_measure *m); + +#endif diff --git a/security/integrity/dim/measure/dim_measure_baseline.c b/security/integrity/dim/measure/dim_measure_baseline.c new file mode 100644 index 000000000000..185a06ec86fa --- /dev/null +++ b/security/integrity/dim/measure/dim_measure_baseline.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_measure.h" + +static inline bool is_valid_mode(int mode) +{ + return mode == DIM_BASELINE || mode == DIM_MEASURE; +} + +static const char *process_static_name(const char *name, int type, + char *buf, int buf_len) +{ + const char *kr = init_uts_ns.name.release; + + if (type != DIM_BASELINE_KERNEL || dim_strcmp(name, kr) == 0) + return name; + + /* name of kernel module has a kernel prefix in static baseline */ + if (sprintf(buf, "%s/%s", kr, name) < 0) + return NULL; + + return buf; +} + +static int static_baseline_add(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + int ret = dim_baseline_add(&m->static_baseline, name, type, digest); + if (ret < 0 && ret != -EEXIST) { + dim_err("failed to add static baseline of %s\n", name); + return ret; + } + + return 0; +} + +static int dynamic_baseline_add(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + int ret = dim_baseline_add(&m->dynamic_baseline, name, type, digest); + if (ret < 0 && ret != -EEXIST) { + dim_err("failed to add dynamic baseline of %s\n", name); + return ret; + } + + return 0; +} + +static bool static_baseline_match(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + char buf[NAME_MAX + NAME_MAX + 1 + 1] = { 0 }; + return dim_baseline_match(&m->static_baseline, + process_static_name(name, type, buf, sizeof(buf)), + type, digest); +} + +static bool dynamic_baseline_match(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + return dim_baseline_match(&m->dynamic_baseline, name, type, digest); +} + +static int static_baseline_search(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + char buf[NAME_MAX + NAME_MAX + 1 + 1] = { 0 }; + return dim_baseline_search_digest(&m->static_baseline, + process_static_name(name, type, buf, sizeof(buf)), + type, digest); +} + +static int dynamic_baseline_search(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + return dim_baseline_search_digest(&m->dynamic_baseline, name, + type, digest); +} + +static int measure_log_add(struct dim_measure *m, const char *name, + struct dim_digest *digest, int flag) +{ + int ret = dim_measure_log_add(&m->log, name, digest, flag); + if (ret < 0 && ret != -EEXIST) { + dim_err("failed to add measure log of %s: %d\n", name, ret); + /* the measure log of this object has been limited */ + return ret == -ENOSPC ? 0 : ret; + } + + return 0; +} + +/* check dynamic measurement result in baseline stage */ +static int process_dynamic_baseline(struct dim_measure *m, const char *name, + struct dim_digest *digest, int *log_flag) +{ + int ret = 0; + struct dim_digest digest_static = { 0 }; + int def_flag = log_flag == NULL ? LOG_NO_SATIC_BASELINE : *log_flag; + + if (m == NULL || name == NULL || digest == NULL) + return -EINVAL; + + /* 1. add digest to dynamic baseline */ + ret = dynamic_baseline_add(m, name, DIM_BASELINE_KERNEL, digest); + if (ret < 0) + return ret; + + /* 2. search digest from static baseline */ + ret = static_baseline_search(m, name, DIM_BASELINE_KERNEL, &digest_static); + if (ret < 0) + /* 2.1. if not find, log the dynamic baseline */ + return measure_log_add(m, name, digest, def_flag); + + /* 2.2. if find, log the static baseline */ + return measure_log_add(m, name, &digest_static, LOG_STATIC_BASELINE); +} + +/* process dynamic measurement result in measure stage */ +static int process_dynamic_measure(struct dim_measure *m, const char *name, + struct dim_digest *digest, int *log_flag) +{ + if (m == NULL || name == NULL || digest == NULL) + return -EINVAL; + + if(!dynamic_baseline_match(m, name, DIM_BASELINE_KERNEL, digest)) { + dim_err("mismatch dynamic baseline of kernel %s\n", name); + if (log_flag != NULL) + *log_flag = LOG_TAMPERED; + + return measure_log_add(m, name, digest, LOG_TAMPERED); + } + + return 0; +} + +/* process static measurement result in baseline stage */ +static int process_static_baseline(struct dim_measure *m, const char *name, + struct dim_digest *digest, int *log_flag) +{ + int ret = 0; + struct dim_digest digest_static = { 0 }; + + /* 1. add digest to dynamic baseline */ + ret = dynamic_baseline_add(m, name, DIM_BASELINE_USER, digest); + if (ret < 0) + return ret; + + /* 2. search digest from static baseline */ + ret = static_baseline_search(m, name, DIM_BASELINE_USER, &digest_static); + if (ret < 0) /* 2.1. if not find, log the dynamic baseline */ + return measure_log_add(m, name, digest, LOG_NO_SATIC_BASELINE); + + /* 2.2. if find, compare with the static baseline */ + if (static_baseline_match(m, name, DIM_BASELINE_USER, digest)) + return measure_log_add(m, name, digest, LOG_STATIC_BASELINE); + + dim_warn("mismatch static baseline of user process %s\n", name); + if (log_flag != NULL) + *log_flag = LOG_TAMPERED; + + return measure_log_add(m, name, digest, LOG_TAMPERED); +} + +/* process static measurement result in measure stage */ +static int process_static_measure(struct dim_measure *m, const char *name, + struct dim_digest *digest, int *log_flag) +{ + if(!dynamic_baseline_match(m, name, DIM_BASELINE_USER, digest)) { + dim_err("mismatch dynamic baseline of user %s\n", name); + if (log_flag != NULL) + *log_flag = LOG_TAMPERED; + + return measure_log_add(m, name, digest, LOG_TAMPERED); + } + + return 0; +} + +int dim_measure_process_static_result(struct dim_measure *m, int mode, + const char *name, + struct dim_digest *digest, + int *log_flag) +{ + if (m == NULL || name == NULL || digest == NULL || + !is_valid_mode(mode)) + return -EINVAL; + + return mode == DIM_BASELINE ? + process_static_baseline(m, name, digest, log_flag) : + process_static_measure(m, name, digest, log_flag); +} + +int dim_measure_process_dynamic_result(struct dim_measure *m, int mode, + const char *name, + struct dim_digest *digest, + int *log_flag) +{ + if (m == NULL || name == NULL || digest == NULL || + !is_valid_mode(mode)) + return -EINVAL; + + return mode == DIM_BASELINE ? + process_dynamic_baseline(m, name, digest, log_flag) : + process_dynamic_measure(m, name, digest, log_flag); +} + +int dim_measure_static_baseline_add(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + if (m == NULL) + return -EINVAL; + + return static_baseline_add(m, name, type, digest); +} + +int dim_measure_dynamic_baseline_add(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + if (m == NULL) + return -EINVAL; + + return dynamic_baseline_add(m, name, type, digest); +} + +int dim_measure_static_baseline_search(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + if (m == NULL) + return -EINVAL; + + return static_baseline_search(m, name, type, digest); +} + +int dim_measure_dynamic_baseline_search(struct dim_measure *m, + const char *name, int type, + struct dim_digest *digest) +{ + if (m == NULL) + return -EINVAL; + + return dynamic_baseline_search(m, name, type, digest); +} diff --git a/security/integrity/dim/measure/dim_measure_status.c b/security/integrity/dim/measure/dim_measure_status.c new file mode 100644 index 000000000000..e57e6115aeaa --- /dev/null +++ b/security/integrity/dim/measure/dim_measure_status.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_measure.h" + +static const char* status_name[MEASURE_STATUS_LAST + 1] = { + [MEASURE_STATUS_OFF] = "DIM_OFF", + [MEASURE_STATUS_NO_BASELINE] = "DIM_NO_BASELINE", + [MEASURE_STATUS_BASELINE_RUNNING] = "DIM_BASELINE_RUNNING", + [MEASURE_STATUS_MEASURE_RUNNING] = "DIM_MEASURE_RUNNING", + [MEASURE_STATUS_PROTECTED] = "DIM_PROTECTED", + [MEASURE_STATUS_ERROR] = "DIM_ERROR", + [MEASURE_STATUS_LAST] = "DIM_UNKNOWN", +}; + +const char *dim_measure_status_print(struct dim_measure *m) +{ + int status = 0; + + if (m == NULL) + return status_name[MEASURE_STATUS_LAST]; + + status = atomic_read(&m->status); + if (status < 0 || status >= MEASURE_STATUS_LAST) + status = MEASURE_STATUS_LAST; + + return status_name[status]; +} + +bool dim_measure_status_error(struct dim_measure *m) +{ + return atomic_read(&m->status) == MEASURE_STATUS_ERROR; +} diff --git a/security/integrity/dim/measure/dim_measure_task.c b/security/integrity/dim/measure/dim_measure_task.c new file mode 100644 index 000000000000..adfc57d8a91b --- /dev/null +++ b/security/integrity/dim/measure/dim_measure_task.c @@ -0,0 +1,121 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_measure.h" + +static void call_measure_func(int mode, struct dim_measure_task *t, + struct dim_measure *m) +{ + int ret = 0; + + if (t->measure == NULL) { + dim_warn("no measure function in %s task", t->name); + return; + } + + dim_info("start to call %s measure task\n", t->name); + ret = t->measure(mode, m); + if (ret < 0) { + dim_err("failed to call measure task %s: %d\n", t->name, ret); + return; + } + + dim_info("succeed to call measure task %s\n", t->name); +} + +void dim_measure_task_measure(int mode, struct dim_measure *m) +{ + int ret = 0; + int status = 0; + struct dim_measure_task *task = NULL; + + if (m == NULL) + return; + + mutex_lock(&m->measure_lock); + status = atomic_read(&m->status); + if (mode == DIM_MEASURE && status != MEASURE_STATUS_PROTECTED) { + dim_info("no baseline, do baseline init instead\n"); + mode = DIM_BASELINE; + } + + atomic_set(&m->status, mode == DIM_BASELINE ? + MEASURE_STATUS_BASELINE_RUNNING : + MEASURE_STATUS_MEASURE_RUNNING); + + if (mode == DIM_BASELINE && m->baseline_prepare != NULL) { + ret = m->baseline_prepare(m); + if (ret < 0) { + atomic_set(&m->status, MEASURE_STATUS_ERROR); + mutex_unlock(&m->measure_lock); + return; + } + } + + list_for_each_entry(task, &m->task_list, node) + call_measure_func(mode, task, m); + + atomic_set(&m->status, MEASURE_STATUS_PROTECTED); + mutex_unlock(&m->measure_lock); +} + +static int task_register(struct dim_measure *m, struct dim_measure_task *t) +{ + int ret = 0; + + if (t == NULL || t->name == NULL || t->measure == NULL) + return -EINVAL; + + if (t->init != NULL) { + ret = t->init(); + if (ret < 0) + return ret; + } + + list_add_tail(&t->node, &m->task_list); + return 0; +} + +static void task_unregister(struct dim_measure_task *t) +{ + if (t->destroy != NULL) + t->destroy(); + + list_del(&t->node); +} + +int dim_measure_tasks_register(struct dim_measure *m, + struct dim_measure_task **tasks, + unsigned int num) +{ + int ret = 0; + int i = 0; + + if (m == NULL || tasks == NULL || num == 0) + return -EINVAL; + + for (; i < num; i++) { + ret = task_register(m, tasks[i]); + if (ret < 0) { + dim_measure_tasks_unregister_all(m); + return ret; + } + + dim_info("register measure task: %s\n", tasks[i]->name); + } + + return 0; +} + +void dim_measure_tasks_unregister_all(struct dim_measure *m) +{ + struct dim_measure_task *pos = NULL; + struct dim_measure_task *n = NULL; + + if (m == NULL) + return; + + list_for_each_entry_safe(pos, n, &m->task_list, node) + task_unregister(pos); +} diff --git a/security/integrity/dim/measure/dim_measure_utils.c b/security/integrity/dim/measure/dim_measure_utils.c new file mode 100644 index 000000000000..049d36241190 --- /dev/null +++ b/security/integrity/dim/measure/dim_measure_utils.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_measure.h" + +void dim_measure_schedule(struct dim_measure *m) +{ + if (m == NULL || m->schedule_jiffies == 0) + return; + + schedule_timeout_uninterruptible(m->schedule_jiffies); +} diff --git a/security/integrity/dim/monitor/dim_monitor.h b/security/integrity/dim/monitor/dim_monitor.h new file mode 100644 index 000000000000..8f1f0f7d821b --- /dev/null +++ b/security/integrity/dim/monitor/dim_monitor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_MONITOR_H +#define __DIM_MONITOR_H + +#include "dim_measure.h" + +#define DIM_MONITOR_HASH_DEFAULT "sha256" +#define DIM_MONITOR_LOG_CAP_DEFAULT 100000 + +#define DIM_CORE "dim_core" +#define DIM_CORE_TEXT "dim_core.text" +#define DIM_CORE_DATA "dim_core.data" + +extern struct dim_measure dim_monitor_handle; + +void dim_monitor_destroy_fs(void); +int dim_monitor_create_fs(void); + +int dim_monitor_measure_init(struct dim_measure_cfg *cfg); +void dim_monitor_measure_destroy(void); +int dim_monitor_measure_blocking(void); +int dim_monitor_baseline_blocking(void); +const char *dim_monitor_status_print(void); + +#endif diff --git a/security/integrity/dim/monitor/dim_monitor_fs.c b/security/integrity/dim/monitor/dim_monitor_fs.c new file mode 100644 index 000000000000..8bb3120c58fb --- /dev/null +++ b/security/integrity/dim/monitor/dim_monitor_fs.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_entry.h" +#include "dim_utils.h" + +#include "dim_measure.h" + +#include "dim_monitor.h" + +extern struct dim_entry *dim_root_entry(void); + +/* + * monitor trigger interface + * dim_entry struct: dim_monitor_measure_entry + * file entry name: monitor_run + * function: dim_monitor_measure_blocking() + */ +dim_trigger_entry(dim_monitor_measure, monitor_run, dim_monitor_measure_blocking); + +/* + * monitor baseline trigger interface + * dim_entry struct: dim_monitor_baseline_entry + * file entry name: monitor_baseline + * function: dim_monitor_baseline_blocking() + */ +dim_trigger_entry(dim_monitor_baseline, monitor_baseline, dim_monitor_baseline_blocking); + +/* + * status print interface + * dim_entry struct: dim_status_entry + * file entry name: monitor_status + * print function: dim_core_status_print + */ +dim_string_print_entry(dim_monitor_status, monitor_status, dim_monitor_status_print); + +/* + * measure log read interface + * dim_entry struct: dim_measure_log_entry + * file entry name: runtime_status + * status to read: dim_measure_log_tree + */ +dim_measure_log_entry(dim_monitor_log, monitor_ascii_runtime_measurements, + &dim_monitor_handle.log); + +static struct dim_entry *dim_monitor_files[] = { + &dim_monitor_measure_entry, + &dim_monitor_baseline_entry, + &dim_monitor_status_entry, + &dim_monitor_log_entry, +}; + +void dim_monitor_destroy_fs(void) +{ + unsigned int len = DIM_ARRAY_LEN(dim_monitor_files); + dim_entry_remove_list(dim_monitor_files, len); +} + +int dim_monitor_create_fs(void) +{ + struct dim_entry *dim_root = dim_root_entry(); + unsigned int len = DIM_ARRAY_LEN(dim_monitor_files); + + if (dim_root == NULL || dim_root->dentry == NULL) + return -ENOENT; + + return dim_entry_create_list(dim_monitor_files, len, dim_root->dentry); +} diff --git a/security/integrity/dim/monitor/dim_monitor_main.c b/security/integrity/dim/monitor/dim_monitor_main.c new file mode 100644 index 000000000000..d0e89f13daf9 --- /dev/null +++ b/security/integrity/dim/monitor/dim_monitor_main.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_measure.h" + +#include "dim_monitor.h" +#include "dim_monitor_symbol.h" + +/* common measurement configuration */ +static struct dim_measure_cfg cfg = { + .alg_name = DIM_MONITOR_HASH_DEFAULT, + .log_cap = DIM_MONITOR_LOG_CAP_DEFAULT, +}; + +module_param_named(measure_log_capacity, cfg.log_cap, uint, 0); +MODULE_PARM_DESC(measure_log_capacity, "Max number of measure log"); + +module_param_named(measure_hash, cfg.alg_name, charp, 0); +MODULE_PARM_DESC(measure_hash, "Hash algorithm for measurement"); + +module_param_named(measure_pcr, cfg.pcr, uint, 0); +MODULE_PARM_DESC(measure_pcr, "TPM PCR index to extend measure log"); + +static int __init dim_monitor_init(void) +{ + int ret; + + ret = dim_monitor_kallsyms_init(); + if (ret < 0) { + dim_err("fail to initialize dim kernel symbol: %d\n", ret); + goto err; + } + + ret = dim_monitor_measure_init(&cfg); + if (ret < 0) { + dim_err("fail to initialize dim measurement: %d\n", ret); + goto err; + } + + ret = dim_monitor_create_fs(); + if (ret < 0) { + dim_err("fail to create dim fs entry: %d\n", ret); + goto err; + } + + return 0; +err: + dim_monitor_measure_destroy(); + dim_monitor_destroy_fs(); + return ret; +} + +static void __exit dim_monitor_exit(void) +{ + dim_monitor_measure_destroy(); + dim_monitor_destroy_fs(); + + #ifdef DIM_DEBUG_MEMORY_LEAK + dim_check_memory_leak(); + #endif +} + +module_init(dim_monitor_init); +module_exit(dim_monitor_exit); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/dim/monitor/dim_monitor_measure.c b/security/integrity/dim/monitor/dim_monitor_measure.c new file mode 100644 index 000000000000..748d5f9a8416 --- /dev/null +++ b/security/integrity/dim/monitor/dim_monitor_measure.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include + +#include "dim_measure.h" + +#include "dim_core_mem_pool.h" + +#include "dim_monitor.h" +#include "dim_monitor_symbol.h" + +#include "measure_task/dim_monitor_measure_task.h" + +/* measurement tasks */ +static struct dim_measure_task *dim_core_tasks[] = { + &dim_monitor_measure_data, + &dim_monitor_measure_text, +}; + +/* the global measurement handle */ +struct dim_measure dim_monitor_handle = { + .task_list = LIST_HEAD_INIT(dim_monitor_handle.task_list), +}; + +/* lock to prevent trigger multiple measurement */ +DEFINE_MUTEX(dim_monitor_measure_lock); + +const char *dim_monitor_status_print(void) +{ + return dim_measure_status_print(&dim_monitor_handle); +} + +int dim_monitor_measure_blocking(void) +{ + if (!mutex_trylock(&dim_monitor_measure_lock)) + return -EBUSY; + + dim_measure_task_measure(DIM_MEASURE, &dim_monitor_handle); + mutex_unlock(&dim_monitor_measure_lock); + return 0; +} + +int dim_monitor_baseline_blocking(void) +{ + if (!mutex_trylock(&dim_monitor_measure_lock)) + return -EBUSY; + + dim_measure_task_measure(DIM_BASELINE, &dim_monitor_handle); + mutex_unlock(&dim_monitor_measure_lock); + return 0; +} + +static int baseline_prepare(struct dim_measure *m) +{ + dim_baseline_destroy_tree(&m->static_baseline); + dim_baseline_destroy_tree(&m->dynamic_baseline); + dim_measure_log_refresh(&m->log); + return 0; +} + +int dim_monitor_measure_init(struct dim_measure_cfg *cfg) +{ + int ret = 0; + + /* init the measurement handle */ + ret = dim_measure_init(&dim_monitor_handle, cfg); + if (ret < 0) { + dim_err("failed to init measurement handle\n"); + return ret; + } + + /* set the baseline prepare function */ + dim_monitor_handle.baseline_prepare = baseline_prepare; + + /* register all measurement tasks */ + ret = dim_measure_tasks_register(&dim_monitor_handle, dim_core_tasks, + DIM_ARRAY_LEN(dim_core_tasks)); + if (ret < 0) { + dim_err("failed to register measure tasks: %d\n", ret); + goto err; + } + + return 0; +err: + dim_measure_destroy(&dim_monitor_handle); + return ret; +} + +void dim_monitor_measure_destroy(void) +{ + mutex_lock(&dim_monitor_measure_lock); + dim_measure_destroy(&dim_monitor_handle); + mutex_unlock(&dim_monitor_measure_lock); +} diff --git a/security/integrity/dim/monitor/dim_monitor_symbol.c b/security/integrity/dim/monitor/dim_monitor_symbol.c new file mode 100644 index 000000000000..4054099bdc69 --- /dev/null +++ b/security/integrity/dim/monitor/dim_monitor_symbol.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include + +#include "dim_symbol.h" + +#include "dim_monitor.h" +#include "dim_monitor_symbol.h" + +struct dim_monitor_kallsyms dim_monitor_kernel_symbol; + +int dim_monitor_kallsyms_init(void) +{ + struct dim_monitor_kallsyms *k = &dim_monitor_kernel_symbol; + DIM_SYMBOL_LOOKUP_FUNC dim_kallsyms_lookup_name = NULL; + + dim_kallsyms_lookup_name = dim_get_symbol_lookup_func(); + if (dim_kallsyms_lookup_name == NULL) { + dim_err("fail to get symbol_lookup_func\n"); + return -EINVAL; + } + + memset(k, 0, sizeof(struct dim_monitor_kallsyms)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + k->find_module = (DIM_FIND_MODULE) + dim_kallsyms_lookup_name("find_module"); + return k->find_module == NULL ? -ENOENT : 0; +#else + return 0; +#endif +} diff --git a/security/integrity/dim/monitor/dim_monitor_symbol.h b/security/integrity/dim/monitor/dim_monitor_symbol.h new file mode 100644 index 000000000000..1705690a42c7 --- /dev/null +++ b/security/integrity/dim/monitor/dim_monitor_symbol.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_MONITOR_SYMBOL_H +#define __DIM_MONITOR_SYMBOL_H + +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) +typedef struct module *(*DIM_FIND_MODULE)(const char *); +#endif + +struct dim_monitor_kallsyms { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + DIM_FIND_MODULE find_module; +#endif +}; + +extern struct dim_monitor_kallsyms dim_monitor_kernel_symbol; + +int dim_monitor_kallsyms_init(void); + +#endif diff --git a/security/integrity/dim/monitor/measure_task/dim_monitor_measure_data.c b/security/integrity/dim/monitor/measure_task/dim_monitor_measure_data.c new file mode 100644 index 000000000000..029840ca431e --- /dev/null +++ b/security/integrity/dim/monitor/measure_task/dim_monitor_measure_data.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include "dim_measure.h" +#include "dim_core_mem_pool.h" + +#include "dim_monitor.h" + +#include "dim_monitor_measure_task.h" + +static void calculate_chunk(struct gen_pool *pool, + struct gen_pool_chunk *chunk, + void *data) +{ + struct shash_desc *shash = (struct shash_desc *)data; + + if (chunk == NULL || shash == NULL) + return; + + (void)crypto_shash_update(shash, (char *)chunk->start_addr, + chunk->end_addr - chunk->start_addr); +} + +static int module_text_measure(int mode, struct dim_measure *m) +{ + int ret = 0; + int log_flag = LOG_DYNAMIC_BASELINE; + struct dim_digest digest = { + .algo = m->hash.algo, + }; + + SHASH_DESC_ON_STACK(shash, m->hash.tfm); + shash->tfm = m->hash.tfm; + + ret = crypto_shash_init(shash); + if (ret < 0) + return ret; + + dim_mem_pool_walk_chunk(calculate_chunk, shash); + ret = crypto_shash_final(shash, digest.data); + if (ret < 0) + return ret; + + ret = dim_measure_process_dynamic_result(m, mode, DIM_CORE_DATA, + &digest, &log_flag); + if (ret < 0) + dim_err("failed to check dim_core data digest: %d\n", ret); + + return 0; +} + +struct dim_measure_task dim_monitor_measure_data = { + .name = "dim_monitor_measure_data", + .init = NULL, + .destroy = NULL, + .measure = module_text_measure, +}; diff --git a/security/integrity/dim/monitor/measure_task/dim_monitor_measure_task.h b/security/integrity/dim/monitor/measure_task/dim_monitor_measure_task.h new file mode 100644 index 000000000000..326fd4862a54 --- /dev/null +++ b/security/integrity/dim/monitor/measure_task/dim_monitor_measure_task.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#ifndef __DIM_MONITOR_MEASURE_TASK_H +#define __DIM_MONITOR_MEASURE_TASK_H + +extern struct dim_measure_task dim_monitor_measure_data; +extern struct dim_measure_task dim_monitor_measure_text; + +#endif diff --git a/security/integrity/dim/monitor/measure_task/dim_monitor_measure_text.c b/security/integrity/dim/monitor/measure_task/dim_monitor_measure_text.c new file mode 100644 index 000000000000..bd1b65ca51c3 --- /dev/null +++ b/security/integrity/dim/monitor/measure_task/dim_monitor_measure_text.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + */ + +#include +#include + +#include "dim_measure.h" + +#include "dim_monitor.h" +#include "dim_monitor_symbol.h" +#include "dim_monitor_measure_task.h" + +static int module_text_measure(int mode, struct dim_measure *m) +{ + int ret = 0; + int log_flag = LOG_DYNAMIC_BASELINE; + struct module *mod = NULL; + struct dim_digest digest = { 0 }; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + mutex_lock(&module_mutex); + mod = find_module(DIM_CORE); +#else + rcu_read_lock_sched(); + mod = dim_monitor_kernel_symbol.find_module(DIM_CORE); +#endif + if (mod == NULL || mod->state != MODULE_STATE_LIVE || + !try_module_get(mod)) + mod = NULL; /* target module not exist or is not alive */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + mutex_unlock(&module_mutex); +#else + rcu_read_unlock_sched(); +#endif + if (mod == NULL) + return -ENOENT; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) + ret = dim_hash_calculate(mod->core_layout.base, + mod->core_layout.text_size, + &m->hash, &digest); +#else + ret = dim_hash_calculate(mod->mem[MOD_TEXT].base, + mod->mem[MOD_TEXT].size, + &m->hash, &digest); +#endif + module_put(mod); + if (ret < 0) + return ret; + + ret = dim_measure_process_dynamic_result(m, mode, DIM_CORE_TEXT, + &digest, &log_flag); + if (ret < 0) + dim_err("failed to check dim_core text digest: %d\n", ret); + + return 0; +} + +struct dim_measure_task dim_monitor_measure_text = { + .name = "dim_monitor_measure_text", + .init = NULL, + .destroy = NULL, + .measure = module_text_measure, +};