diff --git a/LICENSE b/LICENSE index 9b69d208ea3311b95fca9bb4d4d7fbca74e007ad..62471c6ba7452c23970246acb734d955e1d1e717 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ (1) The directories below are licensed under GPL-2.0-or-later. ./newip/ + ./xpm/ As for the specific use of the licenses, please refer to the relevant description in the documents. diff --git a/OAT.xml b/OAT.xml index 43727b726c097d12c9648acde4c5637e64875ca2..6056c1f730a3dae9ec3a5aa1bb50e1b7b5b2acb6 100644 --- a/OAT.xml +++ b/OAT.xml @@ -58,7 +58,9 @@ Note:If the text contains special characters, please escape them according to th + + @@ -75,6 +77,7 @@ Note:If the text contains special characters, please escape them according to th + diff --git a/xpm/Kconfig b/xpm/Kconfig new file mode 100755 index 0000000000000000000000000000000000000000..c83c625d785cae81fe382bf001bda88c1c712d77 --- /dev/null +++ b/xpm/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2023 Huawei Device Co., Ltd. +# +# Config for the excutable permission manager +# + +menu "Executable permission manager" + +config SECURITY_XPM + def_bool $(success, $(srctree)/scripts/ohos-check-dir.sh $(srctree)/security/xpm) + depends on 64BIT + help + The Executable Permission Manager(XPM) control process execution + by inserting control poliy into the security hook list, such as execv, + mmap and etc. It can control not to execute an illegal signature + process. + +config SECURITY_XPM_DEBUG + bool "Enables excutable permission manager debug mode" + depends on SECURITY_XPM + default n + help + This option should only be enabled for debug test which can enable + some debug interfaces to obtain detailed information. +endmenu +# a blank line must be existed \ No newline at end of file diff --git a/xpm/Makefile b/xpm/Makefile new file mode 100755 index 0000000000000000000000000000000000000000..55e72cebad332e0d05a3920abff1d50667a13d41 --- /dev/null +++ b/xpm/Makefile @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2023 Huawei Device Co., Ltd. +# +# Makefile for the ecutable permission manager module +# + +obj-$(CONFIG_SECURITY_XPM) += \ + core/xpm_module.o \ + core/xpm_misc.o \ + core/xpm_hck.o \ + core/xpm_report.o \ + validator/elf_code_segment_info.o \ + validator/exec_signature_info.o + +obj-$(CONFIG_SECURITY_XPM_DEBUG) += \ + core/xpm_debugfs.o + +ccflags-$(CONFIG_SECURITY_XPM) += \ + -I$(srctree)/security/xpm/include \ + -I$(srctree)/security/selinux/include \ + -I$(srctree)/security/selinux + +$(addprefix $(obj)/,$(obj-y)): $(obj)/flask.h + +quiet_cmd_flask = GEN $(obj)/flask.h $(obj)/av_permissions.h + cmd_flask = scripts/selinux/genheaders/genheaders $(obj)/flask.h $(obj)/av_permissions.h + +targets += flask.h av_permissions.h +$(obj)/flask.h: $(srctree)/security/selinux/include/classmap.h FORCE + $(call if_changed,flask) diff --git a/xpm/README_zh.md b/xpm/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..8215bd610a39048fe640c1aba91fed56d8267a52 --- /dev/null +++ b/xpm/README_zh.md @@ -0,0 +1,78 @@ +## 背景 + +当前许多应用滥用热更新机制以绕过应用市场审核,在端侧实现包括但不限于窃取用户数据、弹窗广告、静默安装应用、挖矿等恶意行为。这类恶意行为,极大降低了用户产品使用体验,并最终导致大量投诉,引发舆情公关危机。 + +为从源头上堵住恶意应用作恶途径,提高平台的核心竞争,OH有必要构建强制代码签名机制,具体措施包括: + +1. 应用市场对通过安全审核的代码签名 +2. 端侧强制验证代码签名,拒绝执行不合法的代码 +3. 端侧提供代码完整性保护 ,防止代码内容在运行时被恶意篡改 + +## XPM(eXecutable Permission Manager)模块 + +XPM模块通过扩展内核能力,为应用的二进制和abc代码提供运行时的管控,强制仅包括合法签名的代码允许分配可执行权限(二进制)/通过方舟运行时检查(abc),相关代码才能被执行(abc代码为解释执行)。XPM主要通过三个功能实现上述能力,具体内容如下: + +### 1.执行权限检查 + +通过hook特定系统调用函数,XPM在代码内存映射操作前,强制验证文件的代码签名合法性,拒绝将未包含合法签名的文件映射到可执行内存(见下图一)。此外,XPM还会通过解析文件头,获取文件的代码段信息,限制应用将通过签名验签的文件中数据内容映射到可执行内存。 + +![执行权限检查](figures/xpm_check.png) + +### 2.XPM验签地址区 + +在HAP应用被拉起时,XPM会在进程的地址空间内保留一段验签地址范围,任何尝试被映射到该地址范围的文件都会被校验代码签名,无签名或签名不合法的文件映射操作将会失败(如下图二)。此外,运行时虚拟机在解释执行字节码时通过检查相应内容是否处于该验签地址区域判断代码是否合法。 + +![验签地址区](figures/abc_check.png) + +### 3.代码完整性保护 + +为阻止应用在运行时通过映射代码到可写内存区,篡改已完成校验的代码内容,XPM基于写污点标记的代码执行权限冲突检查,为代码提供运行时的完整性保护。 + +![Integrity](figures/integrity_check.png) + +新增两个页标识,只读代码页被标记为readonly,任何被映射到写内存区域的页都会被标记为writetainted,并且整个生命周期都不会被消除,直到该页被回收。当writetained的页被映射为只读代码(需标记为readonly),或者readonly的页被映射到可写区页(需标记为writetainted),将产生xpm标识冲突,此时将阻止页映射,禁止该访问发生。 + +## 目录 + +XPM执行权限管控的主要代码目录结构如下: + +``` +# 代码路径 /kernel/linux/common_modules/xpm +├── include # XPM 头文件 +├── src +│ ├── core # XPM 管控代码 +│ └── validator # XPM 代码签名检查和代码解析模块 +├── figures # ReadMe 内嵌图例 +├── Konfig +├── Makefile +``` + +## XPM配置指导 + +1. XPM使能 + `CONFIG_SECURITY_XPM=y` + +2. XPM禁用 + `CONFIG_SECURITY_XPM=n` + +3. XPM调试信息 + `CONFIG_SECURITY_XPM_DEBUG=y` + +## 管控规则说明 + +针对当前不同应用的运行需求,通过selinux对相应的应用做标签化处理(exec_no_sign & execmem_anon),实施不同的管控策略,具体如下: + +1. 普通应用类:强制检查二进制可执行文件和abc字节码的合法代码签名,限制申请匿名可执行内存 +2. webview类:强制二进制可执行文件和abc字节码的合法代码签名,不限制匿名可执行内存的申请,允许拥有JIT能力 +3. 调测类:二进制可执行文件和abc字节码不包含合法代码签名,不限制匿名可执行内存的申请,允许拥有JIT能力 +4. 沙箱类:不限制二进制可执行文件和abc字节码包含合法代码签名,不限制匿名可执行内存的申请,允许拥有JIT能力 + +## 相关仓 + +[内核子系统](https://gitee.com/openharmony/docs/blob/master/zh-cn/readme/%E5%86%85%E6%A0%B8%E5%AD%90%E7%B3%BB%E7%BB%9F.md) + +[kernel_linux_5.10](https://gitee.com/openharmony/kernel_linux_5.10) + +[kernel_linux_config](https://gitee.com/openharmony/kernel_linux_config) + +[kernel_linux_build](https://gitee.com/openharmony/kernel_linux_build) diff --git a/xpm/apply_xpm.sh b/xpm/apply_xpm.sh new file mode 100755 index 0000000000000000000000000000000000000000..e8947a93534ad7d7b6734d7dcdb44766b8700f72 --- /dev/null +++ b/xpm/apply_xpm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022 Huawei Device Co., Ltd. +# + +set -e + +OHOS_SOURCE_ROOT=$1 +KERNEL_BUILD_ROOT=$2 +PRODUCT_NAME=$3 +KERNEL_VERSION=$4 +XPM_SOURCE_ROOT=$OHOS_SOURCE_ROOT/kernel/linux/common_modules/xpm + +function main() +{ + pushd . + + if [ ! -d " $KERNEL_BUILD_ROOT/security/xpm" ]; then + mkdir $KERNEL_BUILD_ROOT/security/xpm + fi + + cd $KERNEL_BUILD_ROOT/security/xpm + ln -s -f $(realpath --relative-to=$KERNEL_BUILD_ROOT/security/xpm/ $XPM_SOURCE_ROOT)/* ./ + + popd +} + +main diff --git a/xpm/core/xpm_debugfs.c b/xpm/core/xpm_debugfs.c new file mode 100755 index 0000000000000000000000000000000000000000..7902b5074cdea72b642ab39274ee2490e664e0f7 --- /dev/null +++ b/xpm/core/xpm_debugfs.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include "xpm_log.h" +#include "xpm_debugfs.h" + +extern uint8_t xpm_mode; +static struct dentry *xpm_dir; + +int xpm_debugfs_init(void) +{ + xpm_dir = debugfs_create_dir("xpm", NULL); + if (!xpm_dir) { + xpm_log_error("create xpm debugfs dir failed"); + return -EINVAL; + } + + debugfs_create_u8("xpm_mode", 0600, xpm_dir, &xpm_mode); + + return 0; +} + +void xpm_debugfs_exit(void) +{ + debugfs_remove_recursive(xpm_dir); +} diff --git a/xpm/core/xpm_hck.c b/xpm/core/xpm_hck.c new file mode 100644 index 0000000000000000000000000000000000000000..4c3e98af915c5ad167bae4c73fbc86712c61d69d --- /dev/null +++ b/xpm/core/xpm_hck.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "avc.h" +#include "objsec.h" +#include "xpm_hck.h" +#include "xpm_log.h" +#include "xpm_report.h" +#include "exec_signature_info.h" + +uint8_t xpm_mode = XPM_PERMISSIVE_MODE; + +static int xpm_value(int value) +{ + return (xpm_mode == XPM_PERMISSIVE_MODE ? 0 : value); +} + +static bool xpm_is_anonymous_vma(struct vm_area_struct *vma) +{ + return vma_is_anonymous(vma) || vma_is_shmem(vma); +} + +static int xpm_avc_has_perm(u16 tclass, u32 requested) +{ + struct av_decision avd; + u32 sid = current_sid(); + int rc, rc2; + + rc = avc_has_perm_noaudit(&selinux_state, sid, sid, tclass, requested, + AVC_STRICT, &avd); + rc2 = avc_audit(&selinux_state, sid, sid, tclass, requested, &avd, rc, + NULL, AVC_STRICT); + if (rc2) + return rc2; + + return rc; +} + +static int xpm_validate_signature(struct vm_area_struct *vma, + struct exec_file_signature_info *info) +{ + if (IS_ERR_OR_NULL(info)) + return xpm_avc_has_perm(SECCLASS_XPM, XPM__EXEC_NO_SIGN); + + return 0; +} + +static int xpm_check_code_segment(bool is_exec, struct vm_area_struct *vma, + struct exec_file_signature_info *info) +{ + int i; + unsigned long vm_addr_start, vm_addr_end; + unsigned long seg_addr_start, seg_addr_end; + struct exec_segment_info *segments = info->code_segments; + + if (!is_exec) + return 0; + + if (!segments) { + xpm_log_error("code segments is NULL"); + return -EINVAL; + } + + vm_addr_start = vma->vm_pgoff << PAGE_SHIFT; + vm_addr_end = vm_addr_start + (vma->vm_end - vma->vm_start); + + for (i = 0; i < info->code_segment_count; i++) { + seg_addr_start = ALIGN_DOWN(segments[i].file_offset, PAGE_SIZE); + seg_addr_end = PAGE_ALIGN(segments[i].file_offset + + segments[i].size); + + if ((vm_addr_start >= seg_addr_start) && + (vm_addr_end <= seg_addr_end)) + return 0; + } + + return xpm_avc_has_perm(SECCLASS_XPM, XPM__EXEC_NO_SIGN); +} + +static int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) +{ + int ret; + struct exec_file_signature_info *info = NULL; + bool is_exec = !xpm_is_anonymous_vma(vma) && (prot & PROT_EXEC); + + /* vma is non-executable or mmap in xpm region just return */ + if (!((vma->vm_flags & VM_XPM) || is_exec)) + return 0; + + /* validate signature when vma is mmap in xpm region or executable */ + ret = get_exec_file_signature_info(vma->vm_file, is_exec, &info); + if (ret) { + xpm_log_error("xpm get executable file signature info failed"); + report_file_event(TYPE_FORMAT_UNDEF, vma->vm_file); + return ret; + } + + ret = xpm_validate_signature(vma, info); + if (ret) { + xpm_log_error("xpm validate signature info failed"); + report_mmap_event(TYPE_SIGN_INVALID, vma, is_exec, prot); + goto exit; + } + + ret = xpm_check_code_segment(is_exec, vma, info); + if (ret) { + xpm_log_error("xpm check executable vma mmap code segment failed"); + report_mmap_event(TYPE_DATA_MMAP_CODE, vma, is_exec, prot); + goto exit; + } +exit: + put_exec_file_signature_info(info); + return ret; +} + +static int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) +{ + int ret; + bool is_anon = xpm_is_anonymous_vma(vma); + + if ((vma->vm_flags & VM_XPM) && (is_anon || (prot & PROT_WRITE) || + (prot & PROT_EXEC))) { + xpm_log_error("xpm region mmap not allow anonymous/exec/write permission"); + return -EPERM; + } + + /* anonymous executable permission need controled by selinux */ + if (is_anon && (prot & PROT_EXEC)) { + ret = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXEC_ANON_MEM); + if (ret) { + xpm_log_error("anonymous mmap not allow exec permission"); + report_mmap_event(TYPE_ANON_EXEC, vma, TYPE_ANON, prot); + return -EPERM; + } + } + + if (!is_anon && (prot & PROT_WRITE) && (prot & PROT_EXEC)) { + xpm_log_error("file mmap not allow write & exec permission"); + return -EPERM; + } + + return 0; +} + +static int xpm_common_check(struct vm_area_struct *vma, unsigned long prot) +{ + int ret; + + do { + ret = xpm_check_prot(vma, prot); + if (ret) + break; + + ret = xpm_check_signature(vma, prot); + } while (0); + + return xpm_value(ret); +} + +static int xpm_mmap_check(struct vm_area_struct *vma) +{ + return xpm_common_check(vma, vma->vm_flags); +} + +static int xpm_mprotect_check(struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + (void)reqprot; + + return xpm_common_check(vma, prot); +} + +void xpm_delete_cache_node(struct inode *file_node) +{ + delete_exec_file_signature_info(file_node); +} + +static void xpm_region_outer(unsigned long addr_start, unsigned long addr_end, + unsigned long flags, bool *ret) +{ + struct mm_struct *mm = current->mm; + + if (!mm) + return; + + /* Already in xpm region, just return without judge */ + if (flags & VM_UNMAPPED_AREA_XPM) + return; + + *ret = ((addr_start >= mm->xpm_region.addr_end) || + (addr_end <= mm->xpm_region.addr_start)); +} + +void xpm_get_unmapped_area(unsigned long addr, unsigned long len, + unsigned long map_flags, unsigned long unmapped_flags, + unsigned long *ret) +{ + struct vm_unmapped_area_info info; + struct mm_struct *mm = current->mm; + + if (!mm) + return; + + if ((map_flags & MAP_FIXED) && !(addr >= mm->xpm_region.addr_end || + addr + len <= mm->xpm_region.addr_start)) { + xpm_log_error("xpm region not allow mmap with MAP_FIXED"); + *ret = -EFAULT; + return; + } + + if (map_flags & MAP_XPM) { + if (addr) { + xpm_log_error("xpm region not allow specify addr"); + *ret = -EPERM; + return; + } + + info.flags = VM_UNMAPPED_AREA_XPM | unmapped_flags; + info.length = len; + info.low_limit = mm->xpm_region.addr_start; + info.high_limit = mm->xpm_region.addr_end; + info.align_mask = 0; + info.align_offset = 0; + + *ret = vm_unmapped_area(&info); + } +} + +/* + * A xpm readonly region is an area where any page mapped + * will be marked with XPMReadonly. + * Return 1 if a region is readonly, otherwise, return 0. + */ +static bool is_xpm_readonly_region(struct vm_area_struct *vma) +{ + /* 1. xpm region */ + if (vma->vm_flags & VM_XPM) + return true; + + /* 2. !anonymous && executable */ + if (!xpm_is_anonymous_vma(vma) && (vma->vm_flags & VM_EXEC)) + return true; + + return false; +} + +void xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page, vm_fault_t *ret) +{ + if (!page) + return; + + /* integrity violation: write a readonly page */ + if ((vflags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && + PageXPMReadonly(page)) { + report_integrity_event(TYPE_INTEGRITY_RO, vma, page); + *ret = xpm_value(VM_FAULT_SIGSEGV); + return; + } + + /* integrity violation: execute a writetained page */ + if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { + report_integrity_event(TYPE_INTEGRITY_WT, vma, page); + *ret = xpm_value(VM_FAULT_SIGSEGV); + return; + } +} + +void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, + struct page *page) +{ + /* set writetainted only if a real write occurred */ + if ((vflags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && + !PageXPMWritetainted(page)) { + SetPageXPMWritetainted(page); + return; + } + + /* set xpm readonly flag */ + if (is_xpm_readonly_region(vma) && !PageXPMReadonly(page)) + SetPageXPMReadonly(page); +} + +void xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page, vm_fault_t *ret) +{ + if (!page) + return; + + xpm_integrity_check(vma, vflags, addr, page, ret); + if (!*ret) + xpm_integrity_update(vma, vflags, page); +} + +/* + * check the integrity of these two pages, return true if equal, + * otherwise false + */ +void xpm_integrity_equal(struct page *page, struct page *kpage, bool *ret) +{ + if (!page || !kpage) + return; + + *ret = ((PageXPMWritetainted(page) == PageXPMWritetainted(kpage)) && + (PageXPMReadonly(page) == PageXPMReadonly(kpage))); +} + +static struct security_hook_list xpm_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(mmap_region, xpm_mmap_check), + LSM_HOOK_INIT(file_mprotect, xpm_mprotect_check), +}; + +void xpm_register_xpm_hooks(void) +{ + security_add_hooks(xpm_hooks, ARRAY_SIZE(xpm_hooks), "xpm"); +} + +void xpm_register_hck_hooks(void) +{ + REGISTER_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, + xpm_delete_cache_node); + + REGISTER_HCK_LITE_HOOK(xpm_region_outer_lhck, xpm_region_outer); + REGISTER_HCK_LITE_HOOK(xpm_get_unmapped_area_lhck, + xpm_get_unmapped_area); + + /* xpm integrity*/ + REGISTER_HCK_LITE_HOOK(xpm_integrity_equal_lhck, xpm_integrity_equal); + REGISTER_HCK_LITE_HOOK(xpm_integrity_check_lhck, xpm_integrity_check); + REGISTER_HCK_LITE_HOOK(xpm_integrity_update_lhck, xpm_integrity_update); + REGISTER_HCK_LITE_HOOK(xpm_integrity_validate_lhck, + xpm_integrity_validate); +} diff --git a/xpm/core/xpm_misc.c b/xpm/core/xpm_misc.c new file mode 100755 index 0000000000000000000000000000000000000000..73bf05eb6e2a07669367e4ccfbe363fbd5b85c75 --- /dev/null +++ b/xpm/core/xpm_misc.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "xpm_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include "xpm_log.h" +#include "xpm_report.h" + +#define XPM_SET_REGION _IOW('x', 0x01, struct xpm_region_info) + +static int xpm_set_region(unsigned long addr_base, unsigned long length) +{ + int ret = 0; + unsigned long addr; + struct mm_struct *mm = current->mm; + + if (!mm) + return -EINVAL; + + if (mmap_write_lock_killable(mm)) + return -EINTR; + + if ((mm->xpm_region.addr_start != 0) || + (mm->xpm_region.addr_end != 0)) { + xpm_log_info("xpm region has been set"); + goto exit; + } + + addr = get_unmapped_area(NULL, addr_base, length, 0, 0); + if (IS_ERR_VALUE(addr) || (ULONG_MAX - addr_base < length)) { + xpm_log_error("xpm get unmmaped area failed"); + ret = -EINVAL; + goto exit; + } + + mm->xpm_region.addr_start = addr; + mm->xpm_region.addr_end = addr + length; +exit: + mmap_write_unlock(mm); + return ret; +} + +static long xpm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret; + struct xpm_region_info info = {0}; + + if (unlikely(copy_from_user(&info, (void __user *)(uintptr_t)arg, + sizeof(struct xpm_region_info)))) + return -EFAULT; + + switch (cmd) { + case XPM_SET_REGION: + ret = xpm_set_region(info.addr_base, info.length); + break; + default: + xpm_log_error("xpm ioctl cmd error, cmd = %d", cmd); + ret = -EINVAL; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long xpm_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return xpm_ioctl(file, cmd, (uintptr_t)compat_ptr(arg)); +} +#endif + +static int xpm_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int xpm_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations xpm_fops = { + .owner = THIS_MODULE, + .open = xpm_open, + .release = xpm_release, + .unlocked_ioctl = xpm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = xpm_compat_ioctl, +#endif +}; + +static struct miscdevice xpm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "xpm", + .fops = &xpm_fops, +}; + +int xpm_register_misc_device(void) +{ + return misc_register(&xpm_misc); +} + +void xpm_deregister_misc_device(void) +{ + misc_deregister(&xpm_misc); +} diff --git a/xpm/core/xpm_module.c b/xpm/core/xpm_module.c new file mode 100755 index 0000000000000000000000000000000000000000..498932c2dc29f2b73f749b62f5ab1094b4ffe32e --- /dev/null +++ b/xpm/core/xpm_module.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include "xpm_log.h" +#include "xpm_hck.h" +#include "xpm_misc.h" +#include "xpm_report.h" +#include "xpm_debugfs.h" + +static int __init xpm_module_init(void) +{ + int ret; + + ret = xpm_register_misc_device(); + if (ret) { + xpm_log_error("xpm register misc device failed, ret = %d", ret); + report_init_event(TYPE_DEVICEFS_UNINIT); + return ret; + } + + ret = xpm_debugfs_init(); + if (ret) { + xpm_log_error("xpm init debugfs failed, ret = %d", ret); + xpm_deregister_misc_device(); + report_init_event(TYPE_DEBUGFS_UNINIT); + return ret; + } + + xpm_register_xpm_hooks(); + xpm_register_hck_hooks(); + + xpm_log_info("xpm module init success"); + return 0; +} + +static void __exit xpm_module_exit(void) +{ + xpm_deregister_misc_device(); + xpm_debugfs_exit(); + xpm_log_info("xpm module exit success"); +} + +module_init(xpm_module_init); +module_exit(xpm_module_exit); +MODULE_LICENSE("GPL"); diff --git a/xpm/core/xpm_report.c b/xpm/core/xpm_report.c new file mode 100644 index 0000000000000000000000000000000000000000..1e2bfc07d1eed759ab63ddc3eb95ca10a125f15d --- /dev/null +++ b/xpm/core/xpm_report.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_HW_KERNEL_SG +#include +#endif +#include "xpm_log.h" +#include "xpm_report.h" + +#ifndef CONFIG_HW_KERNEL_SG +typedef struct { + unsigned long event_id; + unsigned int version; + unsigned int content_len; + char content[0]; +} event_info; + +unsigned int report_security_info(const event_info *event) +{ + xpm_log_info("%d: %s", event->event_id, event->content); + return 0; +} +#endif + +static char *xpm_get_filename(struct xpm_event_param *param, char *buf, int len) +{ + char *filename = NULL; + struct file *file = NULL; + + if (param->file) + file = param->file; + else if (param->vma && param->vma->vm_file) + file = param->vma->vm_file; + else + return NULL; + + filename = d_absolute_path(&file->f_path, buf, len); + if (IS_ERR(filename)) { + xpm_log_error("xpm get absolute path failed"); + return NULL; + } + + return filename; +} + +static int set_init_content(struct xpm_event_param *param, + uint8_t *content, uint32_t content_len) +{ + int len; + + len = snprintf(content, content_len, + "{ "JSTR_PAIR(event_type, %s)", "JVAL_PAIR(timestamp, %llu)" }", + param->event_type, param->timestamp); + + if (len < 0 || len > content_len) { + xpm_log_error("snprintf init content failed"); + return -EINVAL; + } + + return 0; +} + +#define PROT_MASK (PROT_EXEC | PROT_READ | PROT_WRITE) +const static char *code_type[] = { + [TYPE_ABC] = "ABC", + [TYPE_ELF] = "ELF", + [TYPE_ANON] = "ANON" +}; +static int set_mmap_content(struct xpm_event_param *param, uint8_t *content, + uint32_t content_len) +{ + int len; + + if (!param->vma) { + xpm_log_error("input vma is NULL"); + return -EINVAL; + } + + len = snprintf(content, content_len, + "{ "JSTR_PAIR(event_type, %s)", "JVAL_PAIR(timestamp, %llu)", " + JVAL_PAIR(pid, %u)", "JSTR_PAIR(filename, %s)", " + JSTR_PAIR(code_type, %s)", "JVAL_PAIR(prot, %lu)"," + JVAL_PAIR(pgoff, %lu)", "JVAL_PAIR(size, %lu)" }", + param->event_type, param->timestamp, param->pid, + param->filename ? param->filename : "", + code_type[param->code], param->prot & PROT_MASK, + param->vma->vm_pgoff, + param->vma->vm_end - param->vma->vm_start); + + if (len < 0 || len > content_len) { + xpm_log_error("snprintf code mmap content failed"); + return -EINVAL; + } + + return 0; +} + +static int set_file_content(struct xpm_event_param *param, + uint8_t *content, uint32_t content_len) +{ + int len; + + len = snprintf(content, content_len, + "{ "JSTR_PAIR(event_type, %s)", "JVAL_PAIR(timestamp, %llu)", " + JVAL_PAIR(pid, %u)", "JSTR_PAIR(filename, %s)" }", + param->event_type, param->timestamp, param->pid, + param->filename ? param->filename : ""); + + if (len < 0 || len > content_len) { + xpm_log_error("snprintf file format content failed"); + return -EINVAL; + } + + return 0; +} + +static int set_integrity_content(struct xpm_event_param *param, + uint8_t *content, uint32_t content_len) +{ + int len; + char *page_type; + + if (!param->vma || !param->page) { + xpm_log_error("input vma or page is NULL"); + return -EINVAL; + } + + page_type = PageKsm(param->page) ? + "[ksm]" : PageAnon(param->page) ? "[anon]" : "[file]"; + + len = snprintf(content, content_len, + "{ " JSTR_PAIR(event_type, %s)", "JVAL_PAIR(timestamp, %llu)", " + JVAL_PAIR(pid, %u)","JSTR_PAIR(page_type, %s)", " + JSTR_PAIR(filename, %s)", "JVAL_PAIR(page_index, %lu)"," + JVAL_PAIR(page_prot, %lu)" }", + param->event_type, param->timestamp, param->pid, page_type, + param->filename ? param->filename : "", param->page->index, + param->vma->vm_page_prot.pgprot & PROT_MASK); + + if (len < 0 || len > content_len) { + xpm_log_error("snprintf init integrity failed"); + return -EINVAL; + } + + return 0; +} + +static const struct xpm_event_info xpm_event[] = { + [TYPE_DEVICEFS_UNINIT] = { "devicefs uninitialized", + EVENT_INIT, set_init_content }, + [TYPE_DEBUGFS_UNINIT] = { "debugfs uninitialized", + EVENT_INIT, set_init_content }, + [TYPE_FORMAT_UNDEF] = { "unkown file format", + EVENT_FILE, set_file_content }, + [TYPE_ANON_EXEC] = { "anon executed", + EVENT_MMAP, set_file_content }, + [TYPE_SIGN_INVALID] = { "invalid signature", + EVENT_MMAP, set_mmap_content }, + [TYPE_DATA_MMAP_CODE] = { "data mmap code", + EVENT_MMAP, set_mmap_content }, + [TYPE_INTEGRITY_RO] = { "code tampered", + EVENT_INTEGRITY, set_integrity_content }, + [TYPE_INTEGRITY_WT] = { "data executed", + EVENT_INTEGRITY, set_integrity_content }, +}; + +static int report_event_inner(enum xpm_event_type type, + struct xpm_event_param *param, event_info *event) +{ + int ret; + + ret = xpm_event[type].set_content(param, event->content, + MAX_CONTENT_LEN); + if (ret) { + xpm_log_error("type [%d] set content failed", type); + return ret; + } + event->content_len = strlen(event->content); + event->event_id = xpm_event[type].event_id; + event->version = XPM_EVENT_VERSION; + + ret = report_security_info(event); + if (ret) { + xpm_log_error("type [%d] report security info failed", type); + return ret; + } + + return 0; +} + +static int xpm_report_event(enum xpm_event_type type, + struct xpm_event_param *param) +{ + int ret; + event_info *sg_event; + char *buf; + + if (!(xpm_event[type].set_content)) { + xpm_log_error("type [%d] set content func invalid", type); + return -EINVAL; + } + + sg_event = kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); + if (!sg_event) { + xpm_log_error("alloc security guard event failed"); + return -ENOMEM; + } + + buf = __getname(); + if (!buf) { + xpm_log_error("alloc file name buf failed"); + kfree(sg_event); + return -ENOMEM; + } + + param->event_type = xpm_event[type].event_type; + param->filename = xpm_get_filename(param, buf, PATH_MAX); + param->timestamp = ktime_get_real_seconds(); + param->pid = current->pid; + + ret = report_event_inner(type, param, sg_event); + + __putname(buf); + kfree(sg_event); + return ret; +} + +void report_init_event(enum xpm_event_type type) +{ + struct xpm_event_param param = {0}; + + xpm_report_ratelimited(xpm_report_event, type, ¶m); +} + +void report_file_event(enum xpm_event_type type, struct file *file) +{ + struct xpm_event_param param = {0}; + + param.file = file; + xpm_report_ratelimited(xpm_report_event, type, ¶m); +} + +void report_mmap_event(enum xpm_event_type type, struct vm_area_struct *vma, + int code, int prot) +{ + struct xpm_event_param param = {0}; + + param.vma = vma; + param.code = code; + param.prot = prot; + xpm_report_ratelimited(xpm_report_event, type, ¶m); +} + +void report_integrity_event(enum xpm_event_type type, + struct vm_area_struct *vma, struct page *page) +{ + struct xpm_event_param param = {0}; + + param.vma = vma; + param.page = page; + xpm_report_ratelimited(xpm_report_event, type, ¶m); +} diff --git a/xpm/figures/abc_check.png b/xpm/figures/abc_check.png new file mode 100644 index 0000000000000000000000000000000000000000..01e2dff3871fb2a6dee309b049e9c0af1aa55fbf Binary files /dev/null and b/xpm/figures/abc_check.png differ diff --git a/xpm/figures/integrity_check.png b/xpm/figures/integrity_check.png new file mode 100644 index 0000000000000000000000000000000000000000..19af000e028f98e7e8b9d82f4e3387beed3dd196 Binary files /dev/null and b/xpm/figures/integrity_check.png differ diff --git a/xpm/figures/xpm_check.png b/xpm/figures/xpm_check.png new file mode 100644 index 0000000000000000000000000000000000000000..e515aced995bdb0d6bf0a706e995398d3ed5aa94 Binary files /dev/null and b/xpm/figures/xpm_check.png differ diff --git a/xpm/include/exec_signature_info.h b/xpm/include/exec_signature_info.h new file mode 100644 index 0000000000000000000000000000000000000000..1f6b63bd4696b10a0b21533f288776a1cc86d231 --- /dev/null +++ b/xpm/include/exec_signature_info.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#ifndef _EXEC_SIGNATURE_INFO_H +#define _EXEC_SIGNATURE_INFO_H + +#include +#include +#include +#include + +struct exec_segment_info { + uintptr_t file_offset; + size_t size; +}; + +#define FILE_SIGNATURE_INVALID 0 +#define FILE_SIGNATURE_FS_VERITY 1 +#define FILE_SIGNATURE_DM_VERITY 2 +#define FILE_SIGNATURE_MASK 0x0000000F +#define FILE_SIGNATURE_DELETE 0x80000000 + +struct exec_file_signature_info { + struct rb_node rb_node; + atomic_t reference; + unsigned int type; + uintptr_t inode; + unsigned int code_segment_count; + struct exec_segment_info *code_segments; +}; + +static inline bool exec_file_signature_is_fs_verity(const struct exec_file_signature_info *signature_info) +{ + return (signature_info->type & FILE_SIGNATURE_MASK) == FILE_SIGNATURE_FS_VERITY; +} + +static inline bool exec_file_signature_is_dm_verity(const struct exec_file_signature_info *signature_info) +{ + return (signature_info->type & FILE_SIGNATURE_MASK) == FILE_SIGNATURE_DM_VERITY; +} + +static inline bool exec_file_signature_is_verity(const struct exec_file_signature_info *signature_info) +{ + return (signature_info->type & FILE_SIGNATURE_MASK) == FILE_SIGNATURE_DM_VERITY || + (signature_info->type & FILE_SIGNATURE_MASK) == FILE_SIGNATURE_FS_VERITY; +} + +static inline bool exec_file_signature_is_delete(const struct exec_file_signature_info *signature_info) +{ + return !!(signature_info->type & FILE_SIGNATURE_DELETE); +} + +int parse_elf_code_segment_info(struct file *file, struct exec_file_signature_info **code_segment_info); +int get_exec_file_signature_info(struct file *file, bool is_exec, struct exec_file_signature_info **info_ptr); +int put_exec_file_signature_info(struct exec_file_signature_info *exec_info); +void delete_exec_file_signature_info(struct inode *file_node); +#endif diff --git a/xpm/include/xpm_debugfs.h b/xpm/include/xpm_debugfs.h new file mode 100755 index 0000000000000000000000000000000000000000..7466f66ce23c3c654e21bbb9be255622b395c552 --- /dev/null +++ b/xpm/include/xpm_debugfs.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _XPM_DEBUGFS_H + +#ifdef CONFIG_SECURITY_XPM_DEBUG +int xpm_debugfs_init(void); +void xpm_debugfs_exit(void); + +#else +static inline int xpm_debugfs_init(void) +{ + return 0; +} + +static inline void xpm_debugfs_exit(void) +{ +} +#endif + +#endif /* _XPM_DEBUGFS_H */ diff --git a/xpm/include/xpm_hck.h b/xpm/include/xpm_hck.h new file mode 100644 index 0000000000000000000000000000000000000000..46bf62d4069e2c54062f0d018b1aa601258660aa --- /dev/null +++ b/xpm/include/xpm_hck.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _XPM_HCK_H +#define _XPM_HCK_H + +#define XPM_PERMISSIVE_MODE 0 +#define XPM_ENFORCE_MODE 1 + +void xpm_register_xpm_hooks(void); + +void xpm_register_hck_hooks(void); + +#endif /* _XPM_HCK_H */ diff --git a/xpm/include/xpm_log.h b/xpm/include/xpm_log.h new file mode 100644 index 0000000000000000000000000000000000000000..5638e750ff2e90fce65322bf6807bc9123291df5 --- /dev/null +++ b/xpm/include/xpm_log.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _XPM_LOG_H +#define _XPM_LOG_H + +#define XPM_CHECK_FAILED (-1024) + +#define XPM_TAG "xpm_kernel" +#define XPM_INFO_TAG "I" +#define XPM_ERROR_TAG "E" +#define XPM_DEBUG_TAG "D" + +#define xpm_log_info(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ + XPM_INFO_TAG, XPM_TAG, __func__, ##args) + +#define xpm_log_error(fmt, args...) pr_err("[%s/%s]%s: " fmt "\n", \ + XPM_ERROR_TAG, XPM_TAG, __func__, ##args) + +#ifdef CONFIG_SECURITY_XPM_DEBUG +#define xpm_log_debug(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ + XPM_DEBUG_TAG, XPM_TAG, __func__, ##args) +#else +#define xpm_log_debug(fmt, args...) no_printk(fmt, ##args) +#endif + +#endif /* _XPM_LOG_H */ diff --git a/xpm/include/xpm_misc.h b/xpm/include/xpm_misc.h new file mode 100755 index 0000000000000000000000000000000000000000..fbdf45e033bbb185414ed661009f9d04e9a5f282 --- /dev/null +++ b/xpm/include/xpm_misc.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _XPM_MISC_H +#define _XPM_MISC_H + +struct xpm_region_info { + unsigned long addr_base; + unsigned long length; +}; + +int xpm_register_misc_device(void); +void xpm_deregister_misc_device(void); + +#endif /* _XPM_MISC_H */ diff --git a/xpm/include/xpm_report.h b/xpm/include/xpm_report.h new file mode 100644 index 0000000000000000000000000000000000000000..0de8269a5fe8dc9baa549fe51037071340f8bf78 --- /dev/null +++ b/xpm/include/xpm_report.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _XPM_REPORT_H +#define _XPM_REPORT_H + +#include +#include + +enum xpm_event_id { + EVENT_INIT = 1011009110, + EVENT_FILE = 1011009111, + EVENT_MMAP = 1011009112, + EVENT_INTEGRITY = 1011009113, +}; + +enum xpm_event_type { + TYPE_DEVICEFS_UNINIT = 0, + TYPE_DEBUGFS_UNINIT, + TYPE_FORMAT_UNDEF, + TYPE_ANON_EXEC, + TYPE_SIGN_INVALID, + TYPE_DATA_MMAP_CODE, + TYPE_INTEGRITY_RO, + TYPE_INTEGRITY_WT, +}; + +enum { + TYPE_ABC, + TYPE_ELF, + TYPE_ANON, +}; + +struct xpm_event_param { + char *event_type; + char *filename; + ktime_t timestamp; + pid_t pid; + + struct vm_area_struct *vma; + struct page *page; + struct file *file; + int code; + unsigned long prot; +}; + +struct xpm_event_info { + char *event_type; + enum xpm_event_id event_id; + int (*set_content)(struct xpm_event_param *param, uint8_t *content, + uint32_t content_len); +}; + +#define MAX_CONTENT_LEN 900 +#define XPM_EVENT_VERSION 0 + +#ifndef CONFIG_SECURITY_XPM_DEBUG + +#define xpm_report_ratelimited(func, fmt, ...) \ + do { \ + static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL, \ + DEFAULT_RATELIMIT_BURST); \ + if (__ratelimit(&_rs)) \ + func(fmt, ##__VA_ARGS__); \ + } while (0) +#else +#define xpm_report_ratelimited(func, fmt, ...) \ + func(fmt, ##__VA_ARGS__); + +#endif + +#define JSTR(val) "\""#val"\"" +#define JVAL_PAIR(val, format) JSTR(val) ": " #format +#define JSTR_PAIR(val, format) JSTR(val) ": " JSTR(format) + +void report_init_event(enum xpm_event_type type); +void report_file_event(enum xpm_event_type type, struct file *file); +void report_mmap_event(enum xpm_event_type type, struct vm_area_struct *vma, + int code, int prot); +void report_integrity_event(enum xpm_event_type type, + struct vm_area_struct *vma, struct page *page); + +#endif /* _XPM_REPORT_H */ diff --git a/xpm/validator/elf_code_segment_info.c b/xpm/validator/elf_code_segment_info.c new file mode 100644 index 0000000000000000000000000000000000000000..223064386dd96314d65e5846e131ae701f386009 --- /dev/null +++ b/xpm/validator/elf_code_segment_info.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#include +#include +#include +#include +#include "exec_signature_info.h" + +#if ELF_EXEC_PAGESIZE > PAGE_SIZE +#define ELF_PHNUM_SIZE ELF_EXEC_PAGESIZE +#else +#define ELF_PHNUM_SIZE PAGE_SIZE +#endif + +#if ELF_PHNUM_SIZE > 65536 +#define ELF_PHNUM_MAX_SIZE 65536 +#else +#define ELF_PHNUM_MAX_SIZE ELF_PHNUM_SIZE +#endif + +struct elf_info { + struct elfhdr elf_ehdr; + uint16_t type; + uint16_t e_phnum; + size_t e_phsize; + uintptr_t e_phoff; +}; + +static int read_elf_info(struct file *file, void *buffer, size_t read_size, loff_t pos) +{ + size_t len; + + len = kernel_read(file, buffer, read_size, &pos); + if (unlikely(len != read_size)) + return -EIO; + + return 0; +} + +static uint64_t elf64_get_value(const struct elfhdr *ehdr, uint64_t value) +{ + if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) + return be64_to_cpu(value); + + if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) + return le64_to_cpu(value); + + return value; +} + +static uint32_t elf32_get_value(const struct elfhdr *ehdr, uint32_t value) +{ + if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) + return be32_to_cpu(value); + + if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) + return le32_to_cpu(value); + + return value; +} + +static uint16_t elf16_get_value(const struct elfhdr *ehdr, uint16_t value) +{ + if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) + return be16_to_cpu(value); + + if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) + return le16_to_cpu(value); + + return value; +} + +static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, + struct elf_info *elf_info) +{ + int i; + int count = 0; + struct elf32_phdr *phdr_info; + uint32_t p_flags; + + for (i = 0; i < elf_info->e_phnum; i++) { + phdr_info = elf_phdr + i; + p_flags = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) + continue; + + count++; + } + return count; +} + +static int get_elf32_code_segment(struct elf32_phdr *elf_phdr, struct elf_info *elf_info, + struct exec_file_signature_info *exec_file_info) +{ + int i; + struct elf32_phdr *phdr_info; + uint32_t p_flags; + uint32_t p_offset; + uint32_t p_filesz; + uint32_t p_memsz; + uint32_t p_addr; + + for (i = 0; i < elf_info->e_phnum; i++) { + phdr_info = elf_phdr + i; + p_flags = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) + continue; + + p_offset = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_offset); + p_filesz = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_filesz); + p_addr = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_paddr); + p_memsz = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_memsz); + if (p_offset + p_filesz < p_offset || p_addr + p_memsz < p_addr) + return -ENOEXEC; + + exec_file_info->code_segments[exec_file_info->code_segment_count].file_offset = p_offset; + exec_file_info->code_segments[exec_file_info->code_segment_count].size = p_filesz; + exec_file_info->code_segment_count++; + } + return 0; +} + +static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, struct elf_info *elf_info) +{ + int i; + int count = 0; + struct elf64_phdr *phdr_info; + uint32_t p_flags; + + for (i = 0; i < elf_info->e_phnum; i++) { + phdr_info = elf_phdr + i; + p_flags = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) + continue; + + count++; + } + return count; +} + +static int get_elf64_code_segment(struct elf64_phdr *elf_phdr, struct elf_info *elf_info, + struct exec_file_signature_info *exec_file_info) +{ + int i; + struct elf64_phdr *phdr_info; + uint32_t p_flags; + uint64_t p_offset; + uint64_t p_filesz; + uint64_t p_memsz; + uint64_t p_addr; + + for (i = 0; i < elf_info->e_phnum; i++) { + phdr_info = elf_phdr + i; + p_flags = elf32_get_value(&elf_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) + continue; + + p_offset = elf64_get_value(&elf_info->elf_ehdr, phdr_info->p_offset); + p_filesz = elf64_get_value(&elf_info->elf_ehdr, phdr_info->p_filesz); + p_addr = elf64_get_value(&elf_info->elf_ehdr, phdr_info->p_paddr); + p_memsz = elf64_get_value(&elf_info->elf_ehdr, phdr_info->p_memsz); + if (p_offset + p_filesz < p_offset || p_addr + p_memsz < p_addr) + return -ENOEXEC; + + exec_file_info->code_segments[exec_file_info->code_segment_count].file_offset = p_offset; + exec_file_info->code_segments[exec_file_info->code_segment_count].size = p_filesz; + exec_file_info->code_segment_count++; + } + return 0; +} + +static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_info *elf_info) +{ + struct elf32_hdr *elf32_ehdr; + struct elf64_hdr *elf64_ehdr; + uint32_t e32_phoff; + uint32_t e32_phsize; + uint64_t e64_phoff; + uint64_t e64_phsize; + uint16_t type; + uint16_t e_ehsize; + struct elfhdr *elf_ehdr = &elf_info->elf_ehdr; + int ret; + + ret = read_elf_info(file, (void *)elf_ehdr, sizeof(struct elfhdr), 0); + if (ret < 0) + return ret; + + if (memcmp(elf_ehdr->e_ident, ELFMAG, SELFMAG) != 0) + return -ENOEXEC; + + type = elf16_get_value(elf_ehdr, elf_ehdr->e_type); + if (type != ET_EXEC && type != ET_DYN) + return -ENOEXEC; + + if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { + elf_info->type = ELFCLASS32; + elf32_ehdr = (struct elf32_hdr *)elf_ehdr; + e_ehsize = elf16_get_value(elf_ehdr, elf32_ehdr->e_ehsize); + if (e_ehsize != sizeof(struct elf32_hdr)) + return -ENOEXEC; + + elf_info->e_phnum = elf16_get_value(elf_ehdr, elf32_ehdr->e_phnum); + e32_phsize = sizeof(struct elf32_phdr) * elf_info->e_phnum; + if (e32_phsize == 0 || e32_phsize > ELF_PHNUM_MAX_SIZE) + return -ENOEXEC; + + e32_phoff = elf32_get_value(elf_ehdr, elf32_ehdr->e_phoff); + if (e32_phoff + e32_phsize < e32_phoff) + return -ENOEXEC; + + elf_info->e_phsize = e32_phsize; + elf_info->e_phoff = e32_phoff; + } else if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS64) { + elf_info->type = ELFCLASS64; + elf64_ehdr = (struct elf64_hdr *)elf_ehdr; + e_ehsize = elf16_get_value(elf_ehdr, elf64_ehdr->e_ehsize); + if (e_ehsize != sizeof(struct elf64_hdr)) + return -ENOEXEC; + + elf_info->e_phnum = elf16_get_value(elf_ehdr, elf64_ehdr->e_phnum); + e64_phsize = sizeof(struct elf64_phdr) * elf_info->e_phnum; + if (e64_phsize == 0 || e64_phsize > ELF_PHNUM_MAX_SIZE) + return -ENOEXEC; + + e64_phoff = elf64_get_value(elf_ehdr, elf64_ehdr->e_phoff); + if (e64_phoff + e64_phsize < e64_phoff) + return -ENOEXEC; + + elf_info->e_phsize = e64_phsize; + elf_info->e_phoff = e64_phoff; + } else + return -ENOEXEC; + + return 0; +} + +static int find_elf_code_segment_info(const char *phdr_info, struct elf_info *elf_info, + struct exec_file_signature_info **file_info) +{ + int ret; + size_t size; + struct exec_file_signature_info *exec_file_info; + int segment_count; + + if (elf_info->type == ELFCLASS32) + segment_count = get_elf32_code_segment_count((struct elf32_phdr *)phdr_info, elf_info); + else + segment_count = get_elf64_code_segment_count((struct elf64_phdr *)phdr_info, elf_info); + + if (segment_count == 0) + return -ENOEXEC; + + size = sizeof(struct exec_file_signature_info) + segment_count * sizeof(struct exec_segment_info); + exec_file_info = kzalloc(size, GFP_KERNEL); + if (exec_file_info == NULL) + return -ENOMEM; + + exec_file_info->code_segments = (struct exec_segment_info *)((char *)exec_file_info + + sizeof(struct exec_file_signature_info)); + if (elf_info->type == ELFCLASS32) + ret = get_elf32_code_segment((struct elf32_phdr *)phdr_info, elf_info, exec_file_info); + else + ret = get_elf64_code_segment((struct elf64_phdr *)phdr_info, elf_info, exec_file_info); + + if (ret < 0) { + kfree(exec_file_info); + return ret; + } + *file_info = exec_file_info; + return 0; +} + +int parse_elf_code_segment_info(struct file *file, + struct exec_file_signature_info **code_segment_info) +{ + const char *phdr_info; + struct elf_info elf_info = {0}; + int ret; + + ret = elf_check_and_get_code_segment_offset(file, &elf_info); + if (ret < 0) + return ret; + + phdr_info = kzalloc(elf_info.e_phsize, GFP_KERNEL); + if (phdr_info == NULL) + return -ENOMEM; + + ret = read_elf_info(file, (void *)phdr_info, elf_info.e_phsize, elf_info.e_phoff); + if (ret < 0) { + kfree(phdr_info); + return ret; + } + + ret = find_elf_code_segment_info(phdr_info, &elf_info, code_segment_info); + kfree(phdr_info); + return ret; +} diff --git a/xpm/validator/exec_signature_info.c b/xpm/validator/exec_signature_info.c new file mode 100644 index 0000000000000000000000000000000000000000..a353b7ea8d384c1a49a8456188b8d91525d1ab40 --- /dev/null +++ b/xpm/validator/exec_signature_info.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include "exec_signature_info.h" + +#define VERITY_NODE_CACHE_LIMITS 10000 +#define VERITY_NODE_CACHE_RECYCLE_NUM 200 + +static DEFINE_RWLOCK(dm_verity_tree_lock); +static struct rb_root dm_verity_tree = RB_ROOT; +static int dm_verity_node_count; +static DEFINE_RWLOCK(fs_verity_tree_lock); +static struct rb_root fs_verity_tree = RB_ROOT; +static int fs_verity_node_count; + +static int check_exec_file_is_verity(struct file *file) +{ + return FILE_SIGNATURE_DM_VERITY; +} + +static struct exec_file_signature_info *rb_search_node(struct rb_root *root, uintptr_t file_inode) +{ + struct rb_node *node = root->rb_node; + struct exec_file_signature_info *file_node; + + while (node != NULL) { + file_node = rb_entry(node, struct exec_file_signature_info, rb_node); + if (file_inode < file_node->inode) { + node = file_node->rb_node.rb_left; + } else if (file_inode > file_node->inode) { + node = file_node->rb_node.rb_right; + } else { + atomic_inc(&file_node->reference); + return file_node; + } + } + return NULL; +} + +static struct exec_file_signature_info *rb_add_node(struct rb_root *root, int *node_count, + struct exec_file_signature_info *node) +{ + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct exec_file_signature_info *file; + + while (*p != NULL) { + parent = *p; + file = rb_entry(parent, struct exec_file_signature_info, rb_node); + if (node->inode < file->inode) { + p = &(*p)->rb_left; + } else if (node->inode > file->inode) { + p = &(*p)->rb_right; + } else { + atomic_inc(&file->reference); + return file; + } + } + + rb_link_node(&node->rb_node, parent, p); + rb_insert_color(&node->rb_node, root); + atomic_inc(&node->reference); + (*node_count)++; + return NULL; +} + +static void rb_erase_node(struct rb_root *root, int *node_count, + struct exec_file_signature_info *node) +{ + rb_erase(&node->rb_node, root); + (*node_count)--; +} + +static int find_idle_nodes(struct rb_root *root, uintptr_t *ilde_nodes, size_t count) +{ + int i = 0; + struct exec_file_signature_info *code_segment; + struct rb_node *node; + + for (node = rb_first(root); node != NULL && i < count; node = rb_next(node)) { + code_segment = rb_entry(node, struct exec_file_signature_info, rb_node); + if (atomic_read(&code_segment->reference) > 0) + continue; + + ilde_nodes[i++] = (uintptr_t)code_segment; + } + return i; +} + +static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) +{ + struct exec_file_signature_info *code_segment_info; + uintptr_t *code_segments; + int i = 0; + int count = VERITY_NODE_CACHE_RECYCLE_NUM; + + code_segments = kcalloc(count, sizeof(uintptr_t), GFP_KERNEL); + if (code_segments == NULL) + return; + + count = find_idle_nodes(root, code_segments, count); + while (i < count) { + code_segment_info = (struct exec_file_signature_info *)code_segments[i]; + rb_erase_node(root, node_count, code_segment_info); + kfree(code_segment_info); + i++; + } + kfree(code_segments); +} + +#ifdef CONFIG_SECURITY_XPM_DEBUG +static size_t test_elf_code_segment_info_size(struct rb_root *root) +{ + size_t size = 0; + struct exec_file_signature_info *file_node; + struct rb_node *node; + + for (node = rb_first(root); node != NULL; node = rb_next(node)) { + file_node = rb_entry(node, struct exec_file_signature_info, rb_node); + size += sizeof(struct exec_file_signature_info) + + file_node->code_segment_count * sizeof(struct exec_segment_info); + } + return size; +} + +static void test_printf_code_segment_cache_size(void) +{ + size_t cache_size = 0; + int count = 0; + + read_lock(&dm_verity_tree_lock); + cache_size += test_elf_code_segment_info_size(&dm_verity_tree); + count += dm_verity_node_count; + read_unlock(&dm_verity_tree_lock); + + read_lock(&fs_verity_tree_lock); + cache_size += test_elf_code_segment_info_size(&fs_verity_tree); + count += fs_verity_node_count; + read_unlock(&fs_verity_tree_lock); + + pr_info("[exec signature cache] count=%d, cache size=%d KB\n", count, cache_size / 1024); +} + +static void test_print_elf_code_segment_info(struct file *file, const struct exec_file_signature_info *file_info) +{ + char *ret_path; + char path[PATH_MAX] = {0}; + static int code_segment_test_count = 100; + int i; + + code_segment_test_count--; + if (code_segment_test_count > 0) + return; + + ret_path = file_path(file, path, PATH_MAX-1); + if (IS_ERR(ret_path)) + return; + + for (i = 0; i < file_info->code_segment_count; i++) { + pr_info("[exec signature segment] %s -> offset: 0x%llx size: 0x%lx\n", + ret_path, file_info->code_segments->file_offset, file_info->code_segments->size); + } + + code_segment_test_count = 100; +} +#endif + +static void rm_code_segment_info(void) +{ + if (dm_verity_node_count + fs_verity_node_count < VERITY_NODE_CACHE_LIMITS) + return; + +#ifdef CONFIG_SECURITY_XPM_DEBUG + test_printf_code_segment_cache_size(); +#endif + + if (dm_verity_node_count > fs_verity_node_count) { + write_lock(&dm_verity_tree_lock); + clear_code_segment_info_cache(&dm_verity_tree, &dm_verity_node_count); + write_unlock(&dm_verity_tree_lock); + return; + } + + write_lock(&fs_verity_tree_lock); + clear_code_segment_info_cache(&fs_verity_tree, &fs_verity_node_count); + write_unlock(&fs_verity_tree_lock); +} + +static int get_elf_code_segment_info(struct file *file, bool is_exec, int type, + struct exec_file_signature_info **code_segment_info) +{ + int ret; + struct rb_root *root; + rwlock_t *verity_lock; + int *node_count; + struct inode *file_node; + struct exec_file_signature_info *new_info; + struct exec_file_signature_info *tmp_info; + + if (type == FILE_SIGNATURE_DM_VERITY) { + root = &dm_verity_tree; + verity_lock = &dm_verity_tree_lock; + node_count = &dm_verity_node_count; + } else if (type == FILE_SIGNATURE_FS_VERITY) { + verity_lock = &fs_verity_tree_lock; + root = &fs_verity_tree; + node_count = &fs_verity_node_count; + } else { + return -EINVAL; + } + + file_node = file_inode(file); + if (file_node == NULL) + return -EINVAL; + + read_lock(verity_lock); + tmp_info = rb_search_node(root, (uintptr_t)file_node); + read_unlock(verity_lock); + if (tmp_info != NULL) { + if (is_exec && tmp_info->code_segments == NULL) + goto need_parse; + + *code_segment_info = tmp_info; + return 0; + } + +need_parse: + rm_code_segment_info(); + + if (!is_exec) { + new_info = kzalloc(sizeof(struct exec_file_signature_info), GFP_KERNEL); + if (new_info == NULL) + return -ENOMEM; + } else { + ret = parse_elf_code_segment_info(file, &new_info); + if (ret < 0) + return ret; +#ifdef CONFIG_SECURITY_XPM_DEBUG + test_print_elf_code_segment_info(file, new_info); +#endif + } + + new_info->type = type; + new_info->inode = (uintptr_t)file_node; + RB_CLEAR_NODE(&new_info->rb_node); + if (tmp_info != NULL) { + write_lock(verity_lock); + rb_erase_node(root, node_count, tmp_info); + tmp_info->type |= FILE_SIGNATURE_DELETE; + write_unlock(verity_lock); + if (atomic_sub_return(1, &tmp_info->reference) <= 0) + kfree(tmp_info); + } + + write_lock(verity_lock); + tmp_info = rb_add_node(root, node_count, new_info); + write_unlock(verity_lock); + if (tmp_info != NULL) { + kfree(new_info); + new_info = tmp_info; + } + *code_segment_info = new_info; + return 0; +} + +int get_exec_file_signature_info(struct file *file, bool is_exec, + struct exec_file_signature_info **info_ptr) +{ + int type; + + if (file == NULL || info_ptr == NULL) + return -EINVAL; + + type = check_exec_file_is_verity(file); + return get_elf_code_segment_info(file, is_exec, type, info_ptr); +} + +int put_exec_file_signature_info(struct exec_file_signature_info *exec_info) +{ + if ((exec_info == NULL) || + !exec_file_signature_is_verity(exec_info)) + return -EINVAL; + + if (atomic_sub_return(1, &exec_info->reference) <= 0 && + exec_file_signature_is_delete(exec_info)) + kfree(exec_info); + return 0; +} + +static struct exec_file_signature_info *elf_code_segment_info_delete(struct rb_root *root, + int *node_count, struct inode *file_node) +{ + struct exec_file_signature_info *signature_info; + + signature_info = rb_search_node(root, (uintptr_t)file_node); + if (signature_info != NULL) { + rb_erase_node(root, node_count, signature_info); + if (atomic_sub_return(1, &signature_info->reference) > 0) + signature_info->type |= FILE_SIGNATURE_DELETE; + else + kfree(signature_info); + } + return signature_info; +} + +void delete_exec_file_signature_info(struct inode *file_node) +{ + struct exec_file_signature_info *signature_info; + + if (file_node == NULL) + return; + + write_lock(&fs_verity_tree_lock); + signature_info = elf_code_segment_info_delete(&fs_verity_tree, &fs_verity_node_count, file_node); + write_unlock(&fs_verity_tree_lock); + if (signature_info != NULL) + return; + + write_lock(&dm_verity_tree_lock); + signature_info = elf_code_segment_info_delete(&dm_verity_tree, &dm_verity_node_count, file_node); + write_unlock(&dm_verity_tree_lock); +}