From 8b2ceda7ffd5d2df9ad9935cd3e60d49cd1ae521 Mon Sep 17 00:00:00 2001 From: limerence Date: Mon, 3 Apr 2023 09:57:14 +0800 Subject: [PATCH 01/35] add xpm module, lock Signed-off-by: limerence --- fs/proc/base.c | 7 + include/linux/lsm_hook_defs.h | 3 + include/linux/lsm_hooks.h | 4 + include/linux/mm.h | 9 + include/linux/mm_types.h | 5 + include/linux/mman.h | 3 + include/linux/security.h | 11 + include/linux/xpm_api.h | 62 ++ include/linux/xpm_types.h | 16 + include/uapi/linux/mman.h | 1 + mm/mmap.c | 31 +- security/Kconfig | 1 + security/Makefile | 2 + security/security.c | 7 + security/selinux/hooks.c | 65 ++ security/selinux/include/classmap.h | 2 + security/xpm/Kconfig | 35 + security/xpm/Makefile | 18 + security/xpm/core/xpm_api.c | 204 ++++++ security/xpm/core/xpm_log.h | 30 + security/xpm/core/xpm_main.c | 35 + security/xpm/core/xpm_misc.c | 130 ++++ security/xpm/core/xpm_misc.h | 17 + .../xpm/validator/elf_code_segment_info.c | 371 +++++++++++ security/xpm/validator/exec_signature_info.c | 50 ++ security/xpm/validator/exec_signature_info.h | 34 + ...47\350\241\214\346\235\203\351\231\220.md" | 607 ++++++++++++++++++ 27 files changed, 1752 insertions(+), 8 deletions(-) create mode 100755 include/linux/xpm_api.h create mode 100644 include/linux/xpm_types.h create mode 100755 security/xpm/Kconfig create mode 100755 security/xpm/Makefile create mode 100755 security/xpm/core/xpm_api.c create mode 100644 security/xpm/core/xpm_log.h create mode 100755 security/xpm/core/xpm_main.c create mode 100755 security/xpm/core/xpm_misc.c create mode 100755 security/xpm/core/xpm_misc.h create mode 100644 security/xpm/validator/elf_code_segment_info.c create mode 100644 security/xpm/validator/exec_signature_info.c create mode 100644 security/xpm/validator/exec_signature_info.h create mode 100755 "security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" diff --git a/fs/proc/base.c b/fs/proc/base.c index 27145778c144..1f4f2fe1e4ce 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -100,6 +100,7 @@ #include #include #include +#include #include #include "internal.h" #include "fd.h" @@ -3459,6 +3460,9 @@ static const struct pid_entry tgid_base_stuff[] = { #ifdef CONFIG_SCHED_RTG_DEBUG REG("sched_group_id", S_IRUGO|S_IWUGO, proc_pid_sched_group_id_operations), #endif +#ifdef CONFIG_XPM + REG("xpm_region", S_IRUGO, proc_xpm_region_operations), +#endif }; static int proc_tgid_base_readdir(struct file *file, struct dir_context *ctx) @@ -3794,6 +3798,9 @@ static const struct pid_entry tid_base_stuff[] = { #ifdef CONFIG_SCHED_RTG_DEBUG REG("sched_group_id", S_IRUGO|S_IWUGO, proc_pid_sched_group_id_operations), #endif +#ifdef CONFIG_XPM + REG("xpm_region", S_IRUGO, proc_xpm_region_operations), +#endif }; static int proc_tid_base_readdir(struct file *file, struct dir_context *ctx) diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index d13631a5e908..dade0ebf8295 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -161,6 +161,9 @@ LSM_HOOK(int, 0, file_ioctl, struct file *file, unsigned int cmd, LSM_HOOK(int, 0, mmap_addr, unsigned long addr) LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) +#ifdef CONFIG_XPM +LSM_HOOK(int, 0, mmap_region, struct vm_area_struct *vma) +#endif LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) LSM_HOOK(int, 0, file_lock, struct file *file, unsigned int cmd) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 64cdf4d7bfb3..4a97b07338ba 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -534,6 +534,10 @@ * @prot contains the protection that will be applied by the kernel. * @flags contains the operational flags. * Return 0 if permission is granted. + * @mmap_region : + * Check permission for a mmap operation. The @file may be NULL, e,g. + * if mapping anonymous memory. + * @vma contains the memory region to mmap. * @file_mprotect: * Check permissions before changing memory access permissions. * @vma contains the memory region to modify. diff --git a/include/linux/mm.h b/include/linux/mm.h index 8b199766b883..31c0a93b555a 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -316,6 +316,7 @@ extern unsigned int kobjsize(const void *objp); #define VM_HIGH_ARCH_BIT_4 36 /* bit only usable on 64-bit architectures */ #define VM_HIGH_ARCH_BIT_5 37 /* bit only usable on 64-bit architectures */ #define VM_HIGH_ARCH_BIT_6 38 /* bit only usable on 64-bit architectures */ +#define VM_HIGH_ARCH_BIT_7 39 /* bit only usable on 64-bit architectures */ #define VM_HIGH_ARCH_0 BIT(VM_HIGH_ARCH_BIT_0) #define VM_HIGH_ARCH_1 BIT(VM_HIGH_ARCH_BIT_1) #define VM_HIGH_ARCH_2 BIT(VM_HIGH_ARCH_BIT_2) @@ -323,6 +324,7 @@ extern unsigned int kobjsize(const void *objp); #define VM_HIGH_ARCH_4 BIT(VM_HIGH_ARCH_BIT_4) #define VM_HIGH_ARCH_5 BIT(VM_HIGH_ARCH_BIT_5) #define VM_HIGH_ARCH_6 BIT(VM_HIGH_ARCH_BIT_6) +#define VM_HIGH_ARCH_7 BIT(VM_HIGH_ARCH_BIT_7) #endif /* CONFIG_ARCH_USES_HIGH_VMA_FLAGS */ #ifdef CONFIG_MEM_PURGEABLE @@ -333,6 +335,12 @@ extern unsigned int kobjsize(const void *objp); #define VM_USEREXPTE 0 #endif /* CONFIG_MEM_PURGEABLE */ +#ifdef CONFIG_XPM +#define VM_XPM VM_HIGH_ARCH_7 +#else /* CONFIG_MEM_PURGEABLE */ +#define VM_XPM 0 +#endif /* CONFIG_MEM_PURGEABLE */ + #ifdef CONFIG_ARCH_HAS_PKEYS # define VM_PKEY_SHIFT VM_HIGH_ARCH_BIT_0 # define VM_PKEY_BIT0 VM_HIGH_ARCH_0 /* A protection key is a 4-bit value */ @@ -2659,6 +2667,7 @@ extern unsigned long __must_check vm_mmap(struct file *, unsigned long, struct vm_unmapped_area_info { #define VM_UNMAPPED_AREA_TOPDOWN 1 +#define VM_UNMAPPED_AREA_XPM 2 unsigned long flags; unsigned long length; unsigned long low_limit; diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index d86bc1d2dcc3..ae8607b93904 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -603,6 +604,10 @@ struct mm_struct { #ifdef CONFIG_IOMMU_SUPPORT u32 pasid; #endif + +#ifdef CONFIG_XPM + struct xpm_region xpm_region; +#endif } __randomize_layout; /* diff --git a/include/linux/mman.h b/include/linux/mman.h index 629cefc4ecba..b701eab8f4b7 100644 --- a/include/linux/mman.h +++ b/include/linux/mman.h @@ -154,6 +154,9 @@ calc_vm_flag_bits(unsigned long flags) _calc_vm_trans(flags, MAP_DENYWRITE, VM_DENYWRITE ) | _calc_vm_trans(flags, MAP_LOCKED, VM_LOCKED ) | _calc_vm_trans(flags, MAP_SYNC, VM_SYNC ) | +#ifdef CONIFG_XPM + _calc_vm_trans(flags, MAP_XPM, VM_XPM ) | +#endif arch_calc_vm_flag_bits(flags); } diff --git a/include/linux/security.h b/include/linux/security.h index e9b4b5410614..d00fff460664 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2002,4 +2002,15 @@ static inline int security_perf_event_write(struct perf_event *event) #endif /* CONFIG_SECURITY */ #endif /* CONFIG_PERF_EVENTS */ +#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY +extern int security_mmap_region(struct vm_area_struct *vma); +#else +static inline int security_mmap_region(struct vm_area_struct *vma) +{ + return 0; +} +#endif /* CONFIG_SECURITY */ +#endif /* CONFIG_XPM */ + #endif /* ! __LINUX_SECURITY_H */ diff --git a/include/linux/xpm_api.h b/include/linux/xpm_api.h new file mode 100755 index 000000000000..00cc1b6b5df2 --- /dev/null +++ b/include/linux/xpm_api.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + +#ifndef _XPM_COMMON_H +#define _XPM_COMMON_H + +#include +#include + +/* xpm internal return values */ +#define XPM_SUCCESS 0 +#define XPM_ERROR (-1) +#define XPM_SIG_ERROR (-2) + +#ifdef CONFIG_XPM_PERMISSIVE +#define XPM_RET(rc) (XPM_SUCCESS) +#else +#define XPM_RET(rc) (rc) +#endif + +extern const struct file_operations proc_xpm_region_operations; + +/** + * check whether input address range is out of the xpm region + */ +#ifdef CONFIG_XPM +extern bool is_xpm_region_outer(unsigned long addr_start, + unsigned long addr_end, unsigned long flags); + +/** + * set xpm region info if has MAP_XPM flag + */ +extern void set_xpm_region_info(struct vm_unmapped_area_info *info, + unsigned long flags); + +/** + * check whether mmap vma has a signature + */ +extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); +#else +bool is_xpm_region_outer(unsigned long addr_start, + unsigned long addr_end, unsigned long flags); +{ + return true; +} + +void set_xpm_region_info(struct vm_unmapped_area_info *info, + unsigned long flags) +{ + return; +} + +static int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) +{ + return XPM_SUCCESS; +} + +#endif + +#endif /* _XPM_COMMON_H */ \ No newline at end of file diff --git a/include/linux/xpm_types.h b/include/linux/xpm_types.h new file mode 100644 index 000000000000..04052eac7c18 --- /dev/null +++ b/include/linux/xpm_types.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + +#ifndef _XPM_TYPES_H +#define _XPM_TYPES_H + +#include + +struct xpm_region { + unsigned long addr_start; /* start adress of xpm region */ + unsigned long addr_end; /* end address of xpm region */ +}; + +#endif /* _XPM_TYPES_H */ \ No newline at end of file diff --git a/include/uapi/linux/mman.h b/include/uapi/linux/mman.h index c28942bcd4e3..58767e34cf0e 100644 --- a/include/uapi/linux/mman.h +++ b/include/uapi/linux/mman.h @@ -18,6 +18,7 @@ #define MAP_SHARED_VALIDATE 0x03 /* share + validate extension flags */ #define MAP_PURGEABLE 0x04 /* purgeable memory */ #define MAP_USEREXPTE 0x08 /* userspace extension page table */ +#define MAP_XPM 0x10 /* xpm control memory */ /* * Huge page size encoding when MAP_HUGETLB is specified, and a huge page diff --git a/mm/mmap.c b/mm/mmap.c index bccc3235cd61..3430b625a603 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -1877,8 +1878,8 @@ unsigned long mmap_region(struct file *file, unsigned long addr, vma_set_anonymous(vma); } - /* Allow architectures to sanity-check the vm_flags */ - if (!arch_validate_flags(vma->vm_flags)) { + /* Allow architectures to sanity-check the vma */ + if (security_mmap_region(vma) || !arch_validate_flags(vma->vm_flags)) { error = -EINVAL; if (file) goto close_and_free_vma; @@ -2000,8 +2001,9 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info) /* Check if current node has a suitable gap */ if (gap_start > high_limit) return -ENOMEM; - if (gap_end >= low_limit && - gap_end > gap_start && gap_end - gap_start >= length) + if ((gap_end >= low_limit && + gap_end > gap_start && gap_end - gap_start >= length) && + (is_xpm_region_outer(gap_start, gap_end, info->flags))) goto found; /* Visit right subtree if it looks promising */ @@ -2104,8 +2106,9 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) gap_end = vm_start_gap(vma); if (gap_end < low_limit) return -ENOMEM; - if (gap_start <= high_limit && - gap_end > gap_start && gap_end - gap_start >= length) + if ((gap_start <= high_limit && + gap_end > gap_start && gap_end - gap_start >= length) && + (is_xpm_region_outer(gap_start, gap_end, info->flags))) goto found; /* Visit left subtree if it looks promising */ @@ -2199,11 +2202,16 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, return addr; if (addr) { + if (flags & MAP_XPM) { + pr_err("do not allow specify addr with MAP_XPM flag\n"); + return -EPERM; + } addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); if (mmap_end - len >= addr && addr >= mmap_min_addr && (!vma || addr + len <= vm_start_gap(vma)) && - (!prev || addr >= vm_end_gap(prev))) + (!prev || addr >= vm_end_gap(prev)) && + (is_xpm_region_outer(addr, addr + len, 0))) return addr; } @@ -2213,6 +2221,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, info.high_limit = mmap_end; info.align_mask = 0; info.align_offset = 0; + set_xpm_region_info(&info, flags); return vm_unmapped_area(&info); } #endif @@ -2241,11 +2250,16 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, /* requesting a specific address */ if (addr) { + if (flags & MAP_XPM) { + pr_err("do not allow specify addr with MAP_XPM flag\n"); + return -EPERM; + } addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); if (mmap_end - len >= addr && addr >= mmap_min_addr && (!vma || addr + len <= vm_start_gap(vma)) && - (!prev || addr >= vm_end_gap(prev))) + (!prev || addr >= vm_end_gap(prev)) && + (is_xpm_region_outer(addr, addr + len, 0))) return addr; } @@ -2255,6 +2269,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, info.high_limit = arch_get_mmap_base(addr, mm->mmap_base); info.align_mask = 0; info.align_offset = 0; + set_xpm_region_info(&info, flags); addr = vm_unmapped_area(&info); /* diff --git a/security/Kconfig b/security/Kconfig index 9893c316da89..43cd1c19a90a 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -230,6 +230,7 @@ source "security/loadpin/Kconfig" source "security/yama/Kconfig" source "security/safesetid/Kconfig" source "security/lockdown/Kconfig" +source "security/xpm/Kconfig" source "security/integrity/Kconfig" diff --git a/security/Makefile b/security/Makefile index 3baf435de541..edbffda9c54d 100644 --- a/security/Makefile +++ b/security/Makefile @@ -13,6 +13,7 @@ subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown subdir-$(CONFIG_BPF_LSM) += bpf +subdir-$(CONFIG_XPM) += xpm # always enable default capabilities obj-y += commoncap.o @@ -32,6 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ obj-$(CONFIG_CGROUPS) += device_cgroup.o obj-$(CONFIG_BPF_LSM) += bpf/ +obj-$(CONFIG_XPM) += xpm/ # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity diff --git a/security/security.c b/security/security.c index 8ea826ea6167..52a92becfeca 100644 --- a/security/security.c +++ b/security/security.c @@ -2575,3 +2575,10 @@ int security_perf_event_write(struct perf_event *event) return call_int_hook(perf_event_write, 0, event); } #endif /* CONFIG_PERF_EVENTS */ + +#ifdef CONFIG_XPM +int security_mmap_region(struct vm_area_struct *vma) +{ + return call_int_hook(mmap_region, 0, vma); +} +#endif diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 662f7b4a9516..b3da278261c7 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -92,6 +92,7 @@ #include #include #include +#include #include "avc.h" #include "objsec.h" @@ -6956,6 +6957,66 @@ static int selinux_perf_event_write(struct perf_event *event) } #endif +#ifdef CONFIG_XPM + +static int xpm_prot_check(unsigned long prot) +{ + int rc = 0; + u32 sid = current_sid(); + + /* + * We are making executable an mapping that also writable will have an + * additional check. + */ + if ((prot & PROT_EXEC) && (prot & PROT_WRITE)) { + rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, + XPM__EXECMEM_WRITE, NULL); + } + + return XPM_RET(rc); +} + +static int xpm_sig_check(struct vm_area_struct *vma, unsigned long prot) +{ + int rc; + u32 sid = current_sid(); + + rc = xpm_sig_validate(vma, prot); + if (rc != XPM_SIG_ERROR) + return rc; + + rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, + XPM__EXECMEM_NO_SIG, NULL); + + return XPM_RET(rc); +} + +static int xpm_check(struct vm_area_struct *vma, unsigned long prot) +{ + int rc; + + rc = xpm_prot_check(prot); + if (rc) + return rc; + + return xpm_sig_check(vma, prot); +} + +static int selinux_xpm_mmap_ctrl(struct vm_area_struct *vma) +{ + return xpm_check(vma, vma->vm_flags); +} + +static int selinux_xpm_mprotect_ctrl(struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + if (checkreqprot_get(&selinux_state)) + prot = reqprot; + + return xpm_check(vma, prot); +} +#endif + /* * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order: * 1. any hooks that don't belong to (2.) or (3.) below, @@ -7241,6 +7302,10 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { #ifdef CONFIG_PERF_EVENTS LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), #endif +#ifdef CONFIG_XPM + LSM_HOOK_INIT(mmap_region, selinux_xpm_mmap_ctrl), + LSM_HOOK_INIT(file_mprotect, selinux_xpm_mprotect_ctrl), +#endif }; static __init int selinux_init(void) diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 7271a0e05966..7ff92bcc2c9b 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -250,6 +250,8 @@ struct security_class_mapping secclass_map[] = { { "open", "cpu", "kernel", "tracepoint", "read", "write", NULL } }, { "lockdown", { "integrity", "confidentiality", NULL } }, + { "xpm", + { "execmem_no_sig", "execmem_write", NULL } }, { NULL } }; diff --git a/security/xpm/Kconfig b/security/xpm/Kconfig new file mode 100755 index 000000000000..ac04a6dfccfc --- /dev/null +++ b/security/xpm/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2023 Huawei Device Co., Ltd. +# +# Config for the excutable permission manager +# + +menu "executable permission manager" + +config XPM + bool "enables excutable permission manager feature" + default y + 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 XPM_PERMISSIVE + bool "enables excutable permission manager permissive mode" + depends on XPM + default y + help + When selected, in violation of the xpm control poilicy result in a + warning instead of refuse to execute. This option should only be + eanabled during development. + +config XPM_DEBUG + bool "excutable permission manager debug mode" + depends on XPM + default y + 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/security/xpm/Makefile b/security/xpm/Makefile new file mode 100755 index 000000000000..a8b77ba0985e --- /dev/null +++ b/security/xpm/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2023 Huawei Device Co., Ltd. +# +# Makefile for the ecutable permission manager(XPM) module +# + +obj-$(CONFIG_XPM) += \ + core/xpm_main.o \ + core/xpm_misc.o \ + core/xpm_api.o \ + validator/elf_code_segment_info.o \ + validator/exec_signature_info.o + +ccflags-$(CONFIG_XPM) += \ + -I$(srctree)/security/xpm/incude \ + -I$(srctree)/security/xpm/validator \ + -I$(srctree)/fs/proc diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c new file mode 100755 index 000000000000..174282972eb2 --- /dev/null +++ b/security/xpm/core/xpm_api.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xpm_log.h" +#include "exec_signature_info.h" + +#define XPM_REGION_STR_LEN 33 + +extern struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); + +#ifdef CONFIG_XPM_DEBUG +static void xpm_print_file_path(struct file *file) +{ + char *pathname = NULL; + char *pathbuf = NULL; + + if (!file) + return; + + pathbuf = __getname(); + if (!pathbuf) + return; + + pathname = d_absolute_path(&file->f_path, pathbuf, PATH_MAX); + if (IS_ERR(pathname)) { + __putname(pathbuf); + } + xpm_log_debug("%s", pathname); + + __putname(pathbuf); +} +#endif + +bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, + unsigned long flags) +{ + bool result; + struct mm_struct *mm = current->mm; + + if (flags & VM_UNMAPPED_AREA_XPM) + return true; + + result = ((addr_start >= mm->xpm_region.addr_end) || + (addr_end <= mm->xpm_region.addr_start)); + if (!result) + xpm_log_debug("addr region: [0x%lx, 0x%lx]," + "xpm region: [0x%lx, 0x%lx]", + addr_start, addr_end, + mm->xpm_region.addr_start, mm->xpm_region.addr_end); + + return result; +} + +void set_xpm_region_info(struct vm_unmapped_area_info *info, + unsigned long flags) +{ + struct mm_struct *mm = current->mm; + + if (!info) { + xpm_log_error("input vm_unmapped_area_info is NULL"); + return; + } + + if (flags & MAP_XPM) { + info->flags |= VM_UNMAPPED_AREA_XPM; + info->low_limit = mm->xpm_region.addr_start; + info->high_limit = mm->xpm_region.addr_end; + } +} + +static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, + struct exec_file_sig_info *info) +{ + int i; + unsigned long vm_addr_start, vm_addr_end; + unsigned long seg_addr_start, seg_addr_end; + struct exec_segment_info *segment = info->code_segment; + + if (!is_exec) + return XPM_SUCCESS; + + if (!segment) { + xpm_log_error("elf executable segemnt is NULL"); + return XPM_ERROR; + } + + 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_section_count; i++) { + seg_addr_start = ALIGN_DOWN(segment[i].file_offset, PAGE_SIZE); + seg_addr_end = PAGE_ALIGN(segment[i].file_offset + + segment[i].size); + + if ((vm_addr_start >= seg_addr_start) && + (vm_addr_end <= seg_addr_end)) + return XPM_SUCCESS; + } + + return XPM_ERROR; +} + +int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) +{ + int ret = 0; + bool is_abc; + struct exec_file_sig_info *info = NULL; + + if (!vma) { + xpm_log_error("input vma is NULL"); + return XPM_ERROR; + } + + is_abc = vma->vm_flags & VM_XPM; + /* if no need validate signature, default result is true */ + if (!is_abc || !(vma->vm_file && (prot & PROT_EXEC))) + return XPM_SUCCESS; + + xpm_log_debug("file type: %s", is_abc ? "ABC" : "ELF"); + /* + * When file map execute permission or xpm validate region, we should check + * the signature of the map region. + */ + ret = get_exec_file_sig_info(vma->vm_file, !is_abc, &info); + if (ret) { + xpm_log_error("get executable file signature info failed," + "ret = 0x%x", ret); + return XPM_ERROR; + } + + if (info == NULL) { + xpm_print_file_path(vma->vm_file); + xpm_log_error("executable file signature is invalid"); + put_exec_file_sig_info(info); + return XPM_SIG_ERROR; + } + + ret = xpm_segment_check(!is_abc, vma, info); + if (ret) { + xpm_print_file_path(vma->vm_file); + xpm_log_error("xpm executable segment check failed"); + ret = XPM_SIG_ERROR; + } + + put_exec_file_sig_info(info); + xpm_log_debug("xpm sig validate success"); + return ret; +} + +static int xpm_region_open(struct inode *inode, struct file *file) +{ + struct mm_struct *mm = proc_mem_open(inode, PTRACE_MODE_READ); + + if (IS_ERR(mm)) + return PTR_ERR(mm); + + file->private_data = mm; + return XPM_SUCCESS; +} + +static ssize_t xpm_region_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct mm_struct *mm = file->private_data; + char xpm_region_str[XPM_REGION_STR_LEN] = {0}; + size_t len; + + if (!mm) + return XPM_SUCCESS; + + len = snprintf(xpm_region_str, XPM_REGION_STR_LEN, "%x-%x", + mm->xpm_region.addr_start, + mm->xpm_region.addr_end); + + return simple_read_from_buffer(buf, count, pos, xpm_region_str, len); +} + +static int xpm_region_release(struct inode *inode, struct file *file) +{ + struct mm_struct *mm = file->private_data; + + if (mm) + mmdrop(mm); + + return XPM_SUCCESS; +} + +const struct file_operations proc_xpm_region_operations = { + .open = xpm_region_open, + .read = xpm_region_read, + .llseek = generic_file_llseek, + .release = xpm_region_release, +}; diff --git a/security/xpm/core/xpm_log.h b/security/xpm/core/xpm_log.h new file mode 100644 index 000000000000..e76929009797 --- /dev/null +++ b/security/xpm/core/xpm_log.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + +#ifndef _XPM_LOG_H +#define _XPM_LOG_H + +#include + +#define XPM_TAG "xpm_kernel" + +#define INFO_TAG "I" +#define ERROR_TAG "E" +#define DEBUG_TAG "D" + +#define xpm_log_info(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ + INFO_TAG, XPM_TAG, __func__, ##args) + +#define xpm_log_error(fmt, args...) pr_err("[%s/%s]%s: " fmt "\n", \ + ERROR_TAG, XPM_TAG, __func__, ##args) + +#ifdef CONFIG_XPM_DEBUG +#define xpm_log_debug(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ + DEBUG_TAG, XPM_TAG, __func__, ##args) +#else +#define xpm_log_debug(fmt, args...) no_printk(fmt, ##args) +#endif + +#endif /* _XPM_LOG_H */ \ No newline at end of file diff --git a/security/xpm/core/xpm_main.c b/security/xpm/core/xpm_main.c new file mode 100755 index 000000000000..9420e900bb28 --- /dev/null +++ b/security/xpm/core/xpm_main.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include "xpm_misc.h" +#include "xpm_log.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); + else + xpm_log_info("xpm module init success"); + + return ret; +} + +static void __exit xpm_module_exit(void) +{ + xpm_deregister_misc_device(); + xpm_log_info("xpm module exit success"); +} + +module_init(xpm_module_init); +module_exit(xpm_module_exit); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c new file mode 100755 index 000000000000..a3b2d97a3040 --- /dev/null +++ b/security/xpm/core/xpm_misc.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * + * Executable permission manager driver module. + */ + +#include "xpm_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xpm_log.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; + unsigned long addr; + struct mm_struct *mm= current->mm; + + 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"); + ret = XPM_SUCCESS; + goto exit; + } + + addr = get_unmapped_area(NULL, addr_base, length, 0, 0); + if (IS_ERR_VALUE(addr)) { + 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; + xpm_log_debug("xpm set kernel region success: [0x%lx, 0x%lx]", + mm->xpm_region.addr_start, mm->xpm_region.addr_end); + +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; + + xpm_log_debug("xpm user set region: [0x%lx, 0x%lx, 0x%lx]", + info.addr_base, info.length, info.addr_base + info.length); + + 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) +{ + int ret; + + ret = misc_register(&xpm_misc); + if (unlikely(ret)) + xpm_log_error("xpm register misc device failed, ret = %d", ret); + else + xpm_log_info("xpm register misc device success"); + + return ret; +} + +void xpm_deregister_misc_device(void) +{ + misc_deregister(&xpm_misc); +} \ No newline at end of file diff --git a/security/xpm/core/xpm_misc.h b/security/xpm/core/xpm_misc.h new file mode 100755 index 000000000000..aac039cf44b8 --- /dev/null +++ b/security/xpm/core/xpm_misc.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* 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 */ \ No newline at end of file diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c new file mode 100644 index 000000000000..dfe7d348ec95 --- /dev/null +++ b/security/xpm/validator/elf_code_segment_info.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#include +#include +#include +#include +#include +#include "exec_signature_info.h" + +struct elf_segment_info { + unsigned short type; + unsigned short e_phnum; + unsigned e_phsize; + unsigned long long e_phoff; +}; + +static DEFINE_SPINLOCK(dm_verity_tree_lock); +static struct rb_root dm_verity_tree = RB_ROOT; +static int dm_verity_node_count = 0; +static DEFINE_SPINLOCK(fs_verity_tree_lock); +static struct rb_root fs_verity_tree = RB_ROOT; +static int fs_verity_node_count = 0; + +static struct exec_file_sig_info *rb_search_node(struct rb_root *root, struct inode *file_inode) +{ + struct rb_node *node = root->rb_node; + struct exec_file_sig_info *file_node; + + while (node != NULL) { + file_node = rb_entry(node, struct exec_file_sig_info, rb_node); + if ((uintptr_t)file_inode < (uintptr_t)file_node->inode) { + node = file_node->rb_node.rb_left; + } else if ((uintptr_t)file_inode > (uintptr_t)file_node->inode) { + node = file_node->rb_node.rb_right; + } else { + file_node->reference++; + return file_node; + } + } + return NULL; +} + +static void rb_add_node(struct rb_root *root, int *node_count, struct exec_file_sig_info *node) +{ + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct exec_file_sig_info *file; + + while (*p != NULL) { + parent = *p; + file = rb_entry(parent, struct exec_file_sig_info, rb_node); + if ((uintptr_t)node->inode < (uintptr_t)file->inode) { + p = &(*p)->rb_left; + } else { + p = &(*p)->rb_right; + } + } + + rb_link_node(&node->rb_node, parent, p); + rb_insert_color(&node->rb_node, root); + node->reference++; + (*node_count)++; +} + +static void rb_erase_node(struct rb_root *root, int *node_count, struct exec_file_sig_info *node) +{ + rb_erase(&node->rb_node, root); + (*node_count)--; +} + +static int read_elf_info(struct file *file, void *buffer, size_t read_size, loff_t pos) +{ + size_t len = kernel_read(file, buffer, read_size, &pos); + if (unlikely(len != read_size)) { + return -EIO; + } + return 0; +} + +static int find_idle_nodes(struct rb_root *root, uintptr_t *ilde_nodes, size_t count) +{ + int i = 0; + struct exec_file_sig_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_sig_info, rb_node); + if (code_segment->reference > 0) { + continue; + } + ilde_nodes[i] = (uintptr_t)code_segment; + i++; + } + return i; +} + +static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) +{ + int i = 0; + int count = 200; + uintptr_t *code_segments = kzalloc(count * sizeof(uintptr_t), GFP_KERNEL); + if (code_segments == NULL) { + return; + } + + count = find_idle_nodes(root, code_segments, count); + while (i < count) { + struct exec_file_sig_info *code_segment_info = (struct exec_file_sig_info *)code_segments[i]; + rb_erase_node(root, node_count, code_segment_info); + kfree(code_segment_info); + i++; + } +} + +static void rm_code_segment_info(void) +{ + if ((dm_verity_node_count + fs_verity_node_count) < 500) { + return; + } + + if (dm_verity_node_count > fs_verity_node_count) { + spin_lock(&dm_verity_tree_lock); + clear_code_segment_info_cache(&dm_verity_tree, &dm_verity_node_count); + spin_unlock(&dm_verity_tree_lock); + return; + } + + spin_lock(&fs_verity_tree_lock); + clear_code_segment_info_cache(&fs_verity_tree, &fs_verity_node_count); + spin_unlock(&fs_verity_tree_lock); +} + +static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, int phdr_num) +{ + int i; + int count = 0; + struct elf32_phdr *phdr_info; + + for (i = 0; i < phdr_num; i++) { + phdr_info = elf_phdr + i; + if (!(phdr_info->p_flags & PF_X)) { + continue; + } + count++; + } + return count; +} + +static void get_elf32_code_segment(struct elf32_phdr *elf_phdr, int phdr_num, struct exec_file_sig_info *segment_info) +{ + int i; + struct elf32_phdr *phdr_info; + + for (i = 0; i < phdr_num; i++) { + phdr_info = elf_phdr + i; + if (!(phdr_info->p_flags & PF_X)) { + continue; + } + segment_info->code_section_count++; + segment_info->code_segment[segment_info->code_section_count - 1].file_offset = phdr_info->p_offset; + segment_info->code_segment[segment_info->code_section_count - 1].size = phdr_info->p_filesz; + } +} + +static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, int phdr_num) +{ + int i; + int count = 0; + struct elf64_phdr *phdr_info; + + for (i = 0; i < phdr_num; i++) { + phdr_info = elf_phdr + i; + if (!(phdr_info->p_flags & PF_X)) { + continue; + } + count++; + } + return count; +} + +static void get_elf64_code_segment(struct elf64_phdr *elf_phdr, int phdr_num, struct exec_file_sig_info *segment_info) +{ + int i; + struct elf64_phdr *phdr_info; + + for (i = 0; i < phdr_num; i++) { + phdr_info = elf_phdr + i; + if (!(phdr_info->p_flags & PF_X)) { + continue; + } + segment_info->code_section_count++; + segment_info->code_segment[segment_info->code_section_count - 1].file_offset = phdr_info->p_offset; + segment_info->code_segment[segment_info->code_section_count - 1].size = phdr_info->p_filesz; + } +} + +static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_segment_info *segment_info) +{ + struct elfhdr elf_ehdr = {0}; + struct elf32_hdr *elf32_ehdr; + struct elf64_hdr *elf64_ehdr; + int 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; + } + + if (elf_ehdr.e_type != ET_EXEC && elf_ehdr.e_type != ET_DYN) { + pr_err("Not an ELF exec.\n"); + return -ENOEXEC; + } + + if (elf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) { + segment_info->type = ELFCLASS32; + elf32_ehdr = (struct elf32_hdr *)&elf_ehdr; + segment_info->e_phnum = elf32_ehdr->e_phnum; + segment_info->e_phsize = sizeof(struct elf32_phdr) * elf32_ehdr->e_phnum; + segment_info->e_phoff = elf32_ehdr->e_phoff; + } else if (elf_ehdr.e_ident[EI_CLASS] == ELFCLASS64) { + segment_info->type = ELFCLASS64; + elf64_ehdr = (struct elf64_hdr *)&elf_ehdr; + segment_info->e_phnum = elf64_ehdr->e_phnum; + segment_info->e_phsize = sizeof(struct elf64_phdr) * elf64_ehdr->e_phnum; + segment_info->e_phoff = elf64_ehdr->e_phoff; + } else { + return -ENOEXEC; + } + return 0; +} + +static int find_elf_code_segment_info(const char *phdr_info, struct elf_segment_info *segment_info, struct exec_file_sig_info **file_info) +{ + size_t size; + struct exec_file_sig_info *exec_segment_info; + int segment_count; + if (segment_info->type == ELFCLASS32) { + segment_count = get_elf32_code_segment_count((struct elf32_phdr *)phdr_info, segment_info->e_phnum); + } else { + segment_count = get_elf64_code_segment_count((struct elf64_phdr *)phdr_info, segment_info->e_phnum); + } + if (segment_count == 0) { + return -ENOEXEC; + } + + size = sizeof(struct exec_file_sig_info) + segment_count * sizeof(struct exec_segment_info); + exec_segment_info = (struct exec_file_sig_info *)kzalloc(size, GFP_KERNEL); + if (exec_segment_info == NULL) { + return -ENOMEM; + } + exec_segment_info->code_segment = (struct exec_segment_info *)((char *)exec_segment_info + sizeof(struct exec_file_sig_info)); + if (segment_info->type == ELFCLASS32) { + get_elf32_code_segment((struct elf32_phdr *)phdr_info, segment_info->e_phnum, exec_segment_info); + } else { + get_elf64_code_segment((struct elf64_phdr *)phdr_info, segment_info->e_phnum, exec_segment_info); + } + *file_info = exec_segment_info; + return 0; +} + +static int parse_elf_code_segment_info(struct file *file, struct exec_file_sig_info **code_segment_info) +{ + const char *phdr_info; + struct elf_segment_info segment_info = {0}; + int ret = elf_check_and_get_code_segment_offset(file, &segment_info); + if (ret < 0) { + return ret; + } + + phdr_info = kzalloc(segment_info.e_phsize, GFP_KERNEL); + if (phdr_info == NULL) { + return -ENOMEM; + } + + ret = read_elf_info(file, (void *)phdr_info, segment_info.e_phsize, segment_info.e_phoff); + if (ret < 0) { + kfree(phdr_info); + return ret; + } + + ret = find_elf_code_segment_info(phdr_info, &segment_info, code_segment_info); + kfree(phdr_info); + return ret; +} + +int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_sig_info **code_segment_info) +{ + int ret; + struct rb_root *root; + spinlock_t *verity_lock; + int *node_count; + struct inode *file_node; + struct exec_file_sig_info *segment_info = NULL; + + if (type == FILE_DM_VERITY) { + root = &dm_verity_tree; + verity_lock = &dm_verity_tree_lock; + node_count = &dm_verity_node_count; + } else if (type == FILE_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; + } + + spin_lock(verity_lock); + segment_info = rb_search_node(root, file_node); + spin_unlock(verity_lock); + if (segment_info != NULL) { + *code_segment_info = segment_info; + return 0; + } + + rm_code_segment_info(); + + if (!is_exec) { + segment_info = (struct exec_file_sig_info *)kzalloc(sizeof(struct exec_file_sig_info *), GFP_KERNEL); + if (segment_info == NULL) { + return -ENOMEM; + } + goto insert_tree; + } + + ret = parse_elf_code_segment_info(file, &segment_info); + if (ret < 0) { + return ret; + } + +insert_tree: + segment_info->type = type; + segment_info->inode = file_node; + RB_CLEAR_NODE(&segment_info->rb_node); + + spin_lock(verity_lock); + rb_add_node(root, node_count, segment_info); + spin_unlock(verity_lock); + *code_segment_info = segment_info; + return 0; +} + +int put_elf_code_segment_info(struct exec_file_sig_info *code_segment_info) +{ + spinlock_t *verity_lock; + + if ((code_segment_info == NULL) || + ((code_segment_info->type != FILE_DM_VERITY) && (code_segment_info->type != FILE_FS_VERITY))) { + return -EINVAL; + } + + if (code_segment_info->type == FILE_DM_VERITY) { + verity_lock = &dm_verity_tree_lock; + } else { + verity_lock = &fs_verity_tree_lock; + } + + spin_lock(verity_lock); + if (code_segment_info->reference > 0) { + code_segment_info->reference--; + } + spin_unlock(verity_lock); + return 0; +} diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c new file mode 100644 index 000000000000..df7146e75b9e --- /dev/null +++ b/security/xpm/validator/exec_signature_info.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#include +#include +#include +#include +#include "exec_signature_info.h" + +static int check_exec_file_is_verity(struct file *file, struct inode *file_node) +{ + /* 校验是否是 dm-verity + * /system/lib /system/lib64 /system/bin + */ + char buf[PATH_MAX]; + char *file_name = file_path(file, buf, PATH_MAX); + if (file_name == NULL) { + return FILE_NORMAL; + } + +#ifdef CONFIG_FS_VERITY + if (file_node->i_verity_info != NULL) { + return FILE_FS_VERITY; + } +#endif + return FILE_DM_VERITY; +} + +int get_exec_file_sig_info(struct file *file, bool is_exec, struct exec_file_sig_info **info_ptr) +{ + int type; + + if (file == NULL || info_ptr == NULL) { + return -EINVAL; + } + + struct inode *file_node = file_inode(file); + if (file_node == NULL) { + return -EINVAL; + } + + type = check_exec_file_is_verity(file, file_node); + return get_elf_code_segment_info(file, is_exec, type, info_ptr); +} + +int put_exec_file_sig_info(struct exec_file_sig_info *exec_info) +{ + return put_elf_code_segment_info(exec_info); +} diff --git a/security/xpm/validator/exec_signature_info.h b/security/xpm/validator/exec_signature_info.h new file mode 100644 index 000000000000..6475b3919e38 --- /dev/null +++ b/security/xpm/validator/exec_signature_info.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#ifndef _EXEC_SIGNATURE_INFO_H +#define _EXEC_SIGNATURE_INFO_H + +#include +#include + +struct exec_segment_info { + unsigned long file_offset; + unsigned long size; +}; + +#define FILE_FS_VERITY 0 +#define FILE_DM_VERITY 1 +#define FILE_NORMAL 2 + +struct exec_file_sig_info { + struct rb_node rb_node; + long reference; + int type; + struct inode *inode; + uint8_t hash[32]; + int code_section_count; + struct exec_segment_info *code_segment; +}; + +int put_elf_code_segment_info(struct exec_file_sig_info *code_segment_info); +int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_sig_info **code_segment_info); +int get_exec_file_sig_info(struct file *file, bool is_exec, struct exec_file_sig_info **info_ptr); +int put_exec_file_sig_info(struct exec_file_sig_info *exec_info); +#endif diff --git "a/security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" "b/security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" new file mode 100755 index 000000000000..7d2b6d7b5db6 --- /dev/null +++ "b/security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" @@ -0,0 +1,607 @@ +# 代码执行权限 + +## 代码下载 +**方式一(推荐):通过repo + ssh下载(需注册公钥,请参考码云帮助中心)。** +>repo init -u git@gitee.com:openharmony/manifest.git -b master --no-repo-verify +repo sync -c +repo forall -c 'git lfs pull' + +**方式二:通过repo + https下载。** +> repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify +repo sync -c +repo forall -c 'git lfs pull' + +**执行prebuilts** +>bash build/prebuilts_download.sh + +##版本相关 +**daliy build** +>http://ci.openharmony.cn/dailys/dailybuilds + +## 编译命令 +|目标|命令| +|---|---| +|rk3568内核|./build.sh --product-name rk3568 --target-cpu arm64 --build-target kernel --gn-args linux_kernel_version="linux-5.10"| + +## 配置文件 +>kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig + +### 安全内存相关 + +#### 函数签名 +``` +int xpm_set_process_va_region(unsigned long addr_base, unsigned long length); +``` +1. 其中入参addr_base为用户态进程推荐的安全内存的起始地址,如果该值为0,则用户态对安全内存的起始地址没有要求,值得注意的是**该值仅为推荐,如果该地址已经被分配vma,这安全内存会继续向下寻找符合length长度的虚拟地址范围**。 +2. length为申请虚拟地址的长度。 +## 设置流程 +### 1. 寻找一块符合要求的虚拟地址范围 +linux进程典型内存布局: +![avtar](https://img-blog.csdn.net/20180615113202958) + +#### unmmaped_area获取关键函数 +```C +unsigned long +get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, + unsigned long pgoff, unsigned long flags) +{ + unsigned long (*get_area)(struct file *, unsigned long, + unsigned long, unsigned long, unsigned long); + + unsigned long error = arch_mmap_check(addr, len, flags); + if (error) + return error; + + /* Careful about overflows.. */ + if (len > TASK_SIZE) + return -ENOMEM; + + get_area = current->mm->get_unmapped_area; + if (file) { + if (file->f_op->get_unmapped_area) + get_area = file->f_op->get_unmapped_area; + } else if (flags & MAP_SHARED) { + /* + * mmap_region() will call shmem_zero_setup() to create a file, + * so use shmem's get_unmapped_area in case it can be huge. + * do_mmap() will clear pgoff, so match alignment. + */ + pgoff = 0; + get_area = shmem_get_unmapped_area; + } + + addr = get_area(file, addr, len, pgoff, flags); + if (IS_ERR_VALUE(addr)) + return addr; + + if (addr > TASK_SIZE - len) + return -ENOMEM; + if (offset_in_page(addr)) + return -EINVAL; + + error = security_mmap_addr(addr); + return error ? error : addr; +} +``` +#### 关键点1 +**mm/mmap.c** +```C +#ifndef HAVE_ARCH_UNMAPPED_AREA +unsigned long +arch_get_unmapped_area(struct file *filp, unsigned long addr, + unsigned long len, unsigned long pgoff, unsigned long flags) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma, *prev; + struct vm_unmapped_area_info info; + const unsigned long mmap_end = arch_get_mmap_end(addr); + + if (len > mmap_end - mmap_min_addr) + return -ENOMEM; + + if (flags & MAP_FIXED) + return addr; + + if (addr) { + addr = PAGE_ALIGN(addr); + // find_vam_prev解释:查找挨着addr前一个vma以及addr后一个vma,以便于计算vma之间的GAP + vma = find_vma_prev(mm, addr, &prev); + //判断解释: + // 1.addr开始的unmmaped的region需要在mmap_min_addr和mmap_end之间。 + // 2.如果addr后面的vma不存在,则证明addr后面都是unmmaped区域;如果vma后面的vma存在,则addr申请的region不能超过后面vma的起始地址,超过了说明这个GAP不合适。 + // 3.同理addr前面的vma不存在,则证明addr前面都unmmaped区域;如果前面的vma存在,则addr申请的region的起始地址不能小于前面vma的结束地址;到了这一步可以确定这个GAP是否满足对应addr申请的大小了。 + + // 但是:xpm又安全虚拟地址范围(xpm_va_region),addr申请的unmmaped的虚拟地址范围要么全在xpm内,要么全不在,一半在一半不在,那就不行。即(addr + len < xpm_va_region->start) || (addr > xpm_va_region->end) || (addr >= xpm_va_region->start && addr + len <= xpm_va_region->end) + // bool is_cross_xpm_va_region(addr, len) + // 问题1:如果存在安全内存不够的情况,是否要返回mmap成功,但是mmap的内存不在安全内存范围内? + if (mmap_end - len >= addr && addr >= mmap_min_addr && + (!vma || addr + len <= vm_start_gap(vma)) && + (!prev || addr >= vm_end_gap(prev))) + return addr; + } + + info.flags = 0; + info.length = len; + info.low_limit = mm->mmap_base; + info.high_limit = mmap_end; + info.align_mask = 0; + info.align_offset = 0; + return vm_unmapped_area(&info); +} +#endif + +#ifndef HAVE_ARCH_UNMAPPED_AREA_TOPDOWN +unsigned long +arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, + unsigned long len, unsigned long pgoff, + unsigned long flags) +{ + struct vm_area_struct *vma, *prev; + struct mm_struct *mm = current->mm; + struct vm_unmapped_area_info info; + const unsigned long mmap_end = arch_get_mmap_end(addr); + + /* requested length too big for entire address space */ + if (len > mmap_end - mmap_min_addr) + return -ENOMEM; + + if (flags & MAP_FIXED) + return addr; + + /* requesting a specific address */ + if (addr) { + addr = PAGE_ALIGN(addr); + // 1. 与arch_get_unmapped_area基本一致 + vma = find_vma_prev(mm, addr, &prev); + if (mmap_end - len >= addr && addr >= mmap_min_addr && + (!vma || addr + len <= vm_start_gap(vma)) && + (!prev || addr >= vm_end_gap(prev))) + return addr; + } + + info.flags = VM_UNMAPPED_AREA_TOPDOWN; + info.length = len; + info.low_limit = max(PAGE_SIZE, mmap_min_addr); + info.high_limit = arch_get_mmap_base(addr, mm->mmap_base); + info.align_mask = 0; + info.align_offset = 0; + addr = vm_unmapped_area(&info); + + /* + * A failed mmap() very likely causes application failure, + * so fall back to the bottom-up function here. This scenario + * can happen with large stack limits and large mmap() + * allocations. + */ + if (offset_in_page(addr)) { + VM_BUG_ON(addr != -ENOMEM); + info.flags = 0; + info.low_limit = TASK_UNMAPPED_BASE; + info.high_limit = mmap_end; + addr = vm_unmapped_area(&info); + } + + return addr; +} +#endif +``` +**arch/arm/mm/mmap.c** +```C +unsigned long +arch_get_unmapped_area(struct file *filp, unsigned long addr, + unsigned long len, unsigned long pgoff, unsigned long flags) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + int do_align = 0; + int aliasing = cache_is_vipt_aliasing(); + struct vm_unmapped_area_info info; + + /* + * We only need to do colour alignment if either the I or D + * caches alias. + */ + if (aliasing) + do_align = filp || (flags & MAP_SHARED); + + /* + * We enforce the MAP_FIXED case. + */ + if (flags & MAP_FIXED) { + if (aliasing && flags & MAP_SHARED && + (addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)) + return -EINVAL; + return addr; + } + + if (len > TASK_SIZE) + return -ENOMEM; + + if (addr) { + if (do_align) + addr = COLOUR_ALIGN(addr, pgoff); + else + addr = PAGE_ALIGN(addr); + + // 1. 获取addr小于vma->end的最靠近的一个vma + vma = find_vma(mm, addr); + // 2. 当前只检查了vma,如果addr插入到了其他vma中应该怎么办呢? + if (TASK_SIZE - len >= addr && + (!vma || addr + len <= vm_start_gap(vma))) + return addr; + } + + info.flags = 0; + info.length = len; + info.low_limit = mm->mmap_base; + info.high_limit = TASK_SIZE; + info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; + info.align_offset = pgoff << PAGE_SHIFT; + return vm_unmapped_area(&info); +} + +unsigned long +arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, + const unsigned long len, const unsigned long pgoff, + const unsigned long flags) +{ + struct vm_area_struct *vma; + struct mm_struct *mm = current->mm; + unsigned long addr = addr0; + int do_align = 0; + int aliasing = cache_is_vipt_aliasing(); + struct vm_unmapped_area_info info; + + /* + * We only need to do colour alignment if either the I or D + * caches alias. + */ + if (aliasing) + do_align = filp || (flags & MAP_SHARED); + + /* requested length too big for entire address space */ + if (len > TASK_SIZE) + return -ENOMEM; + + if (flags & MAP_FIXED) { + if (aliasing && flags & MAP_SHARED && + (addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)) + return -EINVAL; + return addr; + } + + /* requesting a specific address */ + if (addr) { + if (do_align) + addr = COLOUR_ALIGN(addr, pgoff); + else + addr = PAGE_ALIGN(addr); + vma = find_vma(mm, addr); + // 1. 与arch_get_unmapped_area基本一致,无需分析 + if (TASK_SIZE - len >= addr && + (!vma || addr + len <= vm_start_gap(vma))) + return addr; + } + + info.flags = VM_UNMAPPED_AREA_TOPDOWN; + info.length = len; + info.low_limit = FIRST_USER_ADDRESS; + info.high_limit = mm->mmap_base; + info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; + info.align_offset = pgoff << PAGE_SHIFT; + addr = vm_unmapped_area(&info); + + /* + * A failed mmap() very likely causes application failure, + * so fall back to the bottom-up function here. This scenario + * can happen with large stack limits and large mmap() + * allocations. + */ + if (addr & ~PAGE_MASK) { + VM_BUG_ON(addr != -ENOMEM); + info.flags = 0; + info.low_limit = mm->mmap_base; + info.high_limit = TASK_SIZE; + addr = vm_unmapped_area(&info); + } + + return addr; +} + +``` + +#### 关键点2 +```C +/* + * Search for an unmapped address range. + * + * We are looking for a range that: + * - does not intersect with any VMA; + * - is contained within the [low_limit, high_limit) interval; + * - is at least the desired size. + * - satisfies (begin_addr & align_mask) == (align_offset & align_mask) + */ +unsigned long vm_unmapped_area(struct vm_unmapped_area_info *info) +{ + unsigned long addr; + + if (info->flags & VM_UNMAPPED_AREA_TOPDOWN) + addr = unmapped_area_topdown(info); + else + addr = unmapped_area(info); + + trace_vm_unmapped_area(addr, info); + return addr; +} + +static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long length, low_limit, high_limit, gap_start, gap_end; + + /* Adjust search length to account for worst case alignment overhead */ + length = info->length + info->align_mask; + if (length < info->length) + return -ENOMEM; + + /* + * Adjust search limits by the desired length. + * See implementation comment at top of unmapped_area(). + */ + // 1. high_limit: 申请的region的addr_end不能大于high_limit + // 2. low_limit:申请的region的addr_start不能小于low_limit + gap_end = info->high_limit; + if (gap_end < length) + return -ENOMEM; + high_limit = gap_end - length; + + if (info->low_limit > high_limit) + return -ENOMEM; + low_limit = info->low_limit + length; + + /* Check highest gap, which does not precede any rbtree node */ + // 3. 当前进程使用的最高的地址,如果小于high_limit,那[highest_vm_end,最初的hign_limit)都是干净没用过的,并且大小合适,按规则取一块就行。 + // 4. 但是有了安全内存: gap_start = max(mm->highest_vm_end, xpm_va_region->end),xpm_va_region->也是要和最大使用的vma较量较量,虽然没有添加到vma链表里面,但是这个坑我已经占了 + + gap_start = mm->highest_vm_end; + if (gap_start <= high_limit) + goto found_highest; + + // 5. 发现没有合适的干净的ummaped内存,那只能在vma之间卑微的找合适的gap了 + /* Check if rbtree root looks promising */ + if (RB_EMPTY_ROOT(&mm->mm_rb)) + return -ENOMEM; + vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb); + if (vma->rb_subtree_gap < length) + return -ENOMEM; + + while (true) { + /* Visit right subtree if it looks promising */ + gap_start = vma->vm_prev ? vm_end_gap(vma->vm_prev) : 0; + if (gap_start <= high_limit && vma->vm_rb.rb_right) { + struct vm_area_struct *right = + rb_entry(vma->vm_rb.rb_right, + struct vm_area_struct, vm_rb); + if (right->rb_subtree_gap >= length) { + vma = right; + continue; + } + } + + // 6. 找到一个最接近high_limit的并且满足length长度的gap,左侧prev vma的end地址就是gap_start,右侧vma的start地址就是gap_end,如果gap_end也满足low_limit的限制,那后面再校验下gap的范围就找到了。 +check_current: + /* Check if current node has a suitable gap */ + gap_end = vm_start_gap(vma); + if (gap_end < low_limit) + return -ENOMEM; + // 7. 感觉这个校验没啥意义,但是这块需要加上xpm的校验,即不能落到xpm安全内存范围内,才算找到了,要不然继续循环 + // 条件: 不能与安全内存相交、不能在安全内存里面 + if (gap_start <= high_limit && + gap_end > gap_start && gap_end - gap_start >= length) + goto found; + + /* Visit left subtree if it looks promising */ + if (vma->vm_rb.rb_left) { + struct vm_area_struct *left = + rb_entry(vma->vm_rb.rb_left, + struct vm_area_struct, vm_rb); + if (left->rb_subtree_gap >= length) { + vma = left; + continue; + } + } + + /* Go back up the rbtree to find next candidate node */ + while (true) { + struct rb_node *prev = &vma->vm_rb; + if (!rb_parent(prev)) + return -ENOMEM; + vma = rb_entry(rb_parent(prev), + struct vm_area_struct, vm_rb); + if (prev == vma->vm_rb.rb_right) { + gap_start = vma->vm_prev ? + vm_end_gap(vma->vm_prev) : 0; + goto check_current; + } + } + } + +found: + /* We found a suitable gap. Clip it with the original high_limit. */ + if (gap_end > info->high_limit) + gap_end = info->high_limit; + +found_highest: + /* Compute highest gap address at the desired alignment */ + gap_end -= info->length; + gap_end -= (gap_end - info->align_offset) & info->align_mask; + + VM_BUG_ON(gap_end < info->low_limit); + VM_BUG_ON(gap_end < gap_start); + return gap_end; +} + +static unsigned long unmapped_area(struct vm_unmapped_area_info *info) +{ + /* + * We implement the search by looking for an rbtree node that + * immediately follows a suitable gap. That is, + * - gap_start = vma->vm_prev->vm_end <= info->high_limit - length; + * - gap_end = vma->vm_start >= info->low_limit + length; + * - gap_end - gap_start >= length + */ + + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long length, low_limit, high_limit, gap_start, gap_end; + + /* Adjust search length to account for worst case alignment overhead */ + length = info->length + info->align_mask; + if (length < info->length) + return -ENOMEM; + + /* Adjust search limits by the desired length */ + if (info->high_limit < length) + return -ENOMEM; + high_limit = info->high_limit - length; + + if (info->low_limit > high_limit) + return -ENOMEM; + low_limit = info->low_limit + length; + + /* Check if rbtree root looks promising */ + if (RB_EMPTY_ROOT(&mm->mm_rb)) + goto check_highest; + vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb); + if (vma->rb_subtree_gap < length) + goto check_highest; + + while (true) { + /* Visit left subtree if it looks promising */ + gap_end = vm_start_gap(vma); + if (gap_end >= low_limit && vma->vm_rb.rb_left) { + struct vm_area_struct *left = + rb_entry(vma->vm_rb.rb_left, + struct vm_area_struct, vm_rb); + if (left->rb_subtree_gap >= length) { + vma = left; + continue; + } + } + + gap_start = vma->vm_prev ? vm_end_gap(vma->vm_prev) : 0; +check_current: + /* Check if current node has a suitable gap */ + if (gap_start > high_limit) + return -ENOMEM; + if (gap_end >= low_limit && + gap_end > gap_start && gap_end - gap_start >= length) + goto found; + + /* Visit right subtree if it looks promising */ + if (vma->vm_rb.rb_right) { + struct vm_area_struct *right = + rb_entry(vma->vm_rb.rb_right, + struct vm_area_struct, vm_rb); + if (right->rb_subtree_gap >= length) { + vma = right; + continue; + } + } + + /* Go back up the rbtree to find next candidate node */ + while (true) { + struct rb_node *prev = &vma->vm_rb; + if (!rb_parent(prev)) + goto check_highest; + vma = rb_entry(rb_parent(prev), + struct vm_area_struct, vm_rb); + if (prev == vma->vm_rb.rb_left) { + gap_start = vm_end_gap(vma->vm_prev); + gap_end = vm_start_gap(vma); + goto check_current; + } + } + } + +check_highest: + /* Check highest gap, which does not precede any rbtree node */ + gap_start = mm->highest_vm_end; + gap_end = ULONG_MAX; /* Only for VM_BUG_ON below */ + if (gap_start > high_limit) + return -ENOMEM; + +found: + /* We found a suitable gap. Clip it with the original low_limit. */ + if (gap_start < info->low_limit) + gap_start = info->low_limit; + + /* Adjust gap address to the desired alignment */ + gap_start += (info->align_offset - gap_start) & info->align_mask; + + VM_BUG_ON(gap_start + info->length > info->high_limit); + VM_BUG_ON(gap_start + info->length > gap_end); + return gap_start; +} +``` + + + +#### 2. 将虚拟地址范围从vma自动分配中剔除 +#### 3. 将获取的虚拟地址范围返回为用户态 +#### /proc目录下文件创建 +**1. proc文件创建&读写参考:** +```C +static ssize_t oom_score_adj_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct task_struct *task = get_proc_task(file_inode(file)); + char buffer[PROC_NUMBUF]; + short oom_score_adj = OOM_SCORE_ADJ_MIN; + size_t len; + + if (!task) + return -ESRCH; + oom_score_adj = task->signal->oom_score_adj; + put_task_struct(task); + len = snprintf(buffer, sizeof(buffer), "%hd\n", oom_score_adj); + return simple_read_from_buffer(buf, count, ppos, buffer, len); +} + +static const struct file_operations proc_oom_score_adj_operations = { + .read = oom_score_adj_read, + .write = oom_score_adj_write, + .llseek = default_llseek, +}; + +``` +**2. mmap的参考** +```C +static void show_vma_header_prefix(struct seq_file *m, + unsigned long start, unsigned long end, + vm_flags_t flags, unsigned long long pgoff, + dev_t dev, unsigned long ino) +{ + seq_setwidth(m, 25 + sizeof(void *) * 6 - 1); + seq_put_hex_ll(m, NULL, start, 8); + seq_put_hex_ll(m, "-", end, 8); + seq_putc(m, ' '); + seq_putc(m, flags & VM_READ ? 'r' : '-'); + seq_putc(m, flags & VM_WRITE ? 'w' : '-'); + seq_putc(m, flags & VM_EXEC ? 'x' : '-'); + seq_putc(m, flags & VM_MAYSHARE ? 's' : 'p'); + seq_put_hex_ll(m, " ", pgoff, 8); + seq_put_hex_ll(m, " ", MAJOR(dev), 2); + seq_put_hex_ll(m, ":", MINOR(dev), 2); + seq_put_decimal_ull(m, " ", ino); + seq_putc(m, ' '); +} +``` + +### 进程管理 +#### HAP进程fork流程中需要设置进程调试标识&策略函数 +hap应用通过ioctl的方式设置调试标识&管控策略。 +#### 普通二进制进程需要在exec中设置进程调试标识&策略函数 +普通二进制需要在exec系统调用流程中添加设置进程标识和管控策略的函数调调用,security_bprm_check LSM函数处进行拦截 +#### 在mmap中 \ No newline at end of file -- Gitee From 1dd50503b9c2106da6ce22ccf336ca0948253129 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Mon, 3 Apr 2023 10:17:14 +0800 Subject: [PATCH 02/35] update xpm.h Change-Id: Ic82d013d5b87316e99df71da6abb157c929931f9 --- fs/proc/base.c | 2 +- include/linux/xpm.h | 62 ++++++++++++++++++++++++++++++++++++ mm/mmap.c | 2 +- security/selinux/hooks.c | 2 +- security/xpm/core/xpm_api.c | 2 +- security/xpm/core/xpm_main.c | 2 +- security/xpm/core/xpm_misc.c | 2 +- 7 files changed, 68 insertions(+), 6 deletions(-) create mode 100755 include/linux/xpm.h diff --git a/fs/proc/base.c b/fs/proc/base.c index 1f4f2fe1e4ce..46f0208e0312 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -100,7 +100,7 @@ #include #include #include -#include +#include #include #include "internal.h" #include "fd.h" diff --git a/include/linux/xpm.h b/include/linux/xpm.h new file mode 100755 index 000000000000..00cc1b6b5df2 --- /dev/null +++ b/include/linux/xpm.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + +#ifndef _XPM_COMMON_H +#define _XPM_COMMON_H + +#include +#include + +/* xpm internal return values */ +#define XPM_SUCCESS 0 +#define XPM_ERROR (-1) +#define XPM_SIG_ERROR (-2) + +#ifdef CONFIG_XPM_PERMISSIVE +#define XPM_RET(rc) (XPM_SUCCESS) +#else +#define XPM_RET(rc) (rc) +#endif + +extern const struct file_operations proc_xpm_region_operations; + +/** + * check whether input address range is out of the xpm region + */ +#ifdef CONFIG_XPM +extern bool is_xpm_region_outer(unsigned long addr_start, + unsigned long addr_end, unsigned long flags); + +/** + * set xpm region info if has MAP_XPM flag + */ +extern void set_xpm_region_info(struct vm_unmapped_area_info *info, + unsigned long flags); + +/** + * check whether mmap vma has a signature + */ +extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); +#else +bool is_xpm_region_outer(unsigned long addr_start, + unsigned long addr_end, unsigned long flags); +{ + return true; +} + +void set_xpm_region_info(struct vm_unmapped_area_info *info, + unsigned long flags) +{ + return; +} + +static int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) +{ + return XPM_SUCCESS; +} + +#endif + +#endif /* _XPM_COMMON_H */ \ No newline at end of file diff --git a/mm/mmap.c b/mm/mmap.c index 3430b625a603..50404479a95e 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -48,7 +48,7 @@ #include #include #include -#include +#include #include #include diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b3da278261c7..99dfd25f86c8 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -92,7 +92,7 @@ #include #include #include -#include +#include #include "avc.h" #include "objsec.h" diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index 174282972eb2..92a594df0cbc 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include #include diff --git a/security/xpm/core/xpm_main.c b/security/xpm/core/xpm_main.c index 9420e900bb28..157c12a94f08 100755 --- a/security/xpm/core/xpm_main.c +++ b/security/xpm/core/xpm_main.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "xpm_misc.h" #include "xpm_log.h" diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index a3b2d97a3040..6f82da2f878c 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include "xpm_log.h" #define XPM_SET_REGION _IOW('x', 0x01, struct xpm_region_info) -- Gitee From df1ab1abfb9f9175700bc42634d0979ba427919f Mon Sep 17 00:00:00 2001 From: zhang pan Date: Sun, 2 Apr 2023 11:30:42 +0800 Subject: [PATCH 03/35] Merge the XPM integrity feature Change-Id: I95bc9fa47821224e2e6a6603f8508876e228a348 --- include/linux/page-flags.h | 19 ++ include/linux/xpm.h | 80 ++++++- include/linux/xpm_api.h | 62 ------ include/trace/events/mmflags.h | 8 + mm/ksm.c | 4 + mm/memory.c | 34 ++- mm/migrate.c | 7 + security/xpm/Makefile | 2 + security/xpm/core/xpm_api.c | 11 +- security/xpm/core/xpm_integrity.c | 120 +++++++++++ security/xpm/core/xpm_log.h | 30 --- security/xpm/core/xpm_main.c | 9 +- security/xpm/core/xpm_misc.c | 11 +- security/xpm/core/xpm_report.c | 347 ++++++++++++++++++++++++++++++ security/xpm/core/xpm_report.h | 172 +++++++++++++++ 15 files changed, 800 insertions(+), 116 deletions(-) mode change 100755 => 100644 include/linux/xpm.h delete mode 100755 include/linux/xpm_api.h create mode 100644 security/xpm/core/xpm_integrity.c delete mode 100644 security/xpm/core/xpm_log.h create mode 100644 security/xpm/core/xpm_report.c create mode 100644 security/xpm/core/xpm_report.h diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index dcf83c01f57b..98fefde53fb6 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -145,6 +145,10 @@ enum pageflags { #endif #ifdef CONFIG_MEM_PURGEABLE PG_purgeable, +#endif +#ifdef CONFIG_XPM + PG_xpm_readonly, + PG_xpm_writetainted, #endif __NR_PAGEFLAGS, @@ -350,6 +354,14 @@ __PAGEFLAG(Slab, slab, PF_NO_TAIL) __PAGEFLAG(SlobFree, slob_free, PF_NO_TAIL) PAGEFLAG(Checked, checked, PF_NO_COMPOUND) /* Used by some filesystems */ +#ifdef CONFIG_XPM +PAGEFLAG(XPMReadonly, xpm_readonly, PF_HEAD) +PAGEFLAG(XPMWritetainted, xpm_writetainted, PF_HEAD) +#else +PAGEFLAG_FALSE(Readonly) +PAGEFLAG_FALSE(Writetainted) +#endif + /* Xen */ PAGEFLAG(Pinned, pinned, PF_NO_COMPOUND) TESTSCFLAG(Pinned, pinned, PF_NO_COMPOUND) @@ -843,11 +855,18 @@ static inline void ClearPageSlabPfmemalloc(struct page *page) * Flags checked when a page is freed. Pages being freed should not have * these flags set. It they are, there is a problem. */ +#ifdef CONFIG_XPM +#define __XPM_PAGE_FLAGS (1UL << PG_xpm_readonly | 1UL << PG_xpm_writetainted) +#else +#define __XPM_PAGE_FLAGS 0 +#endif + #define PAGE_FLAGS_CHECK_AT_FREE \ (1UL << PG_lru | 1UL << PG_locked | \ 1UL << PG_private | 1UL << PG_private_2 | \ 1UL << PG_writeback | 1UL << PG_reserved | \ 1UL << PG_slab | 1UL << PG_active | \ + __XPM_PAGE_FLAGS | \ 1UL << PG_unevictable | __PG_MLOCKED) /* diff --git a/include/linux/xpm.h b/include/linux/xpm.h old mode 100755 new mode 100644 index 00cc1b6b5df2..ea43f7b63478 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -3,15 +3,16 @@ * Copyright (c) 2023 Huawei Device Co., Ltd. */ -#ifndef _XPM_COMMON_H -#define _XPM_COMMON_H +#ifndef _XPM_H +#define _XPM_H -#include +#include +#include #include /* xpm internal return values */ #define XPM_SUCCESS 0 -#define XPM_ERROR (-1) +#define XPM_FAULT (-1) #define XPM_SIG_ERROR (-2) #ifdef CONFIG_XPM_PERMISSIVE @@ -22,10 +23,31 @@ extern const struct file_operations proc_xpm_region_operations; +#define XPM_PROC_CONTENT_LEN 33 + +#define XPM_TAG "xpm_kernel" +#define XPM_INFO "I" +#define XPM_ERROR "E" +#define XPM_DEBUG "D" + +#define xpm_log_info(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ + XPM_INFO, XPM_TAG, __func__, ##args) + +#define xpm_log_error(fmt, args...) pr_err("[%s/%s]%s: " fmt "\n", \ + XPM_ERROR, XPM_TAG, __func__, ##args) + +#ifdef CONFIG_XPM_DEBUG +#define xpm_log_debug(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ + XPM_DEBUG, XPM_TAG, __func__, ##args) +#else +#define xpm_log_debug(fmt, args...) no_printk(fmt, ##args) +#endif + +#define XPM_PRINT_COUNT 10000 +#ifdef CONFIG_XPM /** * check whether input address range is out of the xpm region */ -#ifdef CONFIG_XPM extern bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags); @@ -39,7 +61,20 @@ extern void set_xpm_region_info(struct vm_unmapped_area_info *info, * check whether mmap vma has a signature */ extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); + +/* + * Check the confliction of a page's xpm flags, make sure a process will not map + * any RO page into a writable vma or a WT page into a execuable/XPM memory region. + */ +vm_fault_t xpm_integrity_check(struct vm_fault *vmf, struct page * page); +vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, struct page * page); +void xpm_integrity_update(struct vm_fault *vmf, struct page * page); +void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, char *extra_info); + +bool xpm_integrity_equal(struct page *page, struct page *kpage); + #else + bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags); { @@ -52,11 +87,42 @@ void set_xpm_region_info(struct vm_unmapped_area_info *info, return; } -static int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) +static int xpm_sig_validate(struct vm_area_struct *vma, + unsigned long prot) { return XPM_SUCCESS; } +static inline bool xpm_integrity_equal(struct page *page, + struct page *kpage) +{ + return 0; +} + +static inline vm_fault_t xpm_integrity_check(struct vm_fault *vmf, + struct page * page) +{ + return 0; +} + +static inline vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, + struct page * page) +{ + return 0; +} + +static inline void xpm_integrity_update(struct vm_fault *vmf, + struct page * page) +{ + return; +} + +static inline void xpm_fault_page_info(struct vm_fault *vmf, + struct page *page, char *extra_info) +{ + return; +} + #endif -#endif /* _XPM_COMMON_H */ \ No newline at end of file +#endif /* _XPM_H */ \ No newline at end of file diff --git a/include/linux/xpm_api.h b/include/linux/xpm_api.h deleted file mode 100755 index 00cc1b6b5df2..000000000000 --- a/include/linux/xpm_api.h +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ -/* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ - -#ifndef _XPM_COMMON_H -#define _XPM_COMMON_H - -#include -#include - -/* xpm internal return values */ -#define XPM_SUCCESS 0 -#define XPM_ERROR (-1) -#define XPM_SIG_ERROR (-2) - -#ifdef CONFIG_XPM_PERMISSIVE -#define XPM_RET(rc) (XPM_SUCCESS) -#else -#define XPM_RET(rc) (rc) -#endif - -extern const struct file_operations proc_xpm_region_operations; - -/** - * check whether input address range is out of the xpm region - */ -#ifdef CONFIG_XPM -extern bool is_xpm_region_outer(unsigned long addr_start, - unsigned long addr_end, unsigned long flags); - -/** - * set xpm region info if has MAP_XPM flag - */ -extern void set_xpm_region_info(struct vm_unmapped_area_info *info, - unsigned long flags); - -/** - * check whether mmap vma has a signature - */ -extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); -#else -bool is_xpm_region_outer(unsigned long addr_start, - unsigned long addr_end, unsigned long flags); -{ - return true; -} - -void set_xpm_region_info(struct vm_unmapped_area_info *info, - unsigned long flags) -{ - return; -} - -static int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) -{ - return XPM_SUCCESS; -} - -#endif - -#endif /* _XPM_COMMON_H */ \ No newline at end of file diff --git a/include/trace/events/mmflags.h b/include/trace/events/mmflags.h index 2332482f7df7..d38e69ef8aad 100644 --- a/include/trace/events/mmflags.h +++ b/include/trace/events/mmflags.h @@ -61,6 +61,12 @@ #define IF_HAVE_PG_PURGEABLE(flag,string) #endif +#ifdef CONFIG_XPM +#define IF_HAVE_PG_XPM_INTEGRITY(flag,string) ,{1UL << flag, string} +#else +#define IF_HAVE_PG_XPM_INTEGRITY(flag,string) +#endif + #ifdef CONFIG_MMU #define IF_HAVE_PG_MLOCK(flag,string) ,{1UL << flag, string} #else @@ -114,6 +120,8 @@ {1UL << PG_swapbacked, "swapbacked" }, \ {1UL << PG_unevictable, "unevictable" } \ IF_HAVE_PG_PURGEABLE(PG_purgeable, "purgeable" ) \ +IF_HAVE_PG_XPM_INTEGRITY(PG_xpm_readonly, "readonly") \ +IF_HAVE_PG_XPM_INTEGRITY(PG_xpm_writetainted, "writetained") \ IF_HAVE_PG_MLOCK(PG_mlocked, "mlocked" ) \ IF_HAVE_PG_UNCACHED(PG_uncached, "uncached" ) \ IF_HAVE_PG_HWPOISON(PG_hwpoison, "hwpoison" ) \ diff --git a/mm/ksm.c b/mm/ksm.c index 25b8362a4f89..24f720ee00ed 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -1212,6 +1212,10 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, if (!PageAnon(page)) goto out; + /* Check XPM flags */ + if(xpm_integrity_equal(page, kpage)) + goto out; + /* * We need the page lock to read a stable PageSwapCache in * write_protect_page(). We use trylock_page() instead of diff --git a/mm/memory.c b/mm/memory.c index ea5741b3288f..f33de506f5b9 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -86,6 +86,7 @@ #include "pgalloc-track.h" #include "internal.h" +#include #if defined(LAST_CPUPID_NOT_IN_PAGE_FLAGS) && !defined(CONFIG_COMPILE_TEST) #warning Unfortunate NUMA and NUMA Balancing config, growing page-frame for last_cpupid. @@ -2942,6 +2943,9 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf) */ set_pte_at_notify(mm, vmf->address, vmf->pte, entry); update_mmu_cache(vma, vmf->address, vmf->pte); + + xpm_integrity_update(vmf, new_page); + if (old_page) { /* * Only after switching the pte to the new page may @@ -3036,6 +3040,10 @@ vm_fault_t finish_mkwrite_fault(struct vm_fault *vmf) pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_NOPAGE; } + + if (xpm_integrity_validate(vmf, vmf->page)) + return VM_FAULT_SIGBUS; + wp_page_reuse(vmf); return 0; } @@ -3087,6 +3095,10 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf) return tmp; } } else { + + if (xpm_integrity_validate(vmf, vmf->page)) + return VM_FAULT_SIGBUS; + wp_page_reuse(vmf); lock_page(vmf->page); } @@ -3171,6 +3183,10 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf) * it's dark out, and we're wearing sunglasses. Hit it. */ unlock_page(page); + + if (xpm_integrity_validate(vmf, vmf->page)) + return VM_FAULT_SIGBUS; + wp_page_reuse(vmf); return VM_FAULT_WRITE; } else if (unlikely((vma->vm_flags & (VM_WRITE|VM_SHARED)) == @@ -3486,6 +3502,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) * must be called after the swap_free(), or it will never succeed. */ + if (xpm_integrity_validate(vmf, page)) { + ret = VM_FAULT_SIGBUS; + goto out_nomap; + } + inc_mm_counter_fast(vma->vm_mm, MM_ANONPAGES); dec_mm_counter_fast(vma->vm_mm, MM_SWAPENTS); pte = mk_pte(page, vma->vm_page_prot); @@ -3595,8 +3616,11 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) if (vma->vm_flags & VM_USEREXPTE) { if (do_uxpte_page_fault(vmf, &entry)) goto oom; - else + else{ + if(xpm_integrity_check(vmf, pte_page(entry))) + goto oom; goto got_page; + } } /* Use the zero-page for reads */ @@ -3672,6 +3696,10 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) setpte: if (vma->vm_flags & VM_PURGEABLE) uxpte_set_present(vma, vmf->address); + + if(!pte_special(entry)){ + xpm_integrity_update(vmf, page); + } set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry); @@ -3930,6 +3958,10 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct page *page) return VM_FAULT_NOPAGE; } + /* check the confliction of xpm integrity flags*/ + if (xpm_integrity_validate(vmf, page)) + return VM_FAULT_SIGBUS; + flush_icache_page(vma, page); entry = mk_pte(page, vma->vm_page_prot); entry = pte_sw_mkyoung(entry); diff --git a/mm/migrate.c b/mm/migrate.c index cf0e966a8faa..af0b7bf83ad2 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -636,6 +636,13 @@ void migrate_page_states(struct page *newpage, struct page *page) if (page_is_idle(page)) set_page_idle(newpage); + /* Migrate the page's xpm state */ + if(PageXPMWritetainted(page)) + SetPageXPMWritetainted(newpage); + + if(PageXPMReadonly(page)) + SetPageXPMReadonly(newpage); + /* * Copy NUMA information to the new page, to prevent over-eager * future migrations of this same page. diff --git a/security/xpm/Makefile b/security/xpm/Makefile index a8b77ba0985e..af8376926767 100755 --- a/security/xpm/Makefile +++ b/security/xpm/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_XPM) += \ core/xpm_main.o \ core/xpm_misc.o \ core/xpm_api.o \ + core/xpm_integrity.o \ + core/xpm_report.o \ validator/elf_code_segment_info.o \ validator/exec_signature_info.o diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index 92a594df0cbc..8e4dc5f36ed2 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -12,7 +12,8 @@ #include #include #include -#include "xpm_log.h" +#include +#include "xpm_report.h" #include "exec_signature_info.h" #define XPM_REGION_STR_LEN 33 @@ -92,7 +93,7 @@ static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, if (!segment) { xpm_log_error("elf executable segemnt is NULL"); - return XPM_ERROR; + return XPM_FAULT; } vm_addr_start = vma->vm_pgoff << PAGE_SHIFT; @@ -108,7 +109,7 @@ static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, return XPM_SUCCESS; } - return XPM_ERROR; + return XPM_FAULT; } int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) @@ -119,7 +120,7 @@ int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) if (!vma) { xpm_log_error("input vma is NULL"); - return XPM_ERROR; + return XPM_FAULT; } is_abc = vma->vm_flags & VM_XPM; @@ -136,7 +137,7 @@ int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) if (ret) { xpm_log_error("get executable file signature info failed," "ret = 0x%x", ret); - return XPM_ERROR; + return XPM_FAULT; } if (info == NULL) { diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c new file mode 100644 index 000000000000..284bc7719c39 --- /dev/null +++ b/security/xpm/core/xpm_integrity.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + */ + +#include +#include +#include +#include +#include "xpm_report.h" + +#ifdef CONFIG_XPM + +static bool is_xpm_readonly(struct vm_area_struct *vma) +{ + struct xpm_region *xpm_region; + struct mm_struct *mm; + + mm = current->mm; + if (!mm) { + xpm_log_error("smm_struct is null"); + return 0; + } + + xpm_region = &mm->xpm_region; + + /* 1. XPM Secure Region */ + if (vma->vm_start <= xpm_region->addr_start && + xpm_region->addr_end <= vma->vm_end) + return 1; + + /* 2. !Anonymous && Executable */ + if (!vma_is_anonymous(vma) && (vma->vm_flags & VM_EXEC)) + return 1; + + return 0; +} + +vm_fault_t xpm_integrity_check(struct vm_fault *vmf, + struct page *page) +{ + struct vm_area_struct *vma; + + if (!page) + return 0; + + vma = vmf->vma; + + /* Integrity violation: map a readonly page into a writable VMA */ + if ((vma->vm_flags & VM_WRITE) && PageXPMReadonly(page)) { + report_integrity_violation(vmf, page, 1); + return XPM_RET(VM_FAULT_SIGBUS); + } + + /* Integrity violation: map a writetained page into a RO VMA */ + if (PageXPMWritetainted(page) && is_xpm_readonly(vma)) { + report_integrity_violation(vmf, page, 0); + return XPM_RET(VM_FAULT_SIGBUS); + } + + return 0; +} + +void xpm_integrity_update(struct vm_fault *vmf, struct page *page) +{ + struct vm_area_struct *vma = vmf->vma; + + if ((vma->vm_flags & VM_WRITE) && !PageXPMWritetainted(page)) { + SetPageXPMWritetainted(page); + +#ifdef CONFIG_XPM_DEBUG + xpm_fault_page_info(vmf, page, "[SET WT]"); + #endif + return; + } + + if (is_xpm_readonly(vma) && !PageXPMReadonly(page)) { + SetPageXPMReadonly(page); + +#ifdef CONFIG_XPM_DEBUG + xpm_fault_page_info(vmf, page, "[SET RD]"); +#endif + } + +} + +vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, + struct page *page) +{ + vm_fault_t ret; + + if (!page) + return 0; + + ret = xpm_integrity_check(vmf, page); + if (ret) + return ret; + + xpm_integrity_update(vmf, page); + + return 0; +} + +/* + * If the xpm integrity flags of these two pages are equal, return 0, + * otherwise return 1 + */ +bool xpm_integrity_equal(struct page *page, struct page *kpage) +{ + if (!kpage) + return 0; + + if ((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) + (PageXPMReadonly(page) != PageXPMReadonly(kpage))) + return 1; + + return 0; +} + +#endif diff --git a/security/xpm/core/xpm_log.h b/security/xpm/core/xpm_log.h deleted file mode 100644 index e76929009797..000000000000 --- a/security/xpm/core/xpm_log.h +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ -/* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ - -#ifndef _XPM_LOG_H -#define _XPM_LOG_H - -#include - -#define XPM_TAG "xpm_kernel" - -#define INFO_TAG "I" -#define ERROR_TAG "E" -#define DEBUG_TAG "D" - -#define xpm_log_info(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ - INFO_TAG, XPM_TAG, __func__, ##args) - -#define xpm_log_error(fmt, args...) pr_err("[%s/%s]%s: " fmt "\n", \ - ERROR_TAG, XPM_TAG, __func__, ##args) - -#ifdef CONFIG_XPM_DEBUG -#define xpm_log_debug(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ - DEBUG_TAG, XPM_TAG, __func__, ##args) -#else -#define xpm_log_debug(fmt, args...) no_printk(fmt, ##args) -#endif - -#endif /* _XPM_LOG_H */ \ No newline at end of file diff --git a/security/xpm/core/xpm_main.c b/security/xpm/core/xpm_main.c index 157c12a94f08..7b9117fdec0b 100755 --- a/security/xpm/core/xpm_main.c +++ b/security/xpm/core/xpm_main.c @@ -9,17 +9,20 @@ #include #include #include "xpm_misc.h" -#include "xpm_log.h" +#include "xpm_report.h" static int __init xpm_module_init(void) { int ret; ret = xpm_register_misc_device(); - if (ret) + if (ret){ xpm_log_error("xpm register misc device failed, ret = %d", ret); - else + report_xpm_init_failed(ret); + } + else{ xpm_log_info("xpm module init success"); + } return ret; } diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index 6f82da2f878c..a04e6cd9acda 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -15,15 +15,15 @@ #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; + int ret = XPM_SUCCESS; unsigned long addr; - struct mm_struct *mm= current->mm; + struct mm_struct *mm = current->mm; if (mmap_write_lock_killable(mm)) return -EINTR; @@ -31,7 +31,6 @@ static int xpm_set_region(unsigned long addr_base, unsigned long length) if ((mm->xpm_region.addr_start != 0) || (mm->xpm_region.addr_end != 0)) { xpm_log_info("xpm region has been set"); - ret = XPM_SUCCESS; goto exit; } @@ -116,10 +115,6 @@ int xpm_register_misc_device(void) int ret; ret = misc_register(&xpm_misc); - if (unlikely(ret)) - xpm_log_error("xpm register misc device failed, ret = %d", ret); - else - xpm_log_info("xpm register misc device success"); return ret; } diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c new file mode 100644 index 000000000000..34706b8c3146 --- /dev/null +++ b/security/xpm/core/xpm_report.c @@ -0,0 +1,347 @@ +#include +#include +#include +#include +#include "xpm_report.h" + +#ifdef CONFIG_XPM +static bool report_rmap_page_info(struct page *page, + struct vm_area_struct *vma, unsigned long addr, void *write_offsense) +{ + int pid; + struct task_struct *tsk; + unsigned long flags; + + tsk = vma->vm_mm->owner; + //mm->owner could be clear to NULL by mm_update_next_owner + pid = tsk ? tsk->pid : -1 ; + flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; + + xpm_log_error(" --> mapped in PID:%d, VADDR: 0x%lx, VMA[flags:0x%lx] \ + [range:0x%lx:0x%lx:0x%lx]", pid, addr, flags, vma->vm_start, + vma->vm_end, (vma->vm_end - vma->vm_start)); + + return true; +} + +void report_integrity_violation(struct vm_fault *vmf, + struct page *page, bool write_offsense) +{ + struct vm_area_struct *vma; + char * page_type; + char path_buf[512]; + char *filename; + char *xpm_conflic_type; + unsigned long flags; + + struct rmap_walk_control rwc = { + .rmap_one = report_rmap_page_info, + .arg = (void*)&write_offsense, + }; + + vma = vmf->vma; + + //get filename + if(!vma_is_anonymous(vma)){ + struct dentry *dentry; + dentry = vma->vm_file->f_path.dentry; + filename = dentry_path(dentry, path_buf, sizeof(path_buf)); + + if(IS_ERR(filename)) + filename = "unknow file"; + }else{ + filename = "anon_file"; + } + + + //get page type + page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; + + //xpm conflic type + xpm_conflic_type = write_offsense? "RO" : "WT"; + + //vma flags + flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; + + //map readonly page into execuable page + xpm_log_error(" [%s] %s Page at 0x%lx [%s] [ref:%d]:0x%lx-> PID:%d,\ + VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx]", + xpm_conflic_type, page_type, page_to_pfn(page), filename, + page->_refcount, page_index(page), current->pid, vmf->address, flags, + vma->vm_start, vma->vm_end + ); + + rmap_walk(page, &rwc); + + return; +} + +void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, + char *extra_info) +{ + struct vm_area_struct *vma; + char * page_type; + char path_buf[512]; + char *filename; + unsigned long flags; + + // static int count = 0; + // if(count++ > XPM_PRINT_COUNT) + // return; + + vma = vmf->vma; + + //get filename + if(!vma_is_anonymous(vma)){ + struct dentry *dentry; + struct file * file; + file = vma->vm_file; + if(file){ + dentry =file->f_path.dentry; + filename = dentry_path(dentry, path_buf, sizeof(path_buf)); + if(IS_ERR(filename)) + filename = "unknow file"; + }else{ + filename = "vma file miss"; + } + }else{ + filename = "anon_file"; + } + + if(!extra_info) + extra_info = ""; + + //vma flags + flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; + + //get page type + if(page){ + page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; + + //map readonly page into execuable page + xpm_log_error("%s -->[%s] %s Page at 0x%lx [%s] [ref:%d]:0x%lx-> PID:%d, \ + VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", + extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", + page_type, page_to_pfn(page), filename, page->_refcount, page_index(page), + current->pid, vmf->address, flags ,vma->vm_start, vma->vm_end, + vma->vm_end - vma->vm_start + ); + + }else { + xpm_log_error("%s -->[%s] [%s]: null page -> PID:%d, VADDR: 0x%lx, \ + VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", + extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", + filename, current->pid, vmf->address, flags ,vma->vm_start, vma->vm_end, + vma->vm_end - vma->vm_start + ); + } + + return; +} + +void report_xpm_init_failed(int err_code) +{ + ktime_t cur_time; + event_info *event = NULL; + + event = (event_info *)kzalloc(sizeof(event_info) + + MAX_CONTENT_LEN, GFP_KERNEL); + + if (event == NULL) { + xpm_log_error("kmalloc event_info failed"); + return; + } + + cur_time = ktime_get_real(); + + event->event_id = XPM_SG_EVENT_ID; + event->version = XPM_SG_VERSION; + + snprintf(event->content, MAX_CONTENT_LEN, XPM_INIT_FAILED_JSON, + XPM_INIT_FAILED, cur_time, err_code); + + event->content_len = strlen(event->content) + 1; + + xpm_report_security_info(event); + + kfree(event); + return; +} + +void report_file_format_damaged(struct file *file, bool signature, char *log) +{ + char *filename, *buf; + ktime_t cur_time; + struct dentry * dentry; + event_info *event; + + if(!file) + return; + + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); + if (!event) { + xpm_log_error("kmalloc event_info failed"); + return; + } + + buf = (char*)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + if (!buf){ + xpm_log_error("kmalloc failed"); + goto free_event; + } + + cur_time = ktime_get_real(); + + dentry =file->f_path.dentry; + filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); + if(IS_ERR(filename)){ + xpm_log_error("unknow file"); + goto free_out; + } + + event->event_id = XPM_SG_EVENT_ID; + event->version = XPM_SG_VERSION; + + snprintf(event->content, MAX_CONTENT_LEN, + XPM_FILE_FORMAT_DAMAGED_JSON, + XPM_FILE_FORMAT_DAMAGED, cur_time, + current->pid, filename, signature, log ? log : ""); + + event->content_len = strlen(event->content) + 1; + + xpm_report_security_info(event); + +free_out: + kfree(buf); +free_event: + kfree(event); + return; +} + +void report_code_map_failed(int err_code, struct file *file, bool signature, + int codetype, int prot, pgoff_t pgoff, size_t size) +{ + char *filename, *buf; + ktime_t cur_time; + event_info *event; + struct dentry * dentry; + + if(!file) + return; + + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, + GFP_KERNEL); + if (!event) { + xpm_log_error("kmalloc event_info failed"); + goto free_event; + } + + buf = (char*)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + if (!buf) { + xpm_log_error("kmalloc failed"); + goto out; + } + + cur_time = ktime_get_real(); + + dentry = file->f_path.dentry; + filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); + if (IS_ERR(filename)) { + xpm_log_error("unknow file"); + goto out; + } + + event->event_id = XPM_SG_EVENT_ID; + event->version = XPM_SG_VERSION; + + snprintf(event->content, MAX_CONTENT_LEN, XPM_MAP_FAILED_JSON, + XPM_CODE_MAP_FAILED, cur_time, err_code, + current->pid, filename, signature, codetype, prot, pgoff, size); + + event->content_len = strlen(event->content) + 1; + + xpm_report_security_info(event); + +out: + kfree(buf); +free_event: + kfree(event); + return; +} + +static int inline get_xpm_flags(struct page *page){ + if(PageXPMReadonly(page)) + return 1; + + if( PageXPMWritetainted(page)) + return 2; + + return 0; +} + +void report_integrity_tampered(struct vm_fault *vmf, struct page *page, + bool violate_type) +{ + char *filename, *buf, *page_type; + struct vm_area_struct *vma; + event_info *event; + ktime_t cur_time; + + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, + GFP_KERNEL); + if (event == NULL) { + xpm_log_error("kmalloc event_info failed"); + goto free_event; + } + + buf = (char*)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + if (!buf) { + xpm_log_error("kmalloc buf failed"); + goto out; + } + + cur_time = ktime_get_real(); + + vma = vmf->vma; + + if(!vma_is_anonymous(vma)){ + struct dentry *dentry; + dentry = vma->vm_file->f_path.dentry; + filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); + + if (IS_ERR(filename)) { + xpm_log_error("unknow file"); + goto out; + } + }else{ + filename = "Anon"; + } + + page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; + + event->event_id = XPM_SG_EVENT_ID; + event->version = XPM_SG_VERSION; + + snprintf(event->content, MAX_CONTENT_LEN, XPM_INTEGRITY_TAMPERED_JSON, + XPM_INTEGRITY_TAMPERED, cur_time, current->pid, get_xpm_flags(page), + page_type, filename, page->index, vma->vm_page_prot); + + event->content_len = strlen(event->content) + 1; + + xpm_report_security_info(event); + +out: + kfree(event); +free_event: + kfree(buf); + return; +} + +#ifndef _HW_KERNEL_SG_COLLECT_H_ +unsigned int xpm_report_security_info(const event_info *info){ + xpm_log_error("%s", info); + return 0; +} +#endif + +#endif \ No newline at end of file diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h new file mode 100644 index 000000000000..f37c872c322e --- /dev/null +++ b/security/xpm/core/xpm_report.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + +#ifndef _XPM_LOG_H +#define _XPM_LOG_H + +#include +#include +#include + +enum { + XPM_INIT_FAILED = 1, + XPM_FILE_FORMAT_DAMAGED, + XPM_CODE_MAP_FAILED, + XPM_INTEGRITY_TAMPERED, + XPM_STASTIC_EVENT +}; + +#ifdef _HW_KERNEL_SG_COLLECT_H_ +#include + +unsigned int inline xpm_report_security_info(const event_info *info) +{ + return report_security_info(const event_info *info); +} + +#define XPM_SG_EVENT_ID 111 +#define XPM_SG_VERSION 222 + +#else +typedef struct { + unsigned long event_id; + unsigned int version; + unsigned int content_len; + char content[0]; +} event_info; + +#define XPM_SG_EVENT_ID 0 +#define XPM_SG_VERSION 0 + +/* + * xpm_report_security_info - report xpm evernt to hievent + */ +unsigned int xpm_report_security_info(const event_info *info); +#endif /*_HW_KERNEL_SG_COLLECˇ_H_*/ + +#define XPM_EVENT_ID 0 +#define XPM_EVENT_VERSION 0 +#define MAX_CONTENT_LEN 900 + +#define JSTR(val) "\""#val"\"" +#define JSPAIR(val, format) JSTR(val) ":" #format + +#define EVENT_COM_CONT() \ + JSPAIR(event, %d) ", " \ + JSPAIR(time, %lld) ", " + +#define PROCESS_INFO \ + JSTR(process) ":{" \ + JSPAIR(pid, %d) \ + "}" + +#define FILE_INFO \ + JSTR(file) ":{" \ + JSTR(filename) ":" JSTR(%s) ", " \ + JSPAIR(signature, %d) \ + "}" + +#define MAP_INFO \ + JSTR(vma) ":{" \ + JSPAIR(code_type, %d) ", "\ + JSPAIR(flags, %lx) ", "\ + JSPAIR(pgoff, 0x%lx) ", "\ + JSPAIR(size, 0x%lx) \ + "}" + +#define PAGE_INFO \ + JSTR(page) ":{" \ + JSPAIR(xpm_flag, %d) ", " \ + JSTR(page_type) ":" JSTR(%s) ", " \ + JSTR(filename) ":" JSTR(%s) ", " \ + JSPAIR(pgoff, %d) \ + "}" + +#define EXTRA_INFO \ + JSTR(extra) ":" JSTR(%s) + +/* + * XPM_INIT_FAILED JSON format + */ +#define XPM_INIT_FAILED_JSON \ + "{" EVENT_COM_CONT() \ + JSTR(detail) ":{" \ + JSPAIR(xpm_flag, %d)\ + "}}" + +/* + * ELF_FORMAT_DAMAGED JSON format + */ +#define XPM_FILE_FORMAT_DAMAGED_JSON \ + "{" EVENT_COM_CONT() \ + JSTR(detail) ":{" \ + PROCESS_INFO "," \ + FILE_INFO "," \ + EXTRA_INFO \ + "}}" + +/* + * XPM_MAP_FAILED JSON format + */ +#define XPM_MAP_FAILED_JSON \ + "{" EVENT_COM_CONT() \ + JSTR(detail) ":{" \ + JSPAIR(fault, %d) "," \ + PROCESS_INFO "," \ + FILE_INFO "," \ + MAP_INFO \ + "}}" + +/* + * XPM_INTEGRITY_VIOLATION JSON format + */ +#define XPM_INTEGRITY_TAMPERED_JSON \ + "{" EVENT_COM_CONT() \ + JSTR(detail) ":{" \ + PROCESS_INFO","\ + PAGE_INFO ","\ + JSPAIR(vm_prot, %d) \ + "}}" + +#ifdef CONFIG_XPM +void report_integrity_violation(struct vm_fault *vmf, + struct page *page, bool write_offsense); +void report_xpm_init_failed(int errs_code); +void report_file_format_damaged(struct file *file, bool signature, char *log); +void report_code_map_failed(int err_code, struct file *file, bool signature, + int codetype, int prot, pgoff_t pgoff, size_t size); +void report_integrity_tampered(struct vm_fault *vmf, struct page *page, bool violate_type); + +#elif +void inline report_integrity_violation(struct vm_fault *vmf, + struct page *page, bool write_offsense) +{ + return; +} + +void report_xpm_init_failed(int errs_code) +{ + return; +} + +void report_file_format_damaged(struct file *file, bool signature, char *log) +{ + return; +} + +void report_code_map_failed(int err_code, struct file *file, bool signature, + int codetype, int prot, pgoff_t pgoff, size_t size) +{ + return; +} + +void report_integrity_tampered(struct vm_fault *vmf, struct page *page, + bool violate_type) +{ + return; +} +#endif + +#endif /* _XPM_LOG_H */ \ No newline at end of file -- Gitee From 11df48f0d13f11f30f77e0561144dcdd26ab7beb Mon Sep 17 00:00:00 2001 From: Li Ang <379998@qq.com> Date: Wed, 5 Apr 2023 10:51:19 +0800 Subject: [PATCH 04/35] compile error --- include/linux/xpm.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index ea43f7b63478..5bfd9c8ceaca 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -76,7 +76,7 @@ bool xpm_integrity_equal(struct page *page, struct page *kpage); #else bool is_xpm_region_outer(unsigned long addr_start, - unsigned long addr_end, unsigned long flags); + unsigned long addr_end, unsigned long flags) { return true; } @@ -125,4 +125,4 @@ static inline void xpm_fault_page_info(struct vm_fault *vmf, #endif -#endif /* _XPM_H */ \ No newline at end of file +#endif /* _XPM_H */ -- Gitee From 2e27905a657c30e4c5ee021acba90472942e9e47 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Tue, 4 Apr 2023 01:13:03 +0800 Subject: [PATCH 05/35] optimize code format Change-Id: Ia5352e032629dc0dcf50c85b3ccd002c1f068719 --- include/linux/xpm.h | 59 +++++------ mm/ksm.c | 3 +- security/xpm/core/xpm_integrity.c | 10 +- security/xpm/core/xpm_report.c | 158 ++++++++++++++++-------------- security/xpm/core/xpm_report.h | 126 +++++++++++------------- 5 files changed, 175 insertions(+), 181 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index ea43f7b63478..28cc2f191d61 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause */ /* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ #ifndef _XPM_H #define _XPM_H @@ -10,6 +10,7 @@ #include #include +#ifdef CONFIG_XPM /* xpm internal return values */ #define XPM_SUCCESS 0 #define XPM_FAULT (-1) @@ -21,8 +22,6 @@ #define XPM_RET(rc) (rc) #endif -extern const struct file_operations proc_xpm_region_operations; - #define XPM_PROC_CONTENT_LEN 33 #define XPM_TAG "xpm_kernel" @@ -43,8 +42,7 @@ extern const struct file_operations proc_xpm_region_operations; #define xpm_log_debug(fmt, args...) no_printk(fmt, ##args) #endif -#define XPM_PRINT_COUNT 10000 -#ifdef CONFIG_XPM +extern const struct file_operations proc_xpm_region_operations; /** * check whether input address range is out of the xpm region */ @@ -62,67 +60,60 @@ extern void set_xpm_region_info(struct vm_unmapped_area_info *info, */ extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); +#define XPM_PRINT_COUNT 10000 /* - * Check the confliction of a page's xpm flags, make sure a process will not map - * any RO page into a writable vma or a WT page into a execuable/XPM memory region. - */ -vm_fault_t xpm_integrity_check(struct vm_fault *vmf, struct page * page); -vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, struct page * page); -void xpm_integrity_update(struct vm_fault *vmf, struct page * page); + * Check the confliction of a page's xpm flags, make sure a process will not map + * any RO page into a writable vma or a WT page into a execuable/XPM memory region. + */ +vm_fault_t xpm_integrity_check(struct vm_fault *vmf, struct page *page); +vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, struct page *page); +void xpm_integrity_update(struct vm_fault *vmf, struct page *page); void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, char *extra_info); - -bool xpm_integrity_equal(struct page *page, struct page *kpage); +bool xpm_cmp_page_integrity(struct page *page, struct page *kpage); #else -bool is_xpm_region_outer(unsigned long addr_start, +static inline bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags); { return true; } -void set_xpm_region_info(struct vm_unmapped_area_info *info, +static inline void set_xpm_region_info(struct vm_unmapped_area_info *info, unsigned long flags) { - return; } -static int xpm_sig_validate(struct vm_area_struct *vma, +static inline int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) { return XPM_SUCCESS; } -static inline bool xpm_integrity_equal(struct page *page, +static inline bool xpm_cmp_page_integrity(struct page *page, struct page *kpage) { return 0; } -static inline vm_fault_t xpm_integrity_check(struct vm_fault *vmf, - struct page * page) +static inline vm_fault_t xpm_integrity_check(struct vm_fault *vmf, + struct page *page) { return 0; } -static inline vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, - struct page * page) +static inline vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, + struct page *page) { return 0; } -static inline void xpm_integrity_update(struct vm_fault *vmf, - struct page * page) -{ - return; -} +static inline void xpm_integrity_update(struct vm_fault *vmf, + struct page *page) { } -static inline void xpm_fault_page_info(struct vm_fault *vmf, - struct page *page, char *extra_info) -{ - return; -} +static inline void xpm_fault_page_info(struct vm_fault *vmf, + struct page *page, char *extra_info) { } #endif -#endif /* _XPM_H */ \ No newline at end of file +#endif /* _XPM_H */ diff --git a/mm/ksm.c b/mm/ksm.c index 24f720ee00ed..7ea4528addcc 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -41,6 +41,7 @@ #include #include "internal.h" +#include #ifdef CONFIG_NUMA #define NUMA(x) (x) @@ -1213,7 +1214,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, goto out; /* Check XPM flags */ - if(xpm_integrity_equal(page, kpage)) + if(xpm_cmp_page_integrity(page, kpage)) goto out; /* diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index 284bc7719c39..3c86fcce6168 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -18,7 +18,7 @@ static bool is_xpm_readonly(struct vm_area_struct *vma) mm = current->mm; if (!mm) { - xpm_log_error("smm_struct is null"); + xpm_log_error("mm_struct is null"); return 0; } @@ -69,7 +69,7 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) SetPageXPMWritetainted(page); #ifdef CONFIG_XPM_DEBUG - xpm_fault_page_info(vmf, page, "[SET WT]"); + // xpm_fault_page_info(vmf, page, "[SET WT]"); #endif return; } @@ -78,7 +78,7 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) SetPageXPMReadonly(page); #ifdef CONFIG_XPM_DEBUG - xpm_fault_page_info(vmf, page, "[SET RD]"); + // xpm_fault_page_info(vmf, page, "[SET RD]"); #endif } @@ -105,12 +105,12 @@ vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, * If the xpm integrity flags of these two pages are equal, return 0, * otherwise return 1 */ -bool xpm_integrity_equal(struct page *page, struct page *kpage) +bool xpm_cmp_page_integrity(struct page *page, struct page *kpage) { if (!kpage) return 0; - if ((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) + if ((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) | (PageXPMReadonly(page) != PageXPMReadonly(kpage))) return 1; diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 34706b8c3146..4fc3dfaaa9c5 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -5,60 +5,60 @@ #include "xpm_report.h" #ifdef CONFIG_XPM -static bool report_rmap_page_info(struct page *page, - struct vm_area_struct *vma, unsigned long addr, void *write_offsense) +static bool report_page_rmap_info(struct page *page, + struct vm_area_struct *vma, unsigned long addr, void *write_violation) { int pid; struct task_struct *tsk; unsigned long flags; - + tsk = vma->vm_mm->owner; //mm->owner could be clear to NULL by mm_update_next_owner - pid = tsk ? tsk->pid : -1 ; + pid = tsk ? tsk->pid : -1; flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; xpm_log_error(" --> mapped in PID:%d, VADDR: 0x%lx, VMA[flags:0x%lx] \ - [range:0x%lx:0x%lx:0x%lx]", pid, addr, flags, vma->vm_start, + [range:0x%lx:0x%lx:0x%lx]", pid, addr, flags, vma->vm_start, vma->vm_end, (vma->vm_end - vma->vm_start)); return true; } -void report_integrity_violation(struct vm_fault *vmf, - struct page *page, bool write_offsense) +static void log_vlolation_detail(struct vm_fault *vmf, + struct page *page, bool write_violation) { struct vm_area_struct *vma; - char * page_type; + char *page_type; char path_buf[512]; char *filename; - char *xpm_conflic_type; + char *xpm_page_type; unsigned long flags; struct rmap_walk_control rwc = { - .rmap_one = report_rmap_page_info, - .arg = (void*)&write_offsense, + .rmap_one = report_page_rmap_info, + .arg = (void *)&write_violation, }; vma = vmf->vma; //get filename - if(!vma_is_anonymous(vma)){ + if (!vma_is_anonymous(vma)) { struct dentry *dentry; + dentry = vma->vm_file->f_path.dentry; filename = dentry_path(dentry, path_buf, sizeof(path_buf)); - if(IS_ERR(filename)) + if (IS_ERR(filename)) filename = "unknow file"; - }else{ + } else { filename = "anon_file"; } - //get page type page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; //xpm conflic type - xpm_conflic_type = write_offsense? "RO" : "WT"; + xpm_page_type = write_violation ? "RO" : "WT"; //vma flags flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; @@ -66,72 +66,83 @@ void report_integrity_violation(struct vm_fault *vmf, //map readonly page into execuable page xpm_log_error(" [%s] %s Page at 0x%lx [%s] [ref:%d]:0x%lx-> PID:%d,\ VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx]", - xpm_conflic_type, page_type, page_to_pfn(page), filename, + xpm_page_type, page_type, page_to_pfn(page), filename, page->_refcount, page_index(page), current->pid, vmf->address, flags, vma->vm_start, vma->vm_end ); - + rmap_walk(page, &rwc); return; } -void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, +void report_integrity_violation(struct vm_fault *vmf, + struct page *page, bool write_violation) +{ +#ifndef CONFIG_XPM_DEBUG + report_code_tampered(vmf, page, write_violation); +#else + log_vlolation_detail(vmf, page, write_violation); +#endif +} + +void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, char *extra_info) { struct vm_area_struct *vma; - char * page_type; + char *page_type; char path_buf[512]; char *filename; unsigned long flags; // static int count = 0; // if(count++ > XPM_PRINT_COUNT) - // return; + // return; vma = vmf->vma; //get filename - if(!vma_is_anonymous(vma)){ + if (!vma_is_anonymous(vma)) { struct dentry *dentry; - struct file * file; + struct file *file; + file = vma->vm_file; - if(file){ - dentry =file->f_path.dentry; + if (file) { + dentry = file->f_path.dentry; filename = dentry_path(dentry, path_buf, sizeof(path_buf)); - if(IS_ERR(filename)) + if (IS_ERR(filename)) filename = "unknow file"; - }else{ + } else { filename = "vma file miss"; } - }else{ + } else { filename = "anon_file"; } - if(!extra_info) + if (!extra_info) extra_info = ""; - + //vma flags flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; //get page type - if(page){ + if (page) { page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; //map readonly page into execuable page xpm_log_error("%s -->[%s] %s Page at 0x%lx [%s] [ref:%d]:0x%lx-> PID:%d, \ - VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", - extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", + VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", + extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", page_type, page_to_pfn(page), filename, page->_refcount, page_index(page), - current->pid, vmf->address, flags ,vma->vm_start, vma->vm_end, + current->pid, vmf->address, flags, vma->vm_start, vma->vm_end, vma->vm_end - vma->vm_start ); - - }else { + + } else { xpm_log_error("%s -->[%s] [%s]: null page -> PID:%d, VADDR: 0x%lx, \ VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", - extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", - filename, current->pid, vmf->address, flags ,vma->vm_start, vma->vm_end, + extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", + filename, current->pid, vmf->address, flags, vma->vm_start, vma->vm_end, vma->vm_end - vma->vm_start ); } @@ -144,14 +155,14 @@ void report_xpm_init_failed(int err_code) ktime_t cur_time; event_info *event = NULL; - event = (event_info *)kzalloc(sizeof(event_info) + + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); if (event == NULL) { xpm_log_error("kmalloc event_info failed"); return; } - + cur_time = ktime_get_real(); event->event_id = XPM_SG_EVENT_ID; @@ -172,29 +183,29 @@ void report_file_format_damaged(struct file *file, bool signature, char *log) { char *filename, *buf; ktime_t cur_time; - struct dentry * dentry; + struct dentry *dentry; event_info *event; - - if(!file) + + if (!file) return; - + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); if (!event) { xpm_log_error("kmalloc event_info failed"); return; } - buf = (char*)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); - if (!buf){ + buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + if (!buf) { xpm_log_error("kmalloc failed"); goto free_event; } - + cur_time = ktime_get_real(); - dentry =file->f_path.dentry; + dentry = file->f_path.dentry; filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); - if(IS_ERR(filename)){ + if (IS_ERR(filename)) { xpm_log_error("unknow file"); goto free_out; } @@ -206,7 +217,7 @@ void report_file_format_damaged(struct file *file, bool signature, char *log) XPM_FILE_FORMAT_DAMAGED_JSON, XPM_FILE_FORMAT_DAMAGED, cur_time, current->pid, filename, signature, log ? log : ""); - + event->content_len = strlen(event->content) + 1; xpm_report_security_info(event); @@ -224,9 +235,9 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, char *filename, *buf; ktime_t cur_time; event_info *event; - struct dentry * dentry; - - if(!file) + struct dentry *dentry; + + if (!file) return; event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, @@ -236,12 +247,12 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, goto free_event; } - buf = (char*)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); if (!buf) { xpm_log_error("kmalloc failed"); goto out; } - + cur_time = ktime_get_real(); dentry = file->f_path.dentry; @@ -253,9 +264,9 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, event->event_id = XPM_SG_EVENT_ID; event->version = XPM_SG_VERSION; - + snprintf(event->content, MAX_CONTENT_LEN, XPM_MAP_FAILED_JSON, - XPM_CODE_MAP_FAILED, cur_time, err_code, + XPM_CODE_MAP_FAILED, cur_time, err_code, current->pid, filename, signature, codetype, prot, pgoff, size); event->content_len = strlen(event->content) + 1; @@ -266,21 +277,22 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, kfree(buf); free_event: kfree(event); - return; + return; } -static int inline get_xpm_flags(struct page *page){ - if(PageXPMReadonly(page)) +static int inline get_xpm_flags(struct page *page) +{ + if (PageXPMReadonly(page)) return 1; - if( PageXPMWritetainted(page)) + if (PageXPMWritetainted(page)) return 2; - + return 0; } -void report_integrity_tampered(struct vm_fault *vmf, struct page *page, - bool violate_type) +void report_code_tampered(struct vm_fault *vmf, struct page *page, + bool write_violation) { char *filename, *buf, *page_type; struct vm_area_struct *vma; @@ -294,18 +306,19 @@ void report_integrity_tampered(struct vm_fault *vmf, struct page *page, goto free_event; } - buf = (char*)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); if (!buf) { xpm_log_error("kmalloc buf failed"); goto out; } - + cur_time = ktime_get_real(); vma = vmf->vma; - if(!vma_is_anonymous(vma)){ + if (!vma_is_anonymous(vma)) { struct dentry *dentry; + dentry = vma->vm_file->f_path.dentry; filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); @@ -313,10 +326,10 @@ void report_integrity_tampered(struct vm_fault *vmf, struct page *page, xpm_log_error("unknow file"); goto out; } - }else{ + } else { filename = "Anon"; } - + page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; event->event_id = XPM_SG_EVENT_ID; @@ -334,14 +347,15 @@ void report_integrity_tampered(struct vm_fault *vmf, struct page *page, kfree(event); free_event: kfree(buf); - return; + return; } #ifndef _HW_KERNEL_SG_COLLECT_H_ -unsigned int xpm_report_security_info(const event_info *info){ +unsigned int xpm_report_security_info(const event_info *info) +{ xpm_log_error("%s", info); return 0; } #endif -#endif \ No newline at end of file +#endif diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h index f37c872c322e..34bbc8a202a2 100644 --- a/security/xpm/core/xpm_report.h +++ b/security/xpm/core/xpm_report.h @@ -21,14 +21,14 @@ enum { #ifdef _HW_KERNEL_SG_COLLECT_H_ #include -unsigned int inline xpm_report_security_info(const event_info *info) +#define XPM_SG_EVENT_ID 111 +#define XPM_SG_VERSION 222 + +static inline unsigned int xpm_report_security_info(const event_info *info) { return report_security_info(const event_info *info); } -#define XPM_SG_EVENT_ID 111 -#define XPM_SG_VERSION 222 - #else typedef struct { unsigned long event_id; @@ -54,60 +54,60 @@ unsigned int xpm_report_security_info(const event_info *info); #define JSPAIR(val, format) JSTR(val) ":" #format #define EVENT_COM_CONT() \ - JSPAIR(event, %d) ", " \ - JSPAIR(time, %lld) ", " + JSPAIR(event, %d) ", "\ + JSPAIR(time, %lld) ", " #define PROCESS_INFO \ - JSTR(process) ":{" \ - JSPAIR(pid, %d) \ - "}" + JSTR(process) ":{" \ + JSPAIR(pid,%d) \ + "}" #define FILE_INFO \ - JSTR(file) ":{" \ - JSTR(filename) ":" JSTR(%s) ", " \ - JSPAIR(signature, %d) \ - "}" + JSTR(file) ":{"\ + JSTR(filename) ":" JSTR(%s) ", "\ + JSPAIR(signature, %d) \ + "}" #define MAP_INFO \ JSTR(vma) ":{" \ - JSPAIR(code_type, %d) ", "\ - JSPAIR(flags, %lx) ", "\ - JSPAIR(pgoff, 0x%lx) ", "\ - JSPAIR(size, 0x%lx) \ - "}" - -#define PAGE_INFO \ - JSTR(page) ":{" \ - JSPAIR(xpm_flag, %d) ", " \ - JSTR(page_type) ":" JSTR(%s) ", " \ - JSTR(filename) ":" JSTR(%s) ", " \ - JSPAIR(pgoff, %d) \ + JSPAIR(code_type, %d) ", "\ + JSPAIR(flags, %lx) ", "\ + JSPAIR(pgoff, 0x%lx) ", "\ + JSPAIR(size, 0x%lx) \ "}" -#define EXTRA_INFO \ +#define PAGE_INFO \ + JSTR(page) ":{" \ + JSPAIR(xpm_flag, %d) ", "\ + JSTR(page_type) ":" JSTR(%s) ", "\ + JSTR(filename) ":" JSTR(%s) ", "\ + JSPAIR(pgoff, %d) \ + "}" + +#define EXTRA_INFO \ JSTR(extra) ":" JSTR(%s) -/* +/* * XPM_INIT_FAILED JSON format */ -#define XPM_INIT_FAILED_JSON \ - "{" EVENT_COM_CONT() \ - JSTR(detail) ":{" \ - JSPAIR(xpm_flag, %d)\ - "}}" +#define XPM_INIT_FAILED_JSON \ + "{" EVENT_COM_CONT() \ + JSTR(detail) ":{" \ + JSPAIR(xpm_flag, %d) \ + "}}" -/* +/* * ELF_FORMAT_DAMAGED JSON format */ #define XPM_FILE_FORMAT_DAMAGED_JSON \ "{" EVENT_COM_CONT() \ - JSTR(detail) ":{" \ - PROCESS_INFO "," \ - FILE_INFO "," \ - EXTRA_INFO \ + JSTR(detail) ":{"\ + PROCESS_INFO ","\ + FILE_INFO "," \ + EXTRA_INFO \ "}}" -/* +/* * XPM_MAP_FAILED JSON format */ #define XPM_MAP_FAILED_JSON \ @@ -119,7 +119,7 @@ unsigned int xpm_report_security_info(const event_info *info); MAP_INFO \ "}}" -/* +/* * XPM_INTEGRITY_VIOLATION JSON format */ #define XPM_INTEGRITY_TAMPERED_JSON \ @@ -127,46 +127,34 @@ unsigned int xpm_report_security_info(const event_info *info); JSTR(detail) ":{" \ PROCESS_INFO","\ PAGE_INFO ","\ - JSPAIR(vm_prot, %d) \ + JSPAIR(vm_prot, %d) \ "}}" #ifdef CONFIG_XPM -void report_integrity_violation(struct vm_fault *vmf, - struct page *page, bool write_offsense); +void report_integrity_violation(struct vm_fault *vmf, + struct page *page, bool write_violation); void report_xpm_init_failed(int errs_code); void report_file_format_damaged(struct file *file, bool signature, char *log); -void report_code_map_failed(int err_code, struct file *file, bool signature, +void report_code_map_failed(int err_code, struct file *file, bool signature, int codetype, int prot, pgoff_t pgoff, size_t size); -void report_integrity_tampered(struct vm_fault *vmf, struct page *page, bool violate_type); +void report_code_tampered(struct vm_fault *vmf, struct page *page, + bool violate_type); -#elif -void inline report_integrity_violation(struct vm_fault *vmf, - struct page *page, bool write_offsense) -{ - return; -} +#else +inline void report_integrity_violation(struct vm_fault *vmf, + struct page *page, bool write_violation) {} -void report_xpm_init_failed(int errs_code) -{ - return; -} +void report_xpm_init_failed(int errs_code) {} -void report_file_format_damaged(struct file *file, bool signature, char *log) -{ - return; -} +void report_file_format_damaged(struct file *file, bool signature, + char *log) {} -void report_code_map_failed(int err_code, struct file *file, bool signature, - int codetype, int prot, pgoff_t pgoff, size_t size) -{ - return; -} +void report_code_map_failed(int err_code, struct file *file, bool signature, + int codetype, int prot, pgoff_t pgoff, size_t size) {} + +void report_code_tampered(struct vm_fault *vmf, struct page *page, + bool violate_type) {} -void report_integrity_tampered(struct vm_fault *vmf, struct page *page, - bool violate_type) -{ - return; -} #endif -#endif /* _XPM_LOG_H */ \ No newline at end of file +#endif /* _XPM_LOG_H */ -- Gitee From e260d64ce428f0f798e19bd1e8b5d666e646af1d Mon Sep 17 00:00:00 2001 From: zhangpan Date: Thu, 6 Apr 2023 10:46:02 +0800 Subject: [PATCH 06/35] Updated the mechanism for setting xpm writetainted bit. Change-Id: I6478bb3b2f8ee44e8fdebfaa306efab56326792f --- security/xpm/core/xpm_integrity.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index 3c86fcce6168..bf9fc9d8b3b2 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -9,9 +9,16 @@ #include #include "xpm_report.h" +#define CONFIG_XPM 1 + #ifdef CONFIG_XPM -static bool is_xpm_readonly(struct vm_area_struct *vma) +/* + * 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) { struct xpm_region *xpm_region; struct mm_struct *mm; @@ -46,14 +53,16 @@ vm_fault_t xpm_integrity_check(struct vm_fault *vmf, vma = vmf->vma; - /* Integrity violation: map a readonly page into a writable VMA */ - if ((vma->vm_flags & VM_WRITE) && PageXPMReadonly(page)) { + /* Integrity violation: write a readonly page */ + if ((vmf->flags & FAULT_FLAG_WRITE) && + (vma->vm_flags & VM_WRITE) && + PageXPMReadonly(page)) { report_integrity_violation(vmf, page, 1); return XPM_RET(VM_FAULT_SIGBUS); } - /* Integrity violation: map a writetained page into a RO VMA */ - if (PageXPMWritetainted(page) && is_xpm_readonly(vma)) { + /* Integrity violation: execute a writetained page */ + if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { report_integrity_violation(vmf, page, 0); return XPM_RET(VM_FAULT_SIGBUS); } @@ -65,7 +74,10 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) { struct vm_area_struct *vma = vmf->vma; - if ((vma->vm_flags & VM_WRITE) && !PageXPMWritetainted(page)) { + /* Set writetainted only if a real write occurred */ + if ((vmf->flags & FAULT_FLAG_WRITE) && + (vma->vm_flags & VM_WRITE) && + !PageXPMWritetainted(page)) { SetPageXPMWritetainted(page); #ifdef CONFIG_XPM_DEBUG @@ -74,7 +86,7 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) return; } - if (is_xpm_readonly(vma) && !PageXPMReadonly(page)) { + if (is_xpm_readonly_region(vma) && !PageXPMReadonly(page)) { SetPageXPMReadonly(page); #ifdef CONFIG_XPM_DEBUG -- Gitee From 30aa858b3bc917ba1800f9c855af3e1ab42c32af Mon Sep 17 00:00:00 2001 From: limerence Date: Thu, 6 Apr 2023 18:01:32 +0800 Subject: [PATCH 07/35] fix xpm region bug & add debugfs Signed-off-by: limerence --- include/linux/mm.h | 2 +- include/linux/mman.h | 2 - include/linux/xpm.h | 31 +++++++------ include/linux/xpm_types.h | 4 +- include/uapi/asm-generic/mman-common.h | 1 + include/uapi/linux/mman.h | 1 - mm/mmap.c | 6 +++ security/selinux/hooks.c | 4 +- security/xpm/Kconfig | 9 ---- security/xpm/Makefile | 1 + security/xpm/core/xpm_api.c | 63 +++++++------------------- security/xpm/core/xpm_debugfs.c | 34 ++++++++++++++ security/xpm/core/xpm_debugfs.h | 24 ++++++++++ security/xpm/core/xpm_integrity.c | 4 +- security/xpm/core/xpm_main.c | 17 +++++-- security/xpm/core/xpm_misc.c | 6 +-- 16 files changed, 120 insertions(+), 89 deletions(-) create mode 100755 security/xpm/core/xpm_debugfs.c create mode 100755 security/xpm/core/xpm_debugfs.h diff --git a/include/linux/mm.h b/include/linux/mm.h index 31c0a93b555a..bb42a45eac06 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -338,7 +338,7 @@ extern unsigned int kobjsize(const void *objp); #ifdef CONFIG_XPM #define VM_XPM VM_HIGH_ARCH_7 #else /* CONFIG_MEM_PURGEABLE */ -#define VM_XPM 0 +#define VM_XPM VM_NONE #endif /* CONFIG_MEM_PURGEABLE */ #ifdef CONFIG_ARCH_HAS_PKEYS diff --git a/include/linux/mman.h b/include/linux/mman.h index b701eab8f4b7..3225d2c14f87 100644 --- a/include/linux/mman.h +++ b/include/linux/mman.h @@ -154,9 +154,7 @@ calc_vm_flag_bits(unsigned long flags) _calc_vm_trans(flags, MAP_DENYWRITE, VM_DENYWRITE ) | _calc_vm_trans(flags, MAP_LOCKED, VM_LOCKED ) | _calc_vm_trans(flags, MAP_SYNC, VM_SYNC ) | -#ifdef CONIFG_XPM _calc_vm_trans(flags, MAP_XPM, VM_XPM ) | -#endif arch_calc_vm_flag_bits(flags); } diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 91dea11764c6..326c5546fb16 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -6,6 +6,7 @@ #ifndef _XPM_H #define _XPM_H +#include #include #include #include @@ -16,33 +17,31 @@ #define XPM_FAULT (-1) #define XPM_SIG_ERROR (-2) -#ifdef CONFIG_XPM_PERMISSIVE -#define XPM_RET(rc) (XPM_SUCCESS) -#else -#define XPM_RET(rc) (rc) -#endif - -#define XPM_PROC_CONTENT_LEN 33 - #define XPM_TAG "xpm_kernel" -#define XPM_INFO "I" -#define XPM_ERROR "E" -#define XPM_DEBUG "D" +#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, XPM_TAG, __func__, ##args) + XPM_INFO_TAG, XPM_TAG, __func__, ##args) #define xpm_log_error(fmt, args...) pr_err("[%s/%s]%s: " fmt "\n", \ - XPM_ERROR, XPM_TAG, __func__, ##args) + XPM_ERROR_TAG, XPM_TAG, __func__, ##args) #ifdef CONFIG_XPM_DEBUG #define xpm_log_debug(fmt, args...) pr_info("[%s/%s]%s: " fmt "\n", \ - XPM_DEBUG, XPM_TAG, __func__, ##args) + XPM_DEBUG_TAG, XPM_TAG, __func__, ##args) #else #define xpm_log_debug(fmt, args...) no_printk(fmt, ##args) #endif extern const struct file_operations proc_xpm_region_operations; + +/** + * get return value by xpm mode (permissive or enforce) + */ +extern int xpm_value(int rc); + /** * check whether input address range is out of the xpm region */ @@ -72,6 +71,10 @@ void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, char *extra_in bool xpm_cmp_page_integrity(struct page *page, struct page *kpage); #else +static inline int xpm_value(int rc) +{ + return rc; +} static inline bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags) diff --git a/include/linux/xpm_types.h b/include/linux/xpm_types.h index 04052eac7c18..b56ae6b5b8ee 100644 --- a/include/linux/xpm_types.h +++ b/include/linux/xpm_types.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-3-Clause */ /* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ #ifndef _XPM_TYPES_H #define _XPM_TYPES_H diff --git a/include/uapi/asm-generic/mman-common.h b/include/uapi/asm-generic/mman-common.h index f94f65d429be..da6cf2b78e8d 100644 --- a/include/uapi/asm-generic/mman-common.h +++ b/include/uapi/asm-generic/mman-common.h @@ -21,6 +21,7 @@ #define MAP_TYPE 0x0f /* Mask for type of mapping */ #define MAP_FIXED 0x10 /* Interpret addr exactly */ #define MAP_ANONYMOUS 0x20 /* don't use a file */ +#define MAP_XPM 0x40 /* xpm control memory */ /* 0x0100 - 0x4000 flags are defined in asm-generic/mman.h */ #define MAP_POPULATE 0x008000 /* populate (prefault) pagetables */ diff --git a/include/uapi/linux/mman.h b/include/uapi/linux/mman.h index 58767e34cf0e..c28942bcd4e3 100644 --- a/include/uapi/linux/mman.h +++ b/include/uapi/linux/mman.h @@ -18,7 +18,6 @@ #define MAP_SHARED_VALIDATE 0x03 /* share + validate extension flags */ #define MAP_PURGEABLE 0x04 /* purgeable memory */ #define MAP_USEREXPTE 0x08 /* userspace extension page table */ -#define MAP_XPM 0x10 /* xpm control memory */ /* * Huge page size encoding when MAP_HUGETLB is specified, and a huge page diff --git a/mm/mmap.c b/mm/mmap.c index 50404479a95e..02db1bd81229 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1461,6 +1461,9 @@ unsigned long do_mmap(struct file *file, unsigned long addr, * that it represents a valid section of the address space. */ addr = get_unmapped_area(file, addr, len, pgoff, flags); + if (flags & MAP_XPM) { + xpm_log_debug("get_unmapped_area flags(0x%x)", flags); + } if (IS_ERR_VALUE(addr)) return addr; @@ -1484,6 +1487,9 @@ unsigned long do_mmap(struct file *file, unsigned long addr, vm_flags = calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; + if (vm_flags & VM_XPM) { + xpm_log_debug("calc_vm_prot_bits vm_flags(0x%x)", vm_flags); + } trace_vendor_do_mmap(&vm_flags, &err); if (err) return err; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 99dfd25f86c8..72c70fab1a5d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6973,7 +6973,7 @@ static int xpm_prot_check(unsigned long prot) XPM__EXECMEM_WRITE, NULL); } - return XPM_RET(rc); + return xpm_value(rc); } static int xpm_sig_check(struct vm_area_struct *vma, unsigned long prot) @@ -6988,7 +6988,7 @@ static int xpm_sig_check(struct vm_area_struct *vma, unsigned long prot) rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, XPM__EXECMEM_NO_SIG, NULL); - return XPM_RET(rc); + return xpm_value(rc); } static int xpm_check(struct vm_area_struct *vma, unsigned long prot) diff --git a/security/xpm/Kconfig b/security/xpm/Kconfig index ac04a6dfccfc..9a873813ab62 100755 --- a/security/xpm/Kconfig +++ b/security/xpm/Kconfig @@ -15,15 +15,6 @@ config XPM mmap and etc. It can control not to execute an illegal signature process. -config XPM_PERMISSIVE - bool "enables excutable permission manager permissive mode" - depends on XPM - default y - help - When selected, in violation of the xpm control poilicy result in a - warning instead of refuse to execute. This option should only be - eanabled during development. - config XPM_DEBUG bool "excutable permission manager debug mode" depends on XPM diff --git a/security/xpm/Makefile b/security/xpm/Makefile index af8376926767..7be5d0f2607c 100755 --- a/security/xpm/Makefile +++ b/security/xpm/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_XPM) += \ core/xpm_main.o \ core/xpm_misc.o \ core/xpm_api.o \ + core/xpm_debugfs.o \ core/xpm_integrity.o \ core/xpm_report.o \ validator/elf_code_segment_info.o \ diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index 8e4dc5f36ed2..7940b1ad6e84 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -3,7 +3,6 @@ * Copyright (c) 2023 Huawei Device Co., Ltd. */ -#include #include #include #include @@ -12,7 +11,6 @@ #include #include #include -#include #include "xpm_report.h" #include "exec_signature_info.h" @@ -20,47 +18,21 @@ extern struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); -#ifdef CONFIG_XPM_DEBUG -static void xpm_print_file_path(struct file *file) -{ - char *pathname = NULL; - char *pathbuf = NULL; - - if (!file) - return; - - pathbuf = __getname(); - if (!pathbuf) - return; - - pathname = d_absolute_path(&file->f_path, pathbuf, PATH_MAX); - if (IS_ERR(pathname)) { - __putname(pathbuf); - } - xpm_log_debug("%s", pathname); - - __putname(pathbuf); -} -#endif - bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags) { - bool result; struct mm_struct *mm = current->mm; - if (flags & VM_UNMAPPED_AREA_XPM) + if (!mm) { + xpm_log_error("current mm is NULL"); return true; + } - result = ((addr_start >= mm->xpm_region.addr_end) || - (addr_end <= mm->xpm_region.addr_start)); - if (!result) - xpm_log_debug("addr region: [0x%lx, 0x%lx]," - "xpm region: [0x%lx, 0x%lx]", - addr_start, addr_end, - mm->xpm_region.addr_start, mm->xpm_region.addr_end); + if (flags & VM_UNMAPPED_AREA_XPM) + return true; - return result; + return (addr_start >= mm->xpm_region.addr_end) || + (addr_end <= mm->xpm_region.addr_start); } void set_xpm_region_info(struct vm_unmapped_area_info *info, @@ -68,8 +40,8 @@ void set_xpm_region_info(struct vm_unmapped_area_info *info, { struct mm_struct *mm = current->mm; - if (!info) { - xpm_log_error("input vm_unmapped_area_info is NULL"); + if (!info || !mm) { + xpm_log_error("vm_unmapped_area_info or mm_struct is NULL"); return; } @@ -77,6 +49,8 @@ void set_xpm_region_info(struct vm_unmapped_area_info *info, info->flags |= VM_UNMAPPED_AREA_XPM; info->low_limit = mm->xpm_region.addr_start; info->high_limit = mm->xpm_region.addr_end; + xpm_log_debug("set xpm region info success, flag(0x%lx), low_limit(0x%lx), high_limit(0x%lx)", + info->flags, info->low_limit, info->high_limit); } } @@ -125,10 +99,9 @@ int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) is_abc = vma->vm_flags & VM_XPM; /* if no need validate signature, default result is true */ - if (!is_abc || !(vma->vm_file && (prot & PROT_EXEC))) + if (!(is_abc || (vma->vm_file && (prot & PROT_EXEC)))) return XPM_SUCCESS; - xpm_log_debug("file type: %s", is_abc ? "ABC" : "ELF"); /* * When file map execute permission or xpm validate region, we should check * the signature of the map region. @@ -141,21 +114,19 @@ int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) } if (info == NULL) { - xpm_print_file_path(vma->vm_file); xpm_log_error("executable file signature is invalid"); - put_exec_file_sig_info(info); - return XPM_SIG_ERROR; + ret = XPM_SIG_ERROR; + goto exit; } ret = xpm_segment_check(!is_abc, vma, info); if (ret) { - xpm_print_file_path(vma->vm_file); xpm_log_error("xpm executable segment check failed"); ret = XPM_SIG_ERROR; + goto exit; } - +exit: put_exec_file_sig_info(info); - xpm_log_debug("xpm sig validate success"); return ret; } @@ -180,7 +151,7 @@ static ssize_t xpm_region_read(struct file *file, char __user *buf, if (!mm) return XPM_SUCCESS; - len = snprintf(xpm_region_str, XPM_REGION_STR_LEN, "%x-%x", + len = snprintf(xpm_region_str, XPM_REGION_STR_LEN, "%lx-%lx", mm->xpm_region.addr_start, mm->xpm_region.addr_end); diff --git a/security/xpm/core/xpm_debugfs.c b/security/xpm/core/xpm_debugfs.c new file mode 100755 index 000000000000..ff1056e81e0a --- /dev/null +++ b/security/xpm/core/xpm_debugfs.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#include "xpm_debugfs.h" +#include + +bool xpm_enforce_mode = false; +static struct dentry *g_xpm_dir = NULL; + +int xpm_value(int rc) +{ + return xpm_enforce_mode ? rc : XPM_SUCCESS; +} + +int xpm_debugfs_init(void) +{ + g_xpm_dir = debugfs_create_dir("xpm", NULL); + if (!g_xpm_dir) { + xpm_log_error("debugfs_create_dir failed"); + return XPM_FAULT; + } + + debugfs_create_bool("xpm_enforce_mode", 0600, g_xpm_dir, + &xpm_enforce_mode); + + return XPM_SUCCESS; +} + +void xpm_debugfs_exit(void) +{ + debugfs_remove_recursive(g_xpm_dir); +} \ No newline at end of file diff --git a/security/xpm/core/xpm_debugfs.h b/security/xpm/core/xpm_debugfs.h new file mode 100755 index 000000000000..db05f094df9d --- /dev/null +++ b/security/xpm/core/xpm_debugfs.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + +#ifndef XPM_DEBUGFS_H + +#include + +#ifdef CONFIG_XPM_DEBUG +extern int xpm_debugfs_init(void); +extern void xpm_debugfs_exit(void); +#else +static inline int xpm_debugfs_init(void) +{ + return XPM_SUCCESS; +} + +static inline void xpm_debugfs_exit(void) +{ +} +#endif + +#endif /* XPM_DEBUGFS_H */ \ No newline at end of file diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index bf9fc9d8b3b2..d89ceb797a0f 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -58,13 +58,13 @@ vm_fault_t xpm_integrity_check(struct vm_fault *vmf, (vma->vm_flags & VM_WRITE) && PageXPMReadonly(page)) { report_integrity_violation(vmf, page, 1); - return XPM_RET(VM_FAULT_SIGBUS); + return xpm_value(VM_FAULT_SIGBUS); } /* Integrity violation: execute a writetained page */ if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { report_integrity_violation(vmf, page, 0); - return XPM_RET(VM_FAULT_SIGBUS); + return xpm_value(VM_FAULT_SIGBUS); } return 0; diff --git a/security/xpm/core/xpm_main.c b/security/xpm/core/xpm_main.c index 7b9117fdec0b..f4c632c99a94 100755 --- a/security/xpm/core/xpm_main.c +++ b/security/xpm/core/xpm_main.c @@ -8,6 +8,7 @@ #include #include #include +#include "xpm_debugfs.h" #include "xpm_misc.h" #include "xpm_report.h" @@ -16,20 +17,26 @@ static int __init xpm_module_init(void) int ret; ret = xpm_register_misc_device(); - if (ret){ + if (ret) { xpm_log_error("xpm register misc device failed, ret = %d", ret); - report_xpm_init_failed(ret); + return ret; } - else{ - xpm_log_info("xpm module init success"); + + ret = xpm_debugfs_init(); + if (ret) { + xpm_deregister_misc_device(); + xpm_log_error("xpm init debugfs failed, ret = %d", ret); + return ret; } + xpm_log_info("xpm module init success"); - return ret; + return XPM_SUCCESS; } static void __exit xpm_module_exit(void) { xpm_deregister_misc_device(); + xpm_debugfs_exit(); xpm_log_info("xpm module exit success"); } diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index a04e6cd9acda..466ffd1da025 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -112,11 +112,7 @@ static struct miscdevice xpm_misc = { int xpm_register_misc_device(void) { - int ret; - - ret = misc_register(&xpm_misc); - - return ret; + return misc_register(&xpm_misc); } void xpm_deregister_misc_device(void) -- Gitee From df67ac5f5fd3b5493973c6511501360c61df0b3e Mon Sep 17 00:00:00 2001 From: zhangpan Date: Sat, 8 Apr 2023 15:00:21 +0800 Subject: [PATCH 08/35] bugfix: fix bugs and update vm fault for integrity violation. Change-Id: Ib99d7ea1ed7f3a2cccdf06cd48139ce3f832dcca --- include/linux/xpm.h | 10 +- mm/ksm.c | 2 +- mm/memory.c | 12 +- security/xpm/core/xpm_integrity.c | 60 +++---- security/xpm/core/xpm_report.c | 278 ++++++------------------------ security/xpm/core/xpm_report.h | 13 +- 6 files changed, 90 insertions(+), 285 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 326c5546fb16..817dada6351b 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -67,8 +67,7 @@ extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); vm_fault_t xpm_integrity_check(struct vm_fault *vmf, struct page *page); vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, struct page *page); void xpm_integrity_update(struct vm_fault *vmf, struct page *page); -void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, char *extra_info); -bool xpm_cmp_page_integrity(struct page *page, struct page *kpage); +bool xpm_integrity_equal(struct page *page, struct page *kpage); #else static inline int xpm_value(int rc) @@ -93,10 +92,10 @@ static inline int xpm_sig_validate(struct vm_area_struct *vma, return XPM_SUCCESS; } -static inline bool xpm_cmp_page_integrity(struct page *page, +static inline bool xpm_integrity_equal(struct page *page, struct page *kpage) { - return 0; + return true; } static inline vm_fault_t xpm_integrity_check(struct vm_fault *vmf, @@ -114,9 +113,6 @@ static inline vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, static inline void xpm_integrity_update(struct vm_fault *vmf, struct page *page) { } -static inline void xpm_fault_page_info(struct vm_fault *vmf, - struct page *page, char *extra_info) { } - #endif #endif /* _XPM_H */ diff --git a/mm/ksm.c b/mm/ksm.c index 7ea4528addcc..9cbb71aee10b 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -1214,7 +1214,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, goto out; /* Check XPM flags */ - if(xpm_cmp_page_integrity(page, kpage)) + if(!xpm_integrity_equal(page, kpage)) goto out; /* diff --git a/mm/memory.c b/mm/memory.c index f33de506f5b9..50386654778f 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -3042,7 +3042,7 @@ vm_fault_t finish_mkwrite_fault(struct vm_fault *vmf) } if (xpm_integrity_validate(vmf, vmf->page)) - return VM_FAULT_SIGBUS; + return VM_FAULT_SIGSEGV; wp_page_reuse(vmf); return 0; @@ -3097,7 +3097,7 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf) } else { if (xpm_integrity_validate(vmf, vmf->page)) - return VM_FAULT_SIGBUS; + return VM_FAULT_SIGSEGV; wp_page_reuse(vmf); lock_page(vmf->page); @@ -3185,7 +3185,7 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf) unlock_page(page); if (xpm_integrity_validate(vmf, vmf->page)) - return VM_FAULT_SIGBUS; + return VM_FAULT_SIGSEGV; wp_page_reuse(vmf); return VM_FAULT_WRITE; @@ -3503,7 +3503,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) */ if (xpm_integrity_validate(vmf, page)) { - ret = VM_FAULT_SIGBUS; + ret = VM_FAULT_SIGSEGV; goto out_nomap; } @@ -3618,7 +3618,7 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) goto oom; else{ if(xpm_integrity_check(vmf, pte_page(entry))) - goto oom; + return VM_FAULT_SIGSEGV; goto got_page; } } @@ -3960,7 +3960,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct page *page) /* check the confliction of xpm integrity flags*/ if (xpm_integrity_validate(vmf, page)) - return VM_FAULT_SIGBUS; + return VM_FAULT_SIGSEGV; flush_icache_page(vma, page); entry = mk_pte(page, vma->vm_page_prot); diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index d89ceb797a0f..b87cb042b807 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -9,8 +9,6 @@ #include #include "xpm_report.h" -#define CONFIG_XPM 1 - #ifdef CONFIG_XPM /* @@ -26,7 +24,7 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) mm = current->mm; if (!mm) { xpm_log_error("mm_struct is null"); - return 0; + return false; } xpm_region = &mm->xpm_region; @@ -34,13 +32,13 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) /* 1. XPM Secure Region */ if (vma->vm_start <= xpm_region->addr_start && xpm_region->addr_end <= vma->vm_end) - return 1; + return true; /* 2. !Anonymous && Executable */ if (!vma_is_anonymous(vma) && (vma->vm_flags & VM_EXEC)) - return 1; + return true; - return 0; + return false; } vm_fault_t xpm_integrity_check(struct vm_fault *vmf, @@ -57,14 +55,14 @@ vm_fault_t xpm_integrity_check(struct vm_fault *vmf, if ((vmf->flags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && PageXPMReadonly(page)) { - report_integrity_violation(vmf, page, 1); - return xpm_value(VM_FAULT_SIGBUS); + report_integrity_violated(vmf, page); + return xpm_value(VM_FAULT_SIGSEGV); } /* Integrity violation: execute a writetained page */ if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { - report_integrity_violation(vmf, page, 0); - return xpm_value(VM_FAULT_SIGBUS); + report_integrity_violated(vmf, page); + return xpm_value(VM_FAULT_SIGSEGV); } return 0; @@ -75,58 +73,44 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) struct vm_area_struct *vma = vmf->vma; /* Set writetainted only if a real write occurred */ - if ((vmf->flags & FAULT_FLAG_WRITE) && - (vma->vm_flags & VM_WRITE) && + if ((vmf->flags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && !PageXPMWritetainted(page)) { SetPageXPMWritetainted(page); - -#ifdef CONFIG_XPM_DEBUG - // xpm_fault_page_info(vmf, page, "[SET WT]"); - #endif return; } - if (is_xpm_readonly_region(vma) && !PageXPMReadonly(page)) { + /* Set xpm readonly flag */ + if (is_xpm_readonly_region(vma) && !PageXPMReadonly(page)) SetPageXPMReadonly(page); - -#ifdef CONFIG_XPM_DEBUG - // xpm_fault_page_info(vmf, page, "[SET RD]"); -#endif - } - + + return; } vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, struct page *page) { vm_fault_t ret; - if (!page) return 0; ret = xpm_integrity_check(vmf, page); - if (ret) - return ret; + if(!ret) + xpm_integrity_update(vmf, page); - xpm_integrity_update(vmf, page); - - return 0; + return ret; } /* - * If the xpm integrity flags of these two pages are equal, return 0, - * otherwise return 1 + * check the integrity of these two pages, return true if equal, + * otherwise false */ -bool xpm_cmp_page_integrity(struct page *page, struct page *kpage) +bool xpm_integrity_equal(struct page *page, struct page *kpage) { if (!kpage) - return 0; - - if ((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) | - (PageXPMReadonly(page) != PageXPMReadonly(kpage))) - return 1; + return true; - return 0; + return !((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) || + (PageXPMReadonly(page) != PageXPMReadonly(kpage))); } #endif diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 4fc3dfaaa9c5..5c8e7c35964e 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -5,175 +5,51 @@ #include "xpm_report.h" #ifdef CONFIG_XPM -static bool report_page_rmap_info(struct page *page, - struct vm_area_struct *vma, unsigned long addr, void *write_violation) +void report_integrity_violated(struct vm_fault *vmf, struct page *page) { - int pid; - struct task_struct *tsk; - unsigned long flags; - - tsk = vma->vm_mm->owner; - //mm->owner could be clear to NULL by mm_update_next_owner - pid = tsk ? tsk->pid : -1; - flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; - - xpm_log_error(" --> mapped in PID:%d, VADDR: 0x%lx, VMA[flags:0x%lx] \ - [range:0x%lx:0x%lx:0x%lx]", pid, addr, flags, vma->vm_start, - vma->vm_end, (vma->vm_end - vma->vm_start)); - - return true; + if(printk_ratelimit()) + report_code_tampered(vmf, page); } -static void log_vlolation_detail(struct vm_fault *vmf, - struct page *page, bool write_violation) +static char *get_file_name(struct file *file, char *path_buf, int len) { - struct vm_area_struct *vma; - char *page_type; - char path_buf[512]; char *filename; - char *xpm_page_type; - unsigned long flags; - - struct rmap_walk_control rwc = { - .rmap_one = report_page_rmap_info, - .arg = (void *)&write_violation, - }; - - vma = vmf->vma; - - //get filename - if (!vma_is_anonymous(vma)) { - struct dentry *dentry; - - dentry = vma->vm_file->f_path.dentry; - filename = dentry_path(dentry, path_buf, sizeof(path_buf)); - - if (IS_ERR(filename)) - filename = "unknow file"; - } else { - filename = "anon_file"; - } - - //get page type - page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; - - //xpm conflic type - xpm_page_type = write_violation ? "RO" : "WT"; - - //vma flags - flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; - - //map readonly page into execuable page - xpm_log_error(" [%s] %s Page at 0x%lx [%s] [ref:%d]:0x%lx-> PID:%d,\ - VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx]", - xpm_page_type, page_type, page_to_pfn(page), filename, - page->_refcount, page_index(page), current->pid, vmf->address, flags, - vma->vm_start, vma->vm_end - ); - - rmap_walk(page, &rwc); + struct dentry *dentry; - return; -} + if (!file) + return NULL; -void report_integrity_violation(struct vm_fault *vmf, - struct page *page, bool write_violation) -{ -#ifndef CONFIG_XPM_DEBUG - report_code_tampered(vmf, page, write_violation); -#else - log_vlolation_detail(vmf, page, write_violation); -#endif + dentry = file->f_path.dentry; + filename = dentry_path(dentry, path_buf, len); + if (IS_ERR(filename)) + return NULL; + return filename; } -void xpm_fault_page_info(struct vm_fault *vmf, struct page *page, - char *extra_info) +static inline void report_xpm_event(event_info *event) { - struct vm_area_struct *vma; - char *page_type; - char path_buf[512]; - char *filename; - unsigned long flags; - - // static int count = 0; - // if(count++ > XPM_PRINT_COUNT) - // return; - - vma = vmf->vma; - - //get filename - if (!vma_is_anonymous(vma)) { - struct dentry *dentry; - struct file *file; - - file = vma->vm_file; - if (file) { - dentry = file->f_path.dentry; - filename = dentry_path(dentry, path_buf, sizeof(path_buf)); - if (IS_ERR(filename)) - filename = "unknow file"; - } else { - filename = "vma file miss"; - } - } else { - filename = "anon_file"; - } - - if (!extra_info) - extra_info = ""; - - //vma flags - flags = vma->vm_flags & VM_DATA_FLAGS_EXEC; - - //get page type - if (page) { - page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; - - //map readonly page into execuable page - xpm_log_error("%s -->[%s] %s Page at 0x%lx [%s] [ref:%d]:0x%lx-> PID:%d, \ - VADDR: 0x%lx, VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", - extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", - page_type, page_to_pfn(page), filename, page->_refcount, page_index(page), - current->pid, vmf->address, flags, vma->vm_start, vma->vm_end, - vma->vm_end - vma->vm_start - ); - - } else { - xpm_log_error("%s -->[%s] [%s]: null page -> PID:%d, VADDR: 0x%lx, \ - VMA[flags:0x%lx][range:0x%lx, 0x%lx, 0x%lx]", - extra_info, (vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read", - filename, current->pid, vmf->address, flags, vma->vm_start, vma->vm_end, - vma->vm_end - vma->vm_start - ); - } - - return; + event->event_id = XPM_SG_EVENT_ID; + event->version = XPM_SG_VERSION; + event->content_len = strlen(event->content) + 1; + xpm_report_security_info(event); } void report_xpm_init_failed(int err_code) { - ktime_t cur_time; - event_info *event = NULL; + event_info *event; event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); - if (event == NULL) { + if (!event) { xpm_log_error("kmalloc event_info failed"); return; } - cur_time = ktime_get_real(); - - event->event_id = XPM_SG_EVENT_ID; - event->version = XPM_SG_VERSION; - snprintf(event->content, MAX_CONTENT_LEN, XPM_INIT_FAILED_JSON, - XPM_INIT_FAILED, cur_time, err_code); + XPM_INIT_FAILED, ktime_get_real(), err_code); - event->content_len = strlen(event->content) + 1; - - xpm_report_security_info(event); + report_xpm_event(event); kfree(event); return; @@ -182,14 +58,15 @@ void report_xpm_init_failed(int err_code) void report_file_format_damaged(struct file *file, bool signature, char *log) { char *filename, *buf; - ktime_t cur_time; - struct dentry *dentry; event_info *event; - if (!file) + if (!file) { + xpm_log_error("damaged fileptr is null"); return; + } - event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, + GFP_KERNEL); if (!event) { xpm_log_error("kmalloc event_info failed"); return; @@ -201,28 +78,18 @@ void report_file_format_damaged(struct file *file, bool signature, char *log) goto free_event; } - cur_time = ktime_get_real(); - - dentry = file->f_path.dentry; - filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); - if (IS_ERR(filename)) { - xpm_log_error("unknow file"); - goto free_out; - } - - event->event_id = XPM_SG_EVENT_ID; - event->version = XPM_SG_VERSION; + filename = get_file_name(file, buf, MAX_CONTENT_LEN); + if(!filename) + goto free_buf; snprintf(event->content, MAX_CONTENT_LEN, XPM_FILE_FORMAT_DAMAGED_JSON, - XPM_FILE_FORMAT_DAMAGED, cur_time, + XPM_FILE_FORMAT_DAMAGED, ktime_get_real(), current->pid, filename, signature, log ? log : ""); - event->content_len = strlen(event->content) + 1; - - xpm_report_security_info(event); + report_xpm_event(event); -free_out: +free_buf: kfree(buf); free_event: kfree(event); @@ -233,9 +100,7 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, int codetype, int prot, pgoff_t pgoff, size_t size) { char *filename, *buf; - ktime_t cur_time; event_info *event; - struct dentry *dentry; if (!file) return; @@ -244,36 +109,26 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, GFP_KERNEL); if (!event) { xpm_log_error("kmalloc event_info failed"); - goto free_event; + return; } buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); if (!buf) { xpm_log_error("kmalloc failed"); - goto out; - } - - cur_time = ktime_get_real(); - - dentry = file->f_path.dentry; - filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); - if (IS_ERR(filename)) { - xpm_log_error("unknow file"); - goto out; + goto free_event; } - event->event_id = XPM_SG_EVENT_ID; - event->version = XPM_SG_VERSION; + filename = get_file_name(file, buf, MAX_CONTENT_LEN); + if(!filename) + goto free_buf; snprintf(event->content, MAX_CONTENT_LEN, XPM_MAP_FAILED_JSON, - XPM_CODE_MAP_FAILED, cur_time, err_code, + XPM_CODE_MAP_FAILED, ktime_get_real(), err_code, current->pid, filename, signature, codetype, prot, pgoff, size); - event->content_len = strlen(event->content) + 1; + report_xpm_event(event); - xpm_report_security_info(event); - -out: +free_buf: kfree(buf); free_event: kfree(event); @@ -282,71 +137,44 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, static int inline get_xpm_flags(struct page *page) { - if (PageXPMReadonly(page)) - return 1; - - if (PageXPMWritetainted(page)) - return 2; - - return 0; + return (PageXPMReadonly(page) << 1 | PageXPMWritetainted(page)); } -void report_code_tampered(struct vm_fault *vmf, struct page *page, - bool write_violation) +void report_code_tampered(struct vm_fault *vmf, struct page *page) { char *filename, *buf, *page_type; struct vm_area_struct *vma; event_info *event; - ktime_t cur_time; event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); if (event == NULL) { xpm_log_error("kmalloc event_info failed"); - goto free_event; + return; } buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); if (!buf) { xpm_log_error("kmalloc buf failed"); - goto out; + goto free_event; } - cur_time = ktime_get_real(); - vma = vmf->vma; + filename = vma_is_anonymous(vma) ? "Anon" : + get_file_name(vma->vm_file, buf, MAX_CONTENT_LEN); - if (!vma_is_anonymous(vma)) { - struct dentry *dentry; - - dentry = vma->vm_file->f_path.dentry; - filename = dentry_path(dentry, buf, MAX_CONTENT_LEN); - - if (IS_ERR(filename)) { - xpm_log_error("unknow file"); - goto out; - } - } else { - filename = "Anon"; - } - - page_type = PageKsm(page) ? "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; - - event->event_id = XPM_SG_EVENT_ID; - event->version = XPM_SG_VERSION; + page_type = PageKsm(page) ? "[ksm]" : + PageAnon(page) ? "[anon]" : "[file]"; snprintf(event->content, MAX_CONTENT_LEN, XPM_INTEGRITY_TAMPERED_JSON, - XPM_INTEGRITY_TAMPERED, cur_time, current->pid, get_xpm_flags(page), - page_type, filename, page->index, vma->vm_page_prot); + XPM_INTEGRITY_TAMPERED, ktime_get_real(), current->pid, get_xpm_flags(page), + page_type, filename ? filename:"", page->index, vma->vm_page_prot); - event->content_len = strlen(event->content) + 1; + report_xpm_event(event); - xpm_report_security_info(event); - -out: - kfree(event); -free_event: kfree(buf); +free_event: + kfree(event); return; } diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h index 34bbc8a202a2..95194d1189c8 100644 --- a/security/xpm/core/xpm_report.h +++ b/security/xpm/core/xpm_report.h @@ -131,18 +131,16 @@ unsigned int xpm_report_security_info(const event_info *info); "}}" #ifdef CONFIG_XPM -void report_integrity_violation(struct vm_fault *vmf, - struct page *page, bool write_violation); +void report_integrity_violated(struct vm_fault *vmf, struct page *page); void report_xpm_init_failed(int errs_code); void report_file_format_damaged(struct file *file, bool signature, char *log); void report_code_map_failed(int err_code, struct file *file, bool signature, int codetype, int prot, pgoff_t pgoff, size_t size); -void report_code_tampered(struct vm_fault *vmf, struct page *page, - bool violate_type); +void report_code_tampered(struct vm_fault *vmf, struct page *page); #else -inline void report_integrity_violation(struct vm_fault *vmf, - struct page *page, bool write_violation) {} +inline void report_integrity_violated(struct vm_fault *vmf, + struct page *page) {} void report_xpm_init_failed(int errs_code) {} @@ -152,8 +150,7 @@ void report_file_format_damaged(struct file *file, bool signature, void report_code_map_failed(int err_code, struct file *file, bool signature, int codetype, int prot, pgoff_t pgoff, size_t size) {} -void report_code_tampered(struct vm_fault *vmf, struct page *page, - bool violate_type) {} +void report_code_tampered(struct vm_fault *vmf, struct page *page) {} #endif -- Gitee From 6c32a167bc5c054f6b2a52a115167765ec5230f2 Mon Sep 17 00:00:00 2001 From: limerence Date: Fri, 7 Apr 2023 11:00:40 +0800 Subject: [PATCH 09/35] modify debugfs coding style Signed-off-by: limerence --- include/linux/xpm.h | 6 +++--- mm/mmap.c | 13 +++---------- security/selinux/hooks.c | 12 +++++++----- security/selinux/include/classmap.h | 2 +- security/xpm/core/xpm_api.c | 23 +++++++++++++++-------- security/xpm/core/xpm_debugfs.c | 12 ++++++------ security/xpm/core/xpm_debugfs.h | 3 +++ 7 files changed, 38 insertions(+), 33 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 326c5546fb16..7777d8260b68 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -15,7 +15,7 @@ /* xpm internal return values */ #define XPM_SUCCESS 0 #define XPM_FAULT (-1) -#define XPM_SIG_ERROR (-2) +#define XPM_SIGNATURE_ERROR (-2) #define XPM_TAG "xpm_kernel" #define XPM_INFO_TAG "I" @@ -57,7 +57,7 @@ extern void set_xpm_region_info(struct vm_unmapped_area_info *info, /** * check whether mmap vma has a signature */ -extern int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot); +extern int xpm_signature_validate(struct vm_area_struct *vma, unsigned long prot); #define XPM_PRINT_COUNT 10000 /* @@ -87,7 +87,7 @@ static inline void set_xpm_region_info(struct vm_unmapped_area_info *info, { } -static inline int xpm_sig_validate(struct vm_area_struct *vma, +static inline int xpm_signature_validate(struct vm_area_struct *vma, unsigned long prot) { return XPM_SUCCESS; diff --git a/mm/mmap.c b/mm/mmap.c index 02db1bd81229..e47fdd463508 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1461,9 +1461,6 @@ unsigned long do_mmap(struct file *file, unsigned long addr, * that it represents a valid section of the address space. */ addr = get_unmapped_area(file, addr, len, pgoff, flags); - if (flags & MAP_XPM) { - xpm_log_debug("get_unmapped_area flags(0x%x)", flags); - } if (IS_ERR_VALUE(addr)) return addr; @@ -1486,10 +1483,6 @@ unsigned long do_mmap(struct file *file, unsigned long addr, */ vm_flags = calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; - - if (vm_flags & VM_XPM) { - xpm_log_debug("calc_vm_prot_bits vm_flags(0x%x)", vm_flags); - } trace_vendor_do_mmap(&vm_flags, &err); if (err) return err; @@ -2209,8 +2202,8 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, if (addr) { if (flags & MAP_XPM) { - pr_err("do not allow specify addr with MAP_XPM flag\n"); - return -EPERM; + xpm_log_error("not allow specify addr with xpm flag"); + return -EINVAL; } addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); @@ -2257,7 +2250,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, /* requesting a specific address */ if (addr) { if (flags & MAP_XPM) { - pr_err("do not allow specify addr with MAP_XPM flag\n"); + xpm_log_error("not allow specify addr with xpm flag"); return -EPERM; } addr = PAGE_ALIGN(addr); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 72c70fab1a5d..3f79e5050cba 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6971,22 +6971,24 @@ static int xpm_prot_check(unsigned long prot) if ((prot & PROT_EXEC) && (prot & PROT_WRITE)) { rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, XPM__EXECMEM_WRITE, NULL); + xpm_log_debug("selinux_state: %d, rc = %d", selinux_state.enforcing, rc); } return xpm_value(rc); } -static int xpm_sig_check(struct vm_area_struct *vma, unsigned long prot) +static int xpm_signature_check(struct vm_area_struct *vma, unsigned long prot) { int rc; u32 sid = current_sid(); - rc = xpm_sig_validate(vma, prot); - if (rc != XPM_SIG_ERROR) + rc = xpm_signature_validate(vma, prot); + if (rc != XPM_SIGNATURE_ERROR) return rc; rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, - XPM__EXECMEM_NO_SIG, NULL); + XPM__EXECMEM_NO_SIGNATURE, NULL); + xpm_log_debug("selinux_state: %d, rc = %d", selinux_state.enforcing, rc); return xpm_value(rc); } @@ -6999,7 +7001,7 @@ static int xpm_check(struct vm_area_struct *vma, unsigned long prot) if (rc) return rc; - return xpm_sig_check(vma, prot); + return xpm_signature_check(vma, prot); } static int selinux_xpm_mmap_ctrl(struct vm_area_struct *vma) diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 7ff92bcc2c9b..370e7069d5a7 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -251,7 +251,7 @@ struct security_class_mapping secclass_map[] = { { "lockdown", { "integrity", "confidentiality", NULL } }, { "xpm", - { "execmem_no_sig", "execmem_write", NULL } }, + { "execmem_no_signature", "execmem_write", NULL } }, { NULL } }; diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index 7940b1ad6e84..424b65d41c79 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -66,7 +66,7 @@ static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, return XPM_SUCCESS; if (!segment) { - xpm_log_error("elf executable segemnt is NULL"); + xpm_log_error("elf executable segment is NULL"); return XPM_FAULT; } @@ -83,13 +83,14 @@ static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, return XPM_SUCCESS; } + xpm_log_error("not executable segment"); return XPM_FAULT; } -int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) +int xpm_signature_validate(struct vm_area_struct *vma, unsigned long prot) { int ret = 0; - bool is_abc; + bool is_abc, is_exec; struct exec_file_sig_info *info = NULL; if (!vma) { @@ -98,15 +99,21 @@ int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) } is_abc = vma->vm_flags & VM_XPM; + is_exec = vma->vm_file && (prot & PROT_EXEC); + if (is_abc && ((prot & PROT_EXEC) || (prot & PROT_WRITE))) { + xpm_log_error("abc code not allow exec and write ptotection"); + return XPM_FAULT; + } + /* if no need validate signature, default result is true */ - if (!(is_abc || (vma->vm_file && (prot & PROT_EXEC)))) + if (!(is_abc || is_exec)) return XPM_SUCCESS; /* * When file map execute permission or xpm validate region, we should check * the signature of the map region. */ - ret = get_exec_file_sig_info(vma->vm_file, !is_abc, &info); + ret = get_exec_file_sig_info(vma->vm_file, is_exec, &info); if (ret) { xpm_log_error("get executable file signature info failed," "ret = 0x%x", ret); @@ -115,14 +122,14 @@ int xpm_sig_validate(struct vm_area_struct *vma, unsigned long prot) if (info == NULL) { xpm_log_error("executable file signature is invalid"); - ret = XPM_SIG_ERROR; + ret = XPM_SIGNATURE_ERROR; goto exit; } - ret = xpm_segment_check(!is_abc, vma, info); + ret = xpm_segment_check(is_exec, vma, info); if (ret) { xpm_log_error("xpm executable segment check failed"); - ret = XPM_SIG_ERROR; + ret = XPM_SIGNATURE_ERROR; goto exit; } exit: diff --git a/security/xpm/core/xpm_debugfs.c b/security/xpm/core/xpm_debugfs.c index ff1056e81e0a..aed86452d802 100755 --- a/security/xpm/core/xpm_debugfs.c +++ b/security/xpm/core/xpm_debugfs.c @@ -6,24 +6,24 @@ #include "xpm_debugfs.h" #include -bool xpm_enforce_mode = false; +uint8_t g_xpm_mode = XPM_PERMISSIVE_MODE; static struct dentry *g_xpm_dir = NULL; -int xpm_value(int rc) +int xpm_value(int value) { - return xpm_enforce_mode ? rc : XPM_SUCCESS; + return (g_xpm_mode == XPM_ENFORCE_MODE ? value : XPM_SUCCESS); } int xpm_debugfs_init(void) { g_xpm_dir = debugfs_create_dir("xpm", NULL); if (!g_xpm_dir) { - xpm_log_error("debugfs_create_dir failed"); + xpm_log_error("create xpm debugfs dir failed"); return XPM_FAULT; } - debugfs_create_bool("xpm_enforce_mode", 0600, g_xpm_dir, - &xpm_enforce_mode); + debugfs_create_u8("xpm_mode", S_IRUSR | S_IWUSR, g_xpm_dir, + &g_xpm_mode); return XPM_SUCCESS; } diff --git a/security/xpm/core/xpm_debugfs.h b/security/xpm/core/xpm_debugfs.h index db05f094df9d..b39a25a88e2c 100755 --- a/security/xpm/core/xpm_debugfs.h +++ b/security/xpm/core/xpm_debugfs.h @@ -7,6 +7,9 @@ #include +#define XPM_PERMISSIVE_MODE 0 +#define XPM_ENFORCE_MODE 1 + #ifdef CONFIG_XPM_DEBUG extern int xpm_debugfs_init(void); extern void xpm_debugfs_exit(void); -- Gitee From 26b0ac06e25ca2a5912f9e3df560d2ba7d3ae15c Mon Sep 17 00:00:00 2001 From: limerence Date: Mon, 10 Apr 2023 11:05:44 +0800 Subject: [PATCH 10/35] add review problem Signed-off-by: limerence --- security/xpm/Kconfig | 2 +- security/xpm/Makefile | 2 +- security/xpm/core/xpm_api.c | 2 +- security/xpm/core/xpm_debugfs.c | 2 +- security/xpm/core/xpm_debugfs.h | 6 +- security/xpm/core/xpm_integrity.c | 6 +- security/xpm/core/xpm_main.c | 2 +- security/xpm/core/xpm_misc.c | 6 +- security/xpm/core/xpm_misc.h | 6 +- security/xpm/core/xpm_report.c | 5 + security/xpm/core/xpm_report.h | 6 +- .../xpm/validator/elf_code_segment_info.c | 3 +- security/xpm/validator/exec_signature_info.c | 3 +- security/xpm/validator/exec_signature_info.h | 7 +- ...47\350\241\214\346\235\203\351\231\220.md" | 607 ------------------ 15 files changed, 32 insertions(+), 633 deletions(-) delete mode 100755 "security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" diff --git a/security/xpm/Kconfig b/security/xpm/Kconfig index 9a873813ab62..a54557437ebd 100755 --- a/security/xpm/Kconfig +++ b/security/xpm/Kconfig @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: GPL-2.0-or-later # Copyright (c) 2023 Huawei Device Co., Ltd. # # Config for the excutable permission manager diff --git a/security/xpm/Makefile b/security/xpm/Makefile index 7be5d0f2607c..d5fffe0c26ab 100755 --- a/security/xpm/Makefile +++ b/security/xpm/Makefile @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: GPL-2.0 +# SPDX-License-Identifier: GPL-2.0-or-later # # Copyright (c) 2023 Huawei Device Co., Ltd. # diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index 424b65d41c79..f4d906b44ddd 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ diff --git a/security/xpm/core/xpm_debugfs.c b/security/xpm/core/xpm_debugfs.c index aed86452d802..44b135cba85c 100755 --- a/security/xpm/core/xpm_debugfs.c +++ b/security/xpm/core/xpm_debugfs.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ diff --git a/security/xpm/core/xpm_debugfs.h b/security/xpm/core/xpm_debugfs.h index b39a25a88e2c..2c87898cef1f 100755 --- a/security/xpm/core/xpm_debugfs.h +++ b/security/xpm/core/xpm_debugfs.h @@ -1,7 +1,7 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ #ifndef XPM_DEBUGFS_H diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index b87cb042b807..9da443544bc0 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2022 Huawei Device Co., Ltd. */ @@ -11,9 +11,9 @@ #ifdef CONFIG_XPM -/* +/* * A xpm readonly region is an area where any page mapped - * will be marked with XPMReadonly. + * 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) diff --git a/security/xpm/core/xpm_main.c b/security/xpm/core/xpm_main.c index f4c632c99a94..4894d9214452 100755 --- a/security/xpm/core/xpm_main.c +++ b/security/xpm/core/xpm_main.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index 466ffd1da025..1b4d77f4a2e5 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -1,8 +1,6 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. - * - * Executable permission manager driver module. */ #include "xpm_misc.h" @@ -35,7 +33,7 @@ static int xpm_set_region(unsigned long addr_base, unsigned long length) } addr = get_unmapped_area(NULL, addr_base, length, 0, 0); - if (IS_ERR_VALUE(addr)) { + if (IS_ERR_VALUE(addr) || (ULONG_MAX - addr_base < length)) { xpm_log_error("xpm get unmmaped area failed"); ret = -EINVAL; goto exit; diff --git a/security/xpm/core/xpm_misc.h b/security/xpm/core/xpm_misc.h index aac039cf44b8..050269446aa6 100755 --- a/security/xpm/core/xpm_misc.h +++ b/security/xpm/core/xpm_misc.h @@ -1,7 +1,7 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ #ifndef _XPM_MISC_H #define _XPM_MISC_H diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 5c8e7c35964e..f6d955bf2ca1 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -1,3 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + #include #include #include diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h index 95194d1189c8..e7536a455283 100644 --- a/security/xpm/core/xpm_report.h +++ b/security/xpm/core/xpm_report.h @@ -1,7 +1,7 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ #ifndef _XPM_LOG_H #define _XPM_LOG_H diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index dfe7d348ec95..27d3c01527aa 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ + #include #include #include diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index df7146e75b9e..fb7d208b6e44 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ + #include #include #include diff --git a/security/xpm/validator/exec_signature_info.h b/security/xpm/validator/exec_signature_info.h index 6475b3919e38..7969f70616bf 100644 --- a/security/xpm/validator/exec_signature_info.h +++ b/security/xpm/validator/exec_signature_info.h @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: BSD-3-Clause +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. - */ +* Copyright (c) 2023 Huawei Device Co., Ltd. +*/ + #ifndef _EXEC_SIGNATURE_INFO_H #define _EXEC_SIGNATURE_INFO_H diff --git "a/security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" "b/security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" deleted file mode 100755 index 7d2b6d7b5db6..000000000000 --- "a/security/xpm/\344\273\243\347\240\201\346\211\247\350\241\214\346\235\203\351\231\220.md" +++ /dev/null @@ -1,607 +0,0 @@ -# 代码执行权限 - -## 代码下载 -**方式一(推荐):通过repo + ssh下载(需注册公钥,请参考码云帮助中心)。** ->repo init -u git@gitee.com:openharmony/manifest.git -b master --no-repo-verify -repo sync -c -repo forall -c 'git lfs pull' - -**方式二:通过repo + https下载。** -> repo init -u https://gitee.com/openharmony/manifest.git -b master --no-repo-verify -repo sync -c -repo forall -c 'git lfs pull' - -**执行prebuilts** ->bash build/prebuilts_download.sh - -##版本相关 -**daliy build** ->http://ci.openharmony.cn/dailys/dailybuilds - -## 编译命令 -|目标|命令| -|---|---| -|rk3568内核|./build.sh --product-name rk3568 --target-cpu arm64 --build-target kernel --gn-args linux_kernel_version="linux-5.10"| - -## 配置文件 ->kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig - -### 安全内存相关 - -#### 函数签名 -``` -int xpm_set_process_va_region(unsigned long addr_base, unsigned long length); -``` -1. 其中入参addr_base为用户态进程推荐的安全内存的起始地址,如果该值为0,则用户态对安全内存的起始地址没有要求,值得注意的是**该值仅为推荐,如果该地址已经被分配vma,这安全内存会继续向下寻找符合length长度的虚拟地址范围**。 -2. length为申请虚拟地址的长度。 -## 设置流程 -### 1. 寻找一块符合要求的虚拟地址范围 -linux进程典型内存布局: -![avtar](https://img-blog.csdn.net/20180615113202958) - -#### unmmaped_area获取关键函数 -```C -unsigned long -get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, - unsigned long pgoff, unsigned long flags) -{ - unsigned long (*get_area)(struct file *, unsigned long, - unsigned long, unsigned long, unsigned long); - - unsigned long error = arch_mmap_check(addr, len, flags); - if (error) - return error; - - /* Careful about overflows.. */ - if (len > TASK_SIZE) - return -ENOMEM; - - get_area = current->mm->get_unmapped_area; - if (file) { - if (file->f_op->get_unmapped_area) - get_area = file->f_op->get_unmapped_area; - } else if (flags & MAP_SHARED) { - /* - * mmap_region() will call shmem_zero_setup() to create a file, - * so use shmem's get_unmapped_area in case it can be huge. - * do_mmap() will clear pgoff, so match alignment. - */ - pgoff = 0; - get_area = shmem_get_unmapped_area; - } - - addr = get_area(file, addr, len, pgoff, flags); - if (IS_ERR_VALUE(addr)) - return addr; - - if (addr > TASK_SIZE - len) - return -ENOMEM; - if (offset_in_page(addr)) - return -EINVAL; - - error = security_mmap_addr(addr); - return error ? error : addr; -} -``` -#### 关键点1 -**mm/mmap.c** -```C -#ifndef HAVE_ARCH_UNMAPPED_AREA -unsigned long -arch_get_unmapped_area(struct file *filp, unsigned long addr, - unsigned long len, unsigned long pgoff, unsigned long flags) -{ - struct mm_struct *mm = current->mm; - struct vm_area_struct *vma, *prev; - struct vm_unmapped_area_info info; - const unsigned long mmap_end = arch_get_mmap_end(addr); - - if (len > mmap_end - mmap_min_addr) - return -ENOMEM; - - if (flags & MAP_FIXED) - return addr; - - if (addr) { - addr = PAGE_ALIGN(addr); - // find_vam_prev解释:查找挨着addr前一个vma以及addr后一个vma,以便于计算vma之间的GAP - vma = find_vma_prev(mm, addr, &prev); - //判断解释: - // 1.addr开始的unmmaped的region需要在mmap_min_addr和mmap_end之间。 - // 2.如果addr后面的vma不存在,则证明addr后面都是unmmaped区域;如果vma后面的vma存在,则addr申请的region不能超过后面vma的起始地址,超过了说明这个GAP不合适。 - // 3.同理addr前面的vma不存在,则证明addr前面都unmmaped区域;如果前面的vma存在,则addr申请的region的起始地址不能小于前面vma的结束地址;到了这一步可以确定这个GAP是否满足对应addr申请的大小了。 - - // 但是:xpm又安全虚拟地址范围(xpm_va_region),addr申请的unmmaped的虚拟地址范围要么全在xpm内,要么全不在,一半在一半不在,那就不行。即(addr + len < xpm_va_region->start) || (addr > xpm_va_region->end) || (addr >= xpm_va_region->start && addr + len <= xpm_va_region->end) - // bool is_cross_xpm_va_region(addr, len) - // 问题1:如果存在安全内存不够的情况,是否要返回mmap成功,但是mmap的内存不在安全内存范围内? - if (mmap_end - len >= addr && addr >= mmap_min_addr && - (!vma || addr + len <= vm_start_gap(vma)) && - (!prev || addr >= vm_end_gap(prev))) - return addr; - } - - info.flags = 0; - info.length = len; - info.low_limit = mm->mmap_base; - info.high_limit = mmap_end; - info.align_mask = 0; - info.align_offset = 0; - return vm_unmapped_area(&info); -} -#endif - -#ifndef HAVE_ARCH_UNMAPPED_AREA_TOPDOWN -unsigned long -arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, - unsigned long len, unsigned long pgoff, - unsigned long flags) -{ - struct vm_area_struct *vma, *prev; - struct mm_struct *mm = current->mm; - struct vm_unmapped_area_info info; - const unsigned long mmap_end = arch_get_mmap_end(addr); - - /* requested length too big for entire address space */ - if (len > mmap_end - mmap_min_addr) - return -ENOMEM; - - if (flags & MAP_FIXED) - return addr; - - /* requesting a specific address */ - if (addr) { - addr = PAGE_ALIGN(addr); - // 1. 与arch_get_unmapped_area基本一致 - vma = find_vma_prev(mm, addr, &prev); - if (mmap_end - len >= addr && addr >= mmap_min_addr && - (!vma || addr + len <= vm_start_gap(vma)) && - (!prev || addr >= vm_end_gap(prev))) - return addr; - } - - info.flags = VM_UNMAPPED_AREA_TOPDOWN; - info.length = len; - info.low_limit = max(PAGE_SIZE, mmap_min_addr); - info.high_limit = arch_get_mmap_base(addr, mm->mmap_base); - info.align_mask = 0; - info.align_offset = 0; - addr = vm_unmapped_area(&info); - - /* - * A failed mmap() very likely causes application failure, - * so fall back to the bottom-up function here. This scenario - * can happen with large stack limits and large mmap() - * allocations. - */ - if (offset_in_page(addr)) { - VM_BUG_ON(addr != -ENOMEM); - info.flags = 0; - info.low_limit = TASK_UNMAPPED_BASE; - info.high_limit = mmap_end; - addr = vm_unmapped_area(&info); - } - - return addr; -} -#endif -``` -**arch/arm/mm/mmap.c** -```C -unsigned long -arch_get_unmapped_area(struct file *filp, unsigned long addr, - unsigned long len, unsigned long pgoff, unsigned long flags) -{ - struct mm_struct *mm = current->mm; - struct vm_area_struct *vma; - int do_align = 0; - int aliasing = cache_is_vipt_aliasing(); - struct vm_unmapped_area_info info; - - /* - * We only need to do colour alignment if either the I or D - * caches alias. - */ - if (aliasing) - do_align = filp || (flags & MAP_SHARED); - - /* - * We enforce the MAP_FIXED case. - */ - if (flags & MAP_FIXED) { - if (aliasing && flags & MAP_SHARED && - (addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)) - return -EINVAL; - return addr; - } - - if (len > TASK_SIZE) - return -ENOMEM; - - if (addr) { - if (do_align) - addr = COLOUR_ALIGN(addr, pgoff); - else - addr = PAGE_ALIGN(addr); - - // 1. 获取addr小于vma->end的最靠近的一个vma - vma = find_vma(mm, addr); - // 2. 当前只检查了vma,如果addr插入到了其他vma中应该怎么办呢? - if (TASK_SIZE - len >= addr && - (!vma || addr + len <= vm_start_gap(vma))) - return addr; - } - - info.flags = 0; - info.length = len; - info.low_limit = mm->mmap_base; - info.high_limit = TASK_SIZE; - info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; - info.align_offset = pgoff << PAGE_SHIFT; - return vm_unmapped_area(&info); -} - -unsigned long -arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, - const unsigned long len, const unsigned long pgoff, - const unsigned long flags) -{ - struct vm_area_struct *vma; - struct mm_struct *mm = current->mm; - unsigned long addr = addr0; - int do_align = 0; - int aliasing = cache_is_vipt_aliasing(); - struct vm_unmapped_area_info info; - - /* - * We only need to do colour alignment if either the I or D - * caches alias. - */ - if (aliasing) - do_align = filp || (flags & MAP_SHARED); - - /* requested length too big for entire address space */ - if (len > TASK_SIZE) - return -ENOMEM; - - if (flags & MAP_FIXED) { - if (aliasing && flags & MAP_SHARED && - (addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)) - return -EINVAL; - return addr; - } - - /* requesting a specific address */ - if (addr) { - if (do_align) - addr = COLOUR_ALIGN(addr, pgoff); - else - addr = PAGE_ALIGN(addr); - vma = find_vma(mm, addr); - // 1. 与arch_get_unmapped_area基本一致,无需分析 - if (TASK_SIZE - len >= addr && - (!vma || addr + len <= vm_start_gap(vma))) - return addr; - } - - info.flags = VM_UNMAPPED_AREA_TOPDOWN; - info.length = len; - info.low_limit = FIRST_USER_ADDRESS; - info.high_limit = mm->mmap_base; - info.align_mask = do_align ? (PAGE_MASK & (SHMLBA - 1)) : 0; - info.align_offset = pgoff << PAGE_SHIFT; - addr = vm_unmapped_area(&info); - - /* - * A failed mmap() very likely causes application failure, - * so fall back to the bottom-up function here. This scenario - * can happen with large stack limits and large mmap() - * allocations. - */ - if (addr & ~PAGE_MASK) { - VM_BUG_ON(addr != -ENOMEM); - info.flags = 0; - info.low_limit = mm->mmap_base; - info.high_limit = TASK_SIZE; - addr = vm_unmapped_area(&info); - } - - return addr; -} - -``` - -#### 关键点2 -```C -/* - * Search for an unmapped address range. - * - * We are looking for a range that: - * - does not intersect with any VMA; - * - is contained within the [low_limit, high_limit) interval; - * - is at least the desired size. - * - satisfies (begin_addr & align_mask) == (align_offset & align_mask) - */ -unsigned long vm_unmapped_area(struct vm_unmapped_area_info *info) -{ - unsigned long addr; - - if (info->flags & VM_UNMAPPED_AREA_TOPDOWN) - addr = unmapped_area_topdown(info); - else - addr = unmapped_area(info); - - trace_vm_unmapped_area(addr, info); - return addr; -} - -static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) -{ - struct mm_struct *mm = current->mm; - struct vm_area_struct *vma; - unsigned long length, low_limit, high_limit, gap_start, gap_end; - - /* Adjust search length to account for worst case alignment overhead */ - length = info->length + info->align_mask; - if (length < info->length) - return -ENOMEM; - - /* - * Adjust search limits by the desired length. - * See implementation comment at top of unmapped_area(). - */ - // 1. high_limit: 申请的region的addr_end不能大于high_limit - // 2. low_limit:申请的region的addr_start不能小于low_limit - gap_end = info->high_limit; - if (gap_end < length) - return -ENOMEM; - high_limit = gap_end - length; - - if (info->low_limit > high_limit) - return -ENOMEM; - low_limit = info->low_limit + length; - - /* Check highest gap, which does not precede any rbtree node */ - // 3. 当前进程使用的最高的地址,如果小于high_limit,那[highest_vm_end,最初的hign_limit)都是干净没用过的,并且大小合适,按规则取一块就行。 - // 4. 但是有了安全内存: gap_start = max(mm->highest_vm_end, xpm_va_region->end),xpm_va_region->也是要和最大使用的vma较量较量,虽然没有添加到vma链表里面,但是这个坑我已经占了 - - gap_start = mm->highest_vm_end; - if (gap_start <= high_limit) - goto found_highest; - - // 5. 发现没有合适的干净的ummaped内存,那只能在vma之间卑微的找合适的gap了 - /* Check if rbtree root looks promising */ - if (RB_EMPTY_ROOT(&mm->mm_rb)) - return -ENOMEM; - vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb); - if (vma->rb_subtree_gap < length) - return -ENOMEM; - - while (true) { - /* Visit right subtree if it looks promising */ - gap_start = vma->vm_prev ? vm_end_gap(vma->vm_prev) : 0; - if (gap_start <= high_limit && vma->vm_rb.rb_right) { - struct vm_area_struct *right = - rb_entry(vma->vm_rb.rb_right, - struct vm_area_struct, vm_rb); - if (right->rb_subtree_gap >= length) { - vma = right; - continue; - } - } - - // 6. 找到一个最接近high_limit的并且满足length长度的gap,左侧prev vma的end地址就是gap_start,右侧vma的start地址就是gap_end,如果gap_end也满足low_limit的限制,那后面再校验下gap的范围就找到了。 -check_current: - /* Check if current node has a suitable gap */ - gap_end = vm_start_gap(vma); - if (gap_end < low_limit) - return -ENOMEM; - // 7. 感觉这个校验没啥意义,但是这块需要加上xpm的校验,即不能落到xpm安全内存范围内,才算找到了,要不然继续循环 - // 条件: 不能与安全内存相交、不能在安全内存里面 - if (gap_start <= high_limit && - gap_end > gap_start && gap_end - gap_start >= length) - goto found; - - /* Visit left subtree if it looks promising */ - if (vma->vm_rb.rb_left) { - struct vm_area_struct *left = - rb_entry(vma->vm_rb.rb_left, - struct vm_area_struct, vm_rb); - if (left->rb_subtree_gap >= length) { - vma = left; - continue; - } - } - - /* Go back up the rbtree to find next candidate node */ - while (true) { - struct rb_node *prev = &vma->vm_rb; - if (!rb_parent(prev)) - return -ENOMEM; - vma = rb_entry(rb_parent(prev), - struct vm_area_struct, vm_rb); - if (prev == vma->vm_rb.rb_right) { - gap_start = vma->vm_prev ? - vm_end_gap(vma->vm_prev) : 0; - goto check_current; - } - } - } - -found: - /* We found a suitable gap. Clip it with the original high_limit. */ - if (gap_end > info->high_limit) - gap_end = info->high_limit; - -found_highest: - /* Compute highest gap address at the desired alignment */ - gap_end -= info->length; - gap_end -= (gap_end - info->align_offset) & info->align_mask; - - VM_BUG_ON(gap_end < info->low_limit); - VM_BUG_ON(gap_end < gap_start); - return gap_end; -} - -static unsigned long unmapped_area(struct vm_unmapped_area_info *info) -{ - /* - * We implement the search by looking for an rbtree node that - * immediately follows a suitable gap. That is, - * - gap_start = vma->vm_prev->vm_end <= info->high_limit - length; - * - gap_end = vma->vm_start >= info->low_limit + length; - * - gap_end - gap_start >= length - */ - - struct mm_struct *mm = current->mm; - struct vm_area_struct *vma; - unsigned long length, low_limit, high_limit, gap_start, gap_end; - - /* Adjust search length to account for worst case alignment overhead */ - length = info->length + info->align_mask; - if (length < info->length) - return -ENOMEM; - - /* Adjust search limits by the desired length */ - if (info->high_limit < length) - return -ENOMEM; - high_limit = info->high_limit - length; - - if (info->low_limit > high_limit) - return -ENOMEM; - low_limit = info->low_limit + length; - - /* Check if rbtree root looks promising */ - if (RB_EMPTY_ROOT(&mm->mm_rb)) - goto check_highest; - vma = rb_entry(mm->mm_rb.rb_node, struct vm_area_struct, vm_rb); - if (vma->rb_subtree_gap < length) - goto check_highest; - - while (true) { - /* Visit left subtree if it looks promising */ - gap_end = vm_start_gap(vma); - if (gap_end >= low_limit && vma->vm_rb.rb_left) { - struct vm_area_struct *left = - rb_entry(vma->vm_rb.rb_left, - struct vm_area_struct, vm_rb); - if (left->rb_subtree_gap >= length) { - vma = left; - continue; - } - } - - gap_start = vma->vm_prev ? vm_end_gap(vma->vm_prev) : 0; -check_current: - /* Check if current node has a suitable gap */ - if (gap_start > high_limit) - return -ENOMEM; - if (gap_end >= low_limit && - gap_end > gap_start && gap_end - gap_start >= length) - goto found; - - /* Visit right subtree if it looks promising */ - if (vma->vm_rb.rb_right) { - struct vm_area_struct *right = - rb_entry(vma->vm_rb.rb_right, - struct vm_area_struct, vm_rb); - if (right->rb_subtree_gap >= length) { - vma = right; - continue; - } - } - - /* Go back up the rbtree to find next candidate node */ - while (true) { - struct rb_node *prev = &vma->vm_rb; - if (!rb_parent(prev)) - goto check_highest; - vma = rb_entry(rb_parent(prev), - struct vm_area_struct, vm_rb); - if (prev == vma->vm_rb.rb_left) { - gap_start = vm_end_gap(vma->vm_prev); - gap_end = vm_start_gap(vma); - goto check_current; - } - } - } - -check_highest: - /* Check highest gap, which does not precede any rbtree node */ - gap_start = mm->highest_vm_end; - gap_end = ULONG_MAX; /* Only for VM_BUG_ON below */ - if (gap_start > high_limit) - return -ENOMEM; - -found: - /* We found a suitable gap. Clip it with the original low_limit. */ - if (gap_start < info->low_limit) - gap_start = info->low_limit; - - /* Adjust gap address to the desired alignment */ - gap_start += (info->align_offset - gap_start) & info->align_mask; - - VM_BUG_ON(gap_start + info->length > info->high_limit); - VM_BUG_ON(gap_start + info->length > gap_end); - return gap_start; -} -``` - - - -#### 2. 将虚拟地址范围从vma自动分配中剔除 -#### 3. 将获取的虚拟地址范围返回为用户态 -#### /proc目录下文件创建 -**1. proc文件创建&读写参考:** -```C -static ssize_t oom_score_adj_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - struct task_struct *task = get_proc_task(file_inode(file)); - char buffer[PROC_NUMBUF]; - short oom_score_adj = OOM_SCORE_ADJ_MIN; - size_t len; - - if (!task) - return -ESRCH; - oom_score_adj = task->signal->oom_score_adj; - put_task_struct(task); - len = snprintf(buffer, sizeof(buffer), "%hd\n", oom_score_adj); - return simple_read_from_buffer(buf, count, ppos, buffer, len); -} - -static const struct file_operations proc_oom_score_adj_operations = { - .read = oom_score_adj_read, - .write = oom_score_adj_write, - .llseek = default_llseek, -}; - -``` -**2. mmap的参考** -```C -static void show_vma_header_prefix(struct seq_file *m, - unsigned long start, unsigned long end, - vm_flags_t flags, unsigned long long pgoff, - dev_t dev, unsigned long ino) -{ - seq_setwidth(m, 25 + sizeof(void *) * 6 - 1); - seq_put_hex_ll(m, NULL, start, 8); - seq_put_hex_ll(m, "-", end, 8); - seq_putc(m, ' '); - seq_putc(m, flags & VM_READ ? 'r' : '-'); - seq_putc(m, flags & VM_WRITE ? 'w' : '-'); - seq_putc(m, flags & VM_EXEC ? 'x' : '-'); - seq_putc(m, flags & VM_MAYSHARE ? 's' : 'p'); - seq_put_hex_ll(m, " ", pgoff, 8); - seq_put_hex_ll(m, " ", MAJOR(dev), 2); - seq_put_hex_ll(m, ":", MINOR(dev), 2); - seq_put_decimal_ull(m, " ", ino); - seq_putc(m, ' '); -} -``` - -### 进程管理 -#### HAP进程fork流程中需要设置进程调试标识&策略函数 -hap应用通过ioctl的方式设置调试标识&管控策略。 -#### 普通二进制进程需要在exec中设置进程调试标识&策略函数 -普通二进制需要在exec系统调用流程中添加设置进程标识和管控策略的函数调调用,security_bprm_check LSM函数处进行拦截 -#### 在mmap中 \ No newline at end of file -- Gitee From 32eab58c816f77ec2a120a1b755214e78b9c31fb Mon Sep 17 00:00:00 2001 From: limerence Date: Mon, 10 Apr 2023 11:51:16 +0800 Subject: [PATCH 11/35] modify xpm config default to n Signed-off-by: limerence --- security/xpm/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/xpm/Kconfig b/security/xpm/Kconfig index a54557437ebd..a516aabb77ab 100755 --- a/security/xpm/Kconfig +++ b/security/xpm/Kconfig @@ -8,7 +8,7 @@ menu "executable permission manager" config XPM bool "enables excutable permission manager feature" - default y + default n help The Executable Permission Manager(XPM) control process execution by inserting control poliy into the security hook list, such as execv, @@ -18,7 +18,7 @@ config XPM config XPM_DEBUG bool "excutable permission manager debug mode" depends on XPM - default y + default n help This option should only be enabled for debug test which can enable some debug interfaces to obtain detailed information. -- Gitee From fd7d8e820a3702cddd7817c77a0f88e70a8d0323 Mon Sep 17 00:00:00 2001 From: limerence Date: Mon, 10 Apr 2023 16:20:09 +0800 Subject: [PATCH 12/35] fix coding style Signed-off-by: limerence --- include/linux/xpm.h | 4 +- security/selinux/hooks.c | 55 ++++++++++++------- security/selinux/include/classmap.h | 2 +- security/xpm/core/xpm_api.c | 2 +- security/xpm/core/xpm_misc.c | 3 - .../xpm/validator/elf_code_segment_info.c | 3 +- 6 files changed, 41 insertions(+), 28 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index aa579a976840..8f575f99de4b 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -57,7 +57,7 @@ extern void set_xpm_region_info(struct vm_unmapped_area_info *info, /** * check whether mmap vma has a signature */ -extern int xpm_signature_validate(struct vm_area_struct *vma, unsigned long prot); +extern int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot); #define XPM_PRINT_COUNT 10000 /* @@ -86,7 +86,7 @@ static inline void set_xpm_region_info(struct vm_unmapped_area_info *info, { } -static inline int xpm_signature_validate(struct vm_area_struct *vma, +static inline int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) { return XPM_SUCCESS; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3f79e5050cba..012b05820be9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6959,63 +6959,78 @@ static int selinux_perf_event_write(struct perf_event *event) #ifdef CONFIG_XPM -static int xpm_prot_check(unsigned long prot) +static int xpm_avc_has_perm(u16 tclass, u32 requested) { - int rc = 0; + 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_check_prot(unsigned long prot) +{ + int rc = 0; /* * We are making executable an mapping that also writable will have an * additional check. */ if ((prot & PROT_EXEC) && (prot & PROT_WRITE)) { - rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, - XPM__EXECMEM_WRITE, NULL); - xpm_log_debug("selinux_state: %d, rc = %d", selinux_state.enforcing, rc); + rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_WRITE); } return xpm_value(rc); } -static int xpm_signature_check(struct vm_area_struct *vma, unsigned long prot) +static int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) { int rc; - u32 sid = current_sid(); - rc = xpm_signature_validate(vma, prot); + rc = xpm_validate_signature(vma, prot); if (rc != XPM_SIGNATURE_ERROR) return rc; - rc = avc_has_perm(&selinux_state, sid, sid, SECCLASS_XPM, - XPM__EXECMEM_NO_SIGNATURE, NULL); - xpm_log_debug("selinux_state: %d, rc = %d", selinux_state.enforcing, rc); + rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_NO_SIGN); return xpm_value(rc); } -static int xpm_check(struct vm_area_struct *vma, unsigned long prot) +static int xpm_common_check(struct vm_area_struct *vma, unsigned long prot) { int rc; - rc = xpm_prot_check(prot); + if (!vma) { + xpm_log_error("input vma is invalid"); + return -EINVAL; + } + + rc = xpm_check_prot(vma->vm_flags | prot); if (rc) return rc; - return xpm_signature_check(vma, prot); + return xpm_check_signature(vma, prot); } -static int selinux_xpm_mmap_ctrl(struct vm_area_struct *vma) +static int selinux_xpm_mmap_check(struct vm_area_struct *vma) { - return xpm_check(vma, vma->vm_flags); + return xpm_common_check(vma, vma->vm_flags); } -static int selinux_xpm_mprotect_ctrl(struct vm_area_struct *vma, +static int selinux_xpm_mprotect_check(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) { if (checkreqprot_get(&selinux_state)) prot = reqprot; - return xpm_check(vma, prot); + return xpm_common_check(vma, prot); } #endif @@ -7305,8 +7320,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), #endif #ifdef CONFIG_XPM - LSM_HOOK_INIT(mmap_region, selinux_xpm_mmap_ctrl), - LSM_HOOK_INIT(file_mprotect, selinux_xpm_mprotect_ctrl), + LSM_HOOK_INIT(mmap_region, selinux_xpm_mmap_check), + LSM_HOOK_INIT(file_mprotect, selinux_xpm_mprotect_check), #endif }; diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 370e7069d5a7..23b6e6a65585 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -251,7 +251,7 @@ struct security_class_mapping secclass_map[] = { { "lockdown", { "integrity", "confidentiality", NULL } }, { "xpm", - { "execmem_no_signature", "execmem_write", NULL } }, + { "execmem_no_sign", "execmem_write", NULL } }, { NULL } }; diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index f4d906b44ddd..d85e715afe6e 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -87,7 +87,7 @@ static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, return XPM_FAULT; } -int xpm_signature_validate(struct vm_area_struct *vma, unsigned long prot) +int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) { int ret = 0; bool is_abc, is_exec; diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index 1b4d77f4a2e5..aed77cbabbce 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -58,9 +58,6 @@ static long xpm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) sizeof(struct xpm_region_info)))) return -EFAULT; - xpm_log_debug("xpm user set region: [0x%lx, 0x%lx, 0x%lx]", - info.addr_base, info.length, info.addr_base + info.length); - switch (cmd) { case XPM_SET_REGION: ret = xpm_set_region(info.addr_base, info.length); diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index 27d3c01527aa..bc4dea4ba749 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "exec_signature_info.h" @@ -211,7 +212,7 @@ static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_s } if (elf_ehdr.e_type != ET_EXEC && elf_ehdr.e_type != ET_DYN) { - pr_err("Not an ELF exec.\n"); + xpm_log_error("Not an ELF exec.\n"); return -ENOEXEC; } -- Gitee From 1dc4e2e1a6bd45dc98ac3cd8488809f34499acd0 Mon Sep 17 00:00:00 2001 From: zhushengle Date: Tue, 11 Apr 2023 10:28:42 +0800 Subject: [PATCH 13/35] =?UTF-8?q?feat=EF=BC=9A=E6=9B=B4=E6=96=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=AE=B5=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhushengle --- security/xpm/core/xpm_api.c | 8 +- .../xpm/validator/elf_code_segment_info.c | 403 +++++++++++++----- security/xpm/validator/exec_signature_info.c | 7 +- security/xpm/validator/exec_signature_info.h | 23 +- 4 files changed, 326 insertions(+), 115 deletions(-) diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index d85e715afe6e..56f088907166 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -55,7 +55,7 @@ void set_xpm_region_info(struct vm_unmapped_area_info *info, } static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, - struct exec_file_sig_info *info) + struct exec_file_signature_info *info) { int i; unsigned long vm_addr_start, vm_addr_end; @@ -91,7 +91,7 @@ int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) { int ret = 0; bool is_abc, is_exec; - struct exec_file_sig_info *info = NULL; + struct exec_file_signature_info *info = NULL; if (!vma) { xpm_log_error("input vma is NULL"); @@ -113,7 +113,7 @@ int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) * When file map execute permission or xpm validate region, we should check * the signature of the map region. */ - ret = get_exec_file_sig_info(vma->vm_file, is_exec, &info); + ret = get_exec_file_signature_info(vma->vm_file, is_exec, &info); if (ret) { xpm_log_error("get executable file signature info failed," "ret = 0x%x", ret); @@ -133,7 +133,7 @@ int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) goto exit; } exit: - put_exec_file_sig_info(info); + put_exec_file_signature_info(info); return ret; } diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index bc4dea4ba749..4a3969553c30 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -1,58 +1,62 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ - #include #include #include #include -#include -#include +#include +#include + #include "exec_signature_info.h" struct elf_segment_info { - unsigned short type; - unsigned short e_phnum; - unsigned e_phsize; - unsigned long long e_phoff; + struct elfhdr elf_ehdr; + uint16_t type; + uint16_t e_phnum; + size_t e_phsize; + uintptr_t e_phoff; }; -static DEFINE_SPINLOCK(dm_verity_tree_lock); +#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 = 0; -static DEFINE_SPINLOCK(fs_verity_tree_lock); +static DEFINE_RWLOCK(fs_verity_tree_lock); static struct rb_root fs_verity_tree = RB_ROOT; static int fs_verity_node_count = 0; -static struct exec_file_sig_info *rb_search_node(struct rb_root *root, struct inode *file_inode) +static struct exec_file_signature_info *rb_search_node(struct rb_root *root, struct inode *file_inode) { struct rb_node *node = root->rb_node; - struct exec_file_sig_info *file_node; + struct exec_file_signature_info *file_node; while (node != NULL) { - file_node = rb_entry(node, struct exec_file_sig_info, rb_node); + file_node = rb_entry(node, struct exec_file_signature_info, rb_node); if ((uintptr_t)file_inode < (uintptr_t)file_node->inode) { node = file_node->rb_node.rb_left; } else if ((uintptr_t)file_inode > (uintptr_t)file_node->inode) { node = file_node->rb_node.rb_right; } else { - file_node->reference++; + atomic_inc(&file_node->reference); return file_node; } } return NULL; } -static void rb_add_node(struct rb_root *root, int *node_count, struct exec_file_sig_info *node) +static void 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_sig_info *file; + struct exec_file_signature_info *file; while (*p != NULL) { parent = *p; - file = rb_entry(parent, struct exec_file_sig_info, rb_node); + file = rb_entry(parent, struct exec_file_signature_info, rb_node); if ((uintptr_t)node->inode < (uintptr_t)file->inode) { p = &(*p)->rb_left; } else { @@ -62,11 +66,11 @@ static void rb_add_node(struct rb_root *root, int *node_count, struct exec_file_ rb_link_node(&node->rb_node, parent, p); rb_insert_color(&node->rb_node, root); - node->reference++; + atomic_inc(&node->reference); (*node_count)++; } -static void rb_erase_node(struct rb_root *root, int *node_count, struct exec_file_sig_info *node) +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)--; @@ -84,11 +88,11 @@ static int read_elf_info(struct file *file, void *buffer, size_t read_size, loff static int find_idle_nodes(struct rb_root *root, uintptr_t *ilde_nodes, size_t count) { int i = 0; - struct exec_file_sig_info *code_segment; + 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_sig_info, rb_node); - if (code_segment->reference > 0) { + 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; @@ -100,7 +104,7 @@ static int find_idle_nodes(struct rb_root *root, uintptr_t *ilde_nodes, size_t c static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) { int i = 0; - int count = 200; + int count = VERITY_NODE_CACHE_RECYCLE_NUM; uintptr_t *code_segments = kzalloc(count * sizeof(uintptr_t), GFP_KERNEL); if (code_segments == NULL) { return; @@ -108,40 +112,73 @@ static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) count = find_idle_nodes(root, code_segments, count); while (i < count) { - struct exec_file_sig_info *code_segment_info = (struct exec_file_sig_info *)code_segments[i]; + struct exec_file_signature_info *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); } static void rm_code_segment_info(void) { - if ((dm_verity_node_count + fs_verity_node_count) < 500) { + if ((dm_verity_node_count + fs_verity_node_count) < VERITY_NODE_CACHE_LIMITS) { return; } if (dm_verity_node_count > fs_verity_node_count) { - spin_lock(&dm_verity_tree_lock); + write_lock(&dm_verity_tree_lock); clear_code_segment_info_cache(&dm_verity_tree, &dm_verity_node_count); - spin_unlock(&dm_verity_tree_lock); + write_unlock(&dm_verity_tree_lock); return; } - spin_lock(&fs_verity_tree_lock); + write_lock(&fs_verity_tree_lock); clear_code_segment_info_cache(&fs_verity_tree, &fs_verity_node_count); - spin_unlock(&fs_verity_tree_lock); + write_unlock(&fs_verity_tree_lock); +} + +static uint64_t elf64_to_cpu(const struct elfhdr *ehdr, uint64_t value) +{ + if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) + value = le64_to_cpu(value); + else if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) + value = be64_to_cpu(value); + + return value; } -static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, int phdr_num) +static uint32_t elf32_to_cpu(const struct elfhdr *ehdr, uint32_t value) +{ + if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) + value = le32_to_cpu(value); + else if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) + value = be32_to_cpu(value); + + return value; +} + +static uint16_t elf16_to_cpu(const struct elfhdr *ehdr, uint16_t value) +{ + if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) + value = le16_to_cpu(value); + else if (ehdr->e_ident[EI_DATA] == ELFDATA2MSB) + value = be16_to_cpu(value); + + return value; +} + +static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, struct elf_segment_info *segment_info) { int i; int count = 0; struct elf32_phdr *phdr_info; + uint32_t p_flags; - for (i = 0; i < phdr_num; i++) { + for (i = 0; i < segment_info->e_phnum; i++) { phdr_info = elf_phdr + i; - if (!(phdr_info->p_flags & PF_X)) { + p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) { continue; } count++; @@ -149,31 +186,47 @@ static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, int phdr_nu return count; } -static void get_elf32_code_segment(struct elf32_phdr *elf_phdr, int phdr_num, struct exec_file_sig_info *segment_info) +static int get_elf32_code_segment(struct elf32_phdr *elf_phdr, struct elf_segment_info *segment_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 < phdr_num; i++) { + for (i = 0; i < segment_info->e_phnum; i++) { phdr_info = elf_phdr + i; - if (!(phdr_info->p_flags & PF_X)) { + p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) { continue; } - segment_info->code_section_count++; - segment_info->code_segment[segment_info->code_section_count - 1].file_offset = phdr_info->p_offset; - segment_info->code_segment[segment_info->code_section_count - 1].size = phdr_info->p_filesz; + p_offset = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_offset); + p_filesz = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_filesz); + p_addr = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_paddr); + p_memsz = elf32_to_cpu(&segment_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_segment[exec_file_info->code_section_count].file_offset = p_offset; + exec_file_info->code_segment[exec_file_info->code_section_count].size = p_filesz; + exec_file_info->code_section_count++; } + return 0; } -static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, int phdr_num) +static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, struct elf_segment_info *segment_info) { int i; int count = 0; struct elf64_phdr *phdr_info; + uint32_t p_flags; - for (i = 0; i < phdr_num; i++) { + for (i = 0; i < segment_info->e_phnum; i++) { phdr_info = elf_phdr + i; - if (!(phdr_info->p_flags & PF_X)) { + p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) { continue; } count++; @@ -181,89 +234,141 @@ static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, int phdr_nu return count; } -static void get_elf64_code_segment(struct elf64_phdr *elf_phdr, int phdr_num, struct exec_file_sig_info *segment_info) +static int get_elf64_code_segment(struct elf64_phdr *elf_phdr, struct elf_segment_info *segment_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 < phdr_num; i++) { + for (i = 0; i < segment_info->e_phnum; i++) { phdr_info = elf_phdr + i; - if (!(phdr_info->p_flags & PF_X)) { + p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + if (!(p_flags & PF_X)) { continue; } - segment_info->code_section_count++; - segment_info->code_segment[segment_info->code_section_count - 1].file_offset = phdr_info->p_offset; - segment_info->code_segment[segment_info->code_section_count - 1].size = phdr_info->p_filesz; + p_offset = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_offset); + p_filesz = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_filesz); + p_addr = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_paddr); + p_memsz = elf64_to_cpu(&segment_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_segment[exec_file_info->code_section_count].file_offset = p_offset; + exec_file_info->code_segment[exec_file_info->code_section_count].size = p_filesz; + exec_file_info->code_section_count++; } + return 0; } static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_segment_info *segment_info) { - struct elfhdr elf_ehdr = {0}; struct elf32_hdr *elf32_ehdr; struct elf64_hdr *elf64_ehdr; - int ret = read_elf_info(file, (void *)&elf_ehdr, sizeof(struct elfhdr), 0); + 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 = &segment_info->elf_ehdr; + + int 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) { + if (memcmp(elf_ehdr->e_ident, ELFMAG, SELFMAG) != 0) { return -ENOEXEC; } - if (elf_ehdr.e_type != ET_EXEC && elf_ehdr.e_type != ET_DYN) { - xpm_log_error("Not an ELF exec.\n"); + type = elf16_to_cpu(elf_ehdr, elf_ehdr->e_type); + if (type != ET_EXEC && type != ET_DYN) { return -ENOEXEC; } - if (elf_ehdr.e_ident[EI_CLASS] == ELFCLASS32) { + if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { segment_info->type = ELFCLASS32; - elf32_ehdr = (struct elf32_hdr *)&elf_ehdr; - segment_info->e_phnum = elf32_ehdr->e_phnum; - segment_info->e_phsize = sizeof(struct elf32_phdr) * elf32_ehdr->e_phnum; - segment_info->e_phoff = elf32_ehdr->e_phoff; - } else if (elf_ehdr.e_ident[EI_CLASS] == ELFCLASS64) { + elf32_ehdr = (struct elf32_hdr *)elf_ehdr; + e_ehsize = elf16_to_cpu(elf_ehdr, elf32_ehdr->e_ehsize); + if (e_ehsize != sizeof(struct elf32_hdr)) { + return -ENOEXEC; + } + segment_info->e_phnum = elf16_to_cpu(elf_ehdr, elf32_ehdr->e_phnum); + if (segment_info->e_phnum == 0) { + return -ENOEXEC; + } + e32_phsize = sizeof(struct elf32_phdr) * segment_info->e_phnum; + e32_phoff = elf32_to_cpu(elf_ehdr, elf32_ehdr->e_phoff); + if (e32_phoff > 0 && e32_phoff + e32_phsize < e32_phoff) { + return -ENOEXEC; + } + segment_info->e_phsize = e32_phsize; + segment_info->e_phoff = e32_phoff; + } else if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS64) { segment_info->type = ELFCLASS64; - elf64_ehdr = (struct elf64_hdr *)&elf_ehdr; - segment_info->e_phnum = elf64_ehdr->e_phnum; - segment_info->e_phsize = sizeof(struct elf64_phdr) * elf64_ehdr->e_phnum; - segment_info->e_phoff = elf64_ehdr->e_phoff; + elf64_ehdr = (struct elf64_hdr *)elf_ehdr; + e_ehsize = elf16_to_cpu(elf_ehdr, elf64_ehdr->e_ehsize); + if (e_ehsize != sizeof(struct elf64_hdr)) { + return -ENOEXEC; + } + segment_info->e_phnum = elf16_to_cpu(elf_ehdr, elf64_ehdr->e_phnum); + if (segment_info->e_phnum == 0) { + return -ENOEXEC; + } + e64_phsize = sizeof(struct elf64_phdr) * segment_info->e_phnum; + e64_phoff = elf64_to_cpu(elf_ehdr, elf64_ehdr->e_phoff); + if (e64_phoff > 0 && e64_phoff + e64_phsize < e64_phoff) { + return -ENOEXEC; + } + segment_info->e_phsize = e64_phsize; + segment_info->e_phoff = e64_phoff; } else { return -ENOEXEC; } return 0; } -static int find_elf_code_segment_info(const char *phdr_info, struct elf_segment_info *segment_info, struct exec_file_sig_info **file_info) +static int find_elf_code_segment_info(const char *phdr_info, struct elf_segment_info *segment_info, struct exec_file_signature_info **file_info) { + int ret; size_t size; - struct exec_file_sig_info *exec_segment_info; + struct exec_file_signature_info *exec_file_info; int segment_count; + if (segment_info->type == ELFCLASS32) { - segment_count = get_elf32_code_segment_count((struct elf32_phdr *)phdr_info, segment_info->e_phnum); + segment_count = get_elf32_code_segment_count((struct elf32_phdr *)phdr_info, segment_info); } else { - segment_count = get_elf64_code_segment_count((struct elf64_phdr *)phdr_info, segment_info->e_phnum); + segment_count = get_elf64_code_segment_count((struct elf64_phdr *)phdr_info, segment_info); } if (segment_count == 0) { return -ENOEXEC; } - size = sizeof(struct exec_file_sig_info) + segment_count * sizeof(struct exec_segment_info); - exec_segment_info = (struct exec_file_sig_info *)kzalloc(size, GFP_KERNEL); - if (exec_segment_info == NULL) { + size = sizeof(struct exec_file_signature_info) + segment_count * sizeof(struct exec_segment_info); + exec_file_info = (struct exec_file_signature_info *)kzalloc(size, GFP_KERNEL); + if (exec_file_info == NULL) { return -ENOMEM; } - exec_segment_info->code_segment = (struct exec_segment_info *)((char *)exec_segment_info + sizeof(struct exec_file_sig_info)); + exec_file_info->code_segment = (struct exec_segment_info *)((char *)exec_file_info + sizeof(struct exec_file_signature_info)); if (segment_info->type == ELFCLASS32) { - get_elf32_code_segment((struct elf32_phdr *)phdr_info, segment_info->e_phnum, exec_segment_info); + ret = get_elf32_code_segment((struct elf32_phdr *)phdr_info, segment_info, exec_file_info); } else { - get_elf64_code_segment((struct elf64_phdr *)phdr_info, segment_info->e_phnum, exec_segment_info); + ret = get_elf64_code_segment((struct elf64_phdr *)phdr_info, segment_info, exec_file_info); + } + if (ret < 0) { + kfree(exec_file_info); + return ret; } - *file_info = exec_segment_info; + *file_info = exec_file_info; return 0; } -static int parse_elf_code_segment_info(struct file *file, struct exec_file_sig_info **code_segment_info) +static int parse_elf_code_segment_info(struct file *file, struct exec_file_signature_info **code_segment_info) { const char *phdr_info; struct elf_segment_info segment_info = {0}; @@ -288,14 +393,14 @@ static int parse_elf_code_segment_info(struct file *file, struct exec_file_sig_i return ret; } -int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_sig_info **code_segment_info) +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; - spinlock_t *verity_lock; + rwlock_t *verity_lock; int *node_count; struct inode *file_node; - struct exec_file_sig_info *segment_info = NULL; + struct exec_file_signature_info *segment_info = NULL; if (type == FILE_DM_VERITY) { root = &dm_verity_tree; @@ -314,9 +419,9 @@ int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct return -EINVAL; } - spin_lock(verity_lock); + read_lock(verity_lock); segment_info = rb_search_node(root, file_node); - spin_unlock(verity_lock); + read_unlock(verity_lock); if (segment_info != NULL) { *code_segment_info = segment_info; return 0; @@ -325,49 +430,153 @@ int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct rm_code_segment_info(); if (!is_exec) { - segment_info = (struct exec_file_sig_info *)kzalloc(sizeof(struct exec_file_sig_info *), GFP_KERNEL); + segment_info = (struct exec_file_signature_info *)kzalloc(sizeof(struct exec_file_signature_info), GFP_KERNEL); if (segment_info == NULL) { return -ENOMEM; } - goto insert_tree; - } - - ret = parse_elf_code_segment_info(file, &segment_info); - if (ret < 0) { - return ret; + } else { + ret = parse_elf_code_segment_info(file, &segment_info); + if (ret < 0) { + return ret; + } } -insert_tree: segment_info->type = type; segment_info->inode = file_node; RB_CLEAR_NODE(&segment_info->rb_node); - spin_lock(verity_lock); + write_lock(verity_lock); rb_add_node(root, node_count, segment_info); - spin_unlock(verity_lock); + write_unlock(verity_lock); *code_segment_info = segment_info; return 0; } -int put_elf_code_segment_info(struct exec_file_sig_info *code_segment_info) +int put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) { - spinlock_t *verity_lock; - if ((code_segment_info == NULL) || ((code_segment_info->type != FILE_DM_VERITY) && (code_segment_info->type != FILE_FS_VERITY))) { return -EINVAL; } + atomic_dec_and_test(&code_segment_info->reference); + return 0; +} + +int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) +{ + struct rb_root *root; + rwlock_t *verity_lock; + int *node_count; + struct exec_file_signature_info *segment_info = NULL; + + if (code_segment_info == NULL) { + return -EINVAL; + } + if (code_segment_info->type == FILE_DM_VERITY) { + root = &dm_verity_tree; verity_lock = &dm_verity_tree_lock; - } else { + node_count = &dm_verity_node_count; + } else if (code_segment_info->type == FILE_FS_VERITY) { verity_lock = &fs_verity_tree_lock; + root = &fs_verity_tree; + node_count = &fs_verity_node_count; + } else { + return -EINVAL; } - spin_lock(verity_lock); - if (code_segment_info->reference > 0) { - code_segment_info->reference--; + write_lock(verity_lock); + segment_info = rb_search_node(root, code_segment_info->inode); + if (segment_info == NULL) { + write_unlock(verity_lock); + return -EINVAL; } - spin_unlock(verity_lock); + rb_erase_node(root, node_count, code_segment_info); + write_unlock(verity_lock); + kfree(code_segment_info); + return 0; +} + +static int destroy_elf_code_segment_tree(struct rb_root *root, int *node_count) +{ + struct rb_node *node; + struct exec_file_signature_info *file_node; + + do { + node = rb_first(root); + if (node == NULL) { + return 0; + } + file_node = rb_entry(node, struct exec_file_signature_info, rb_node); + if (atomic_read(&file_node->reference) > 0) { + return -EPERM; + } + rb_erase_node(root, node_count, file_node); + } while (1); return 0; } + +int test_destroy_elf_code_segment_info_cache(void) +{ + int ret; + int count = 0; + + write_lock(&dm_verity_tree_lock); + count += dm_verity_node_count; + ret = destroy_elf_code_segment_tree(&dm_verity_tree, &dm_verity_node_count); + write_unlock(&dm_verity_tree_lock); + if (ret < 0) { + return ret; + } + + write_lock(&fs_verity_tree_lock); + count += fs_verity_node_count; + ret = destroy_elf_code_segment_tree(&fs_verity_tree, &fs_verity_node_count); + write_unlock(&fs_verity_tree_lock); + if (ret < 0) { + return ret; + } + return count; +} + +static size_t 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_section_count * sizeof(struct exec_segment_info); + } + return size; +} + +size_t test_get_elf_code_segment_info_cache_size(int *cache_count) +{ + size_t cache_size = 0; + int count = 0; + + read_lock(&dm_verity_tree_lock); + cache_size += 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 += elf_code_segment_info_size(&fs_verity_tree); + count += fs_verity_node_count; + read_unlock(&fs_verity_tree_lock); + + if (cache_count) { + *cache_count = count; + } + return cache_size; +} + +void test_print_elf_code_segment_info(const char *file_path, const struct exec_file_signature_info *file_info) +{ + int i; + for (i = 0; i < file_info->code_section_count; i++) { + pr_info("%s -> offset: 0x%llx size: 0x%lx\n", file_path, file_info->code_segment->file_offset, file_info->code_segment->size); + } +} diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index fb7d208b6e44..0d84920d4c8a 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -1,8 +1,7 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ - #include #include #include @@ -28,7 +27,7 @@ static int check_exec_file_is_verity(struct file *file, struct inode *file_node) return FILE_DM_VERITY; } -int get_exec_file_sig_info(struct file *file, bool is_exec, struct exec_file_sig_info **info_ptr) +int get_exec_file_signature_info(struct file *file, bool is_exec, struct exec_file_signature_info **info_ptr) { int type; @@ -45,7 +44,7 @@ int get_exec_file_sig_info(struct file *file, bool is_exec, struct exec_file_sig return get_elf_code_segment_info(file, is_exec, type, info_ptr); } -int put_exec_file_sig_info(struct exec_file_sig_info *exec_info) +int put_exec_file_signature_info(struct exec_file_signature_info *exec_info) { return put_elf_code_segment_info(exec_info); } diff --git a/security/xpm/validator/exec_signature_info.h b/security/xpm/validator/exec_signature_info.h index 7969f70616bf..0d3ec225cc03 100644 --- a/security/xpm/validator/exec_signature_info.h +++ b/security/xpm/validator/exec_signature_info.h @@ -1,11 +1,11 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +// SPDX-License-Identifier: BSD-3-Clause /* -* Copyright (c) 2023 Huawei Device Co., Ltd. -*/ - + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ #ifndef _EXEC_SIGNATURE_INFO_H #define _EXEC_SIGNATURE_INFO_H +#include #include #include @@ -18,9 +18,9 @@ struct exec_segment_info { #define FILE_DM_VERITY 1 #define FILE_NORMAL 2 -struct exec_file_sig_info { +struct exec_file_signature_info { struct rb_node rb_node; - long reference; + atomic_t reference; int type; struct inode *inode; uint8_t hash[32]; @@ -28,8 +28,11 @@ struct exec_file_sig_info { struct exec_segment_info *code_segment; }; -int put_elf_code_segment_info(struct exec_file_sig_info *code_segment_info); -int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_sig_info **code_segment_info); -int get_exec_file_sig_info(struct file *file, bool is_exec, struct exec_file_sig_info **info_ptr); -int put_exec_file_sig_info(struct exec_file_sig_info *exec_info); +void test_print_elf_code_segment_info(const char *file_path, const struct exec_file_signature_info *file_info); +int test_destroy_elf_code_segment_info_cache(void); +int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); +int put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); +int get_elf_code_segment_info(struct file *file, bool is_exec, int type, 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); #endif -- Gitee From b1bcf5f160703cb1ca421f1ee75f2c88f48faa7b Mon Sep 17 00:00:00 2001 From: limerence Date: Tue, 11 Apr 2023 21:20:07 +0800 Subject: [PATCH 14/35] =?UTF-8?q?=E6=B7=BB=E5=8A=A0anon=20selinux=E7=AD=96?= =?UTF-8?q?=E7=95=A5=20&=20=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: limerence --- include/linux/xpm.h | 19 ++++++-- security/selinux/hooks.c | 48 ++++++++----------- security/selinux/include/classmap.h | 2 +- security/xpm/core/xpm_api.c | 48 ++++++++++++++----- .../xpm/validator/elf_code_segment_info.c | 2 +- security/xpm/validator/exec_signature_info.c | 2 +- security/xpm/validator/exec_signature_info.h | 2 +- 7 files changed, 75 insertions(+), 48 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 8f575f99de4b..f103e20b5bf0 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -15,7 +15,7 @@ /* xpm internal return values */ #define XPM_SUCCESS 0 #define XPM_FAULT (-1) -#define XPM_SIGNATURE_ERROR (-2) +#define XPM_CHECK_FAILED (-2) #define XPM_TAG "xpm_kernel" #define XPM_INFO_TAG "I" @@ -55,9 +55,14 @@ extern void set_xpm_region_info(struct vm_unmapped_area_info *info, unsigned long flags); /** - * check whether mmap vma has a signature + * check whether mmap vma has a valid prot */ -extern int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot); +extern int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot); + +/** + * check whether mmap vma has a valid signature + */ +extern int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot); #define XPM_PRINT_COUNT 10000 /* @@ -86,7 +91,13 @@ static inline void set_xpm_region_info(struct vm_unmapped_area_info *info, { } -static inline int xpm_validate_signature(struct vm_area_struct *vma, +static inline int xpm_check_prot(struct vm_area_struct *vma, + unsigned long prot) +{ + return XPM_SUCCESS; +} + +static inline int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) { return XPM_SUCCESS; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 012b05820be9..8279a6a593ef 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6958,7 +6958,6 @@ static int selinux_perf_event_write(struct perf_event *event) #endif #ifdef CONFIG_XPM - static int xpm_avc_has_perm(u16 tclass, u32 requested) { struct av_decision avd; @@ -6975,62 +6974,53 @@ static int xpm_avc_has_perm(u16 tclass, u32 requested) return rc; } -static int xpm_check_prot(unsigned long prot) +static int xpm_prot_control(struct vm_area_struct *vma, unsigned long prot) { - int rc = 0; + int rc; - /* - * We are making executable an mapping that also writable will have an - * additional check. - */ - if ((prot & PROT_EXEC) && (prot & PROT_WRITE)) { - rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_WRITE); - } + rc = xpm_check_prot(vma, prot); + if (rc != XPM_CHECK_FAILED) + return xpm_value(rc); + rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__ANON_EXECMEM); return xpm_value(rc); } -static int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) +static int xpm_signature_control(struct vm_area_struct *vma, unsigned long prot) { int rc; - rc = xpm_validate_signature(vma, prot); - if (rc != XPM_SIGNATURE_ERROR) - return rc; + rc = xpm_check_signature(vma, prot); + if (rc != XPM_CHECK_FAILED) + return xpm_value(rc); rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_NO_SIGN); - return xpm_value(rc); } -static int xpm_common_check(struct vm_area_struct *vma, unsigned long prot) +static int xpm_common_control(struct vm_area_struct *vma, unsigned long prot) { int rc; - if (!vma) { - xpm_log_error("input vma is invalid"); - return -EINVAL; - } - - rc = xpm_check_prot(vma->vm_flags | prot); + rc = xpm_prot_control(vma, prot); if (rc) return rc; - return xpm_check_signature(vma, prot); + return xpm_signature_control(vma, prot); } -static int selinux_xpm_mmap_check(struct vm_area_struct *vma) +static int selinux_xpm_mmap_control(struct vm_area_struct *vma) { - return xpm_common_check(vma, vma->vm_flags); + return xpm_common_control(vma, vma->vm_flags); } -static int selinux_xpm_mprotect_check(struct vm_area_struct *vma, +static int selinux_xpm_mprotect_control(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) { if (checkreqprot_get(&selinux_state)) prot = reqprot; - return xpm_common_check(vma, prot); + return xpm_common_control(vma, prot); } #endif @@ -7320,8 +7310,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), #endif #ifdef CONFIG_XPM - LSM_HOOK_INIT(mmap_region, selinux_xpm_mmap_check), - LSM_HOOK_INIT(file_mprotect, selinux_xpm_mprotect_check), + LSM_HOOK_INIT(mmap_region, selinux_xpm_mmap_control), + LSM_HOOK_INIT(file_mprotect, selinux_xpm_mprotect_control), #endif }; diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 23b6e6a65585..c6cdaaaae867 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -251,7 +251,7 @@ struct security_class_mapping secclass_map[] = { { "lockdown", { "integrity", "confidentiality", NULL } }, { "xpm", - { "execmem_no_sign", "execmem_write", NULL } }, + { "execmem_no_sign", "anon_execmem", NULL } }, { NULL } }; diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index 56f088907166..e2c6d52b4722 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -54,7 +54,7 @@ void set_xpm_region_info(struct vm_unmapped_area_info *info, } } -static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, +static int xpm_check_segment(bool is_exec, struct vm_area_struct *vma, struct exec_file_signature_info *info) { int i; @@ -83,11 +83,11 @@ static int xpm_segment_check(bool is_exec, struct vm_area_struct *vma, return XPM_SUCCESS; } - xpm_log_error("not executable segment"); + xpm_log_error("vma not in executable segment"); return XPM_FAULT; } -int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) +int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) { int ret = 0; bool is_abc, is_exec; @@ -100,11 +100,6 @@ int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) is_abc = vma->vm_flags & VM_XPM; is_exec = vma->vm_file && (prot & PROT_EXEC); - if (is_abc && ((prot & PROT_EXEC) || (prot & PROT_WRITE))) { - xpm_log_error("abc code not allow exec and write ptotection"); - return XPM_FAULT; - } - /* if no need validate signature, default result is true */ if (!(is_abc || is_exec)) return XPM_SUCCESS; @@ -122,14 +117,14 @@ int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) if (info == NULL) { xpm_log_error("executable file signature is invalid"); - ret = XPM_SIGNATURE_ERROR; + ret = XPM_CHECK_FAILED; goto exit; } - ret = xpm_segment_check(is_exec, vma, info); + ret = xpm_check_segment(is_exec, vma, info); if (ret) { xpm_log_error("xpm executable segment check failed"); - ret = XPM_SIGNATURE_ERROR; + ret = XPM_CHECK_FAILED; goto exit; } exit: @@ -137,6 +132,37 @@ int xpm_validate_signature(struct vm_area_struct *vma, unsigned long prot) return ret; } +int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) +{ + int ret = 0; + + if (!vma) { + xpm_log_error("input vma is NULL"); + return XPM_FAULT; + } + + if (vma->vm_file) { + if ((vma->vm_flags & VM_XPM) && + ((prot & PROT_WRITE) || (prot & PROT_EXEC))) { + xpm_log_error("abc code not allow write or exec prot"); + ret = XPM_FAULT; + } + + if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) { + xpm_log_error("file mmap not allow both exec and write" + " prot"); + ret = XPM_FAULT; + } + } else { + if (prot & PROT_EXEC) { + xpm_log_debug("anonymous mmap has exec prot"); + ret = XPM_CHECK_FAILED; + } + } + + return ret; +} + static int xpm_region_open(struct inode *inode, struct file *file) { struct mm_struct *mm = proc_mem_open(inode, PTRACE_MODE_READ); diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index 4a3969553c30..72691c4719d4 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index 0d84920d4c8a..463d07e5e5e1 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ diff --git a/security/xpm/validator/exec_signature_info.h b/security/xpm/validator/exec_signature_info.h index 0d3ec225cc03..3c84cfe7debc 100644 --- a/security/xpm/validator/exec_signature_info.h +++ b/security/xpm/validator/exec_signature_info.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ -- Gitee From 3d3fb30c2fde2f2244f31b55967fb5319b6a82d6 Mon Sep 17 00:00:00 2001 From: zhushengle Date: Wed, 12 Apr 2023 18:24:19 +0800 Subject: [PATCH 15/35] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E5=8F=AF?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=96=87=E4=BB=B6=E8=A7=A3=E6=9E=90=E6=A6=82?= =?UTF-8?q?=E7=8E=87=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhushengle --- security/xpm/core/xpm_api.c | 4 +- .../xpm/validator/elf_code_segment_info.c | 232 +++++++++++------- security/xpm/validator/exec_signature_info.c | 108 ++++++-- security/xpm/validator/exec_signature_info.h | 50 ++-- 4 files changed, 266 insertions(+), 128 deletions(-) diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index e2c6d52b4722..bc49fc12c71e 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -60,7 +60,7 @@ static int xpm_check_segment(bool is_exec, struct vm_area_struct *vma, int i; unsigned long vm_addr_start, vm_addr_end; unsigned long seg_addr_start, seg_addr_end; - struct exec_segment_info *segment = info->code_segment; + struct exec_segment_info *segment = info->code_segments; if (!is_exec) return XPM_SUCCESS; @@ -73,7 +73,7 @@ static int xpm_check_segment(bool is_exec, struct vm_area_struct *vma, 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_section_count; i++) { + for (i = 0; i < info->code_segment_count; i++) { seg_addr_start = ALIGN_DOWN(segment[i].file_offset, PAGE_SIZE); seg_addr_end = PAGE_ALIGN(segment[i].file_offset + segment[i].size); diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index 72691c4719d4..f8febdea0068 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -8,10 +8,9 @@ #include #include #include - #include "exec_signature_info.h" -struct elf_segment_info { +struct elf_info { struct elfhdr elf_ehdr; uint16_t type; uint16_t e_phnum; @@ -29,16 +28,16 @@ static DEFINE_RWLOCK(fs_verity_tree_lock); static struct rb_root fs_verity_tree = RB_ROOT; static int fs_verity_node_count = 0; -static struct exec_file_signature_info *rb_search_node(struct rb_root *root, struct inode *file_inode) +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 ((uintptr_t)file_inode < (uintptr_t)file_node->inode) { + if (file_inode < file_node->inode) { node = file_node->rb_node.rb_left; - } else if ((uintptr_t)file_inode > (uintptr_t)file_node->inode) { + } else if (file_inode > file_node->inode) { node = file_node->rb_node.rb_right; } else { atomic_inc(&file_node->reference); @@ -48,7 +47,8 @@ static struct exec_file_signature_info *rb_search_node(struct rb_root *root, str return NULL; } -static void rb_add_node(struct rb_root *root, int *node_count, struct exec_file_signature_info *node) +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; @@ -57,10 +57,13 @@ static void rb_add_node(struct rb_root *root, int *node_count, struct exec_file_ while (*p != NULL) { parent = *p; file = rb_entry(parent, struct exec_file_signature_info, rb_node); - if ((uintptr_t)node->inode < (uintptr_t)file->inode) { + if (node->inode < file->inode) { p = &(*p)->rb_left; - } else { + } else if (node->inode > file->inode) { p = &(*p)->rb_right; + } else { + atomic_inc(&file->reference); + return file; } } @@ -68,9 +71,11 @@ static void rb_add_node(struct rb_root *root, int *node_count, struct exec_file_ 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) +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)--; @@ -103,6 +108,7 @@ static int find_idle_nodes(struct rb_root *root, uintptr_t *ilde_nodes, size_t c static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) { + struct exec_file_signature_info *code_segment_info; int i = 0; int count = VERITY_NODE_CACHE_RECYCLE_NUM; uintptr_t *code_segments = kzalloc(count * sizeof(uintptr_t), GFP_KERNEL); @@ -112,7 +118,7 @@ static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) count = find_idle_nodes(root, code_segments, count); while (i < count) { - struct exec_file_signature_info *code_segment_info = (struct exec_file_signature_info *)code_segments[i]; + 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++; @@ -168,16 +174,17 @@ static uint16_t elf16_to_cpu(const struct elfhdr *ehdr, uint16_t value) return value; } -static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, struct elf_segment_info *segment_info) +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 < segment_info->e_phnum; i++) { + for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; - p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); if (!(p_flags & PF_X)) { continue; } @@ -186,7 +193,8 @@ static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, struct elf_ return count; } -static int get_elf32_code_segment(struct elf32_phdr *elf_phdr, struct elf_segment_info *segment_info, struct exec_file_signature_info *exec_file_info) +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; @@ -196,36 +204,36 @@ static int get_elf32_code_segment(struct elf32_phdr *elf_phdr, struct elf_segmen uint32_t p_memsz; uint32_t p_addr; - for (i = 0; i < segment_info->e_phnum; i++) { + for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; - p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); if (!(p_flags & PF_X)) { continue; } - p_offset = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_offset); - p_filesz = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_filesz); - p_addr = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_paddr); - p_memsz = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_memsz); + p_offset = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_offset); + p_filesz = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_filesz); + p_addr = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_paddr); + p_memsz = elf32_to_cpu(&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_segment[exec_file_info->code_section_count].file_offset = p_offset; - exec_file_info->code_segment[exec_file_info->code_section_count].size = p_filesz; - exec_file_info->code_section_count++; + 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_segment_info *segment_info) +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 < segment_info->e_phnum; i++) { + for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; - p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); if (!(p_flags & PF_X)) { continue; } @@ -234,7 +242,8 @@ static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, struct elf_ return count; } -static int get_elf64_code_segment(struct elf64_phdr *elf_phdr, struct elf_segment_info *segment_info, struct exec_file_signature_info *exec_file_info) +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; @@ -244,27 +253,27 @@ static int get_elf64_code_segment(struct elf64_phdr *elf_phdr, struct elf_segmen uint64_t p_memsz; uint64_t p_addr; - for (i = 0; i < segment_info->e_phnum; i++) { + for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; - p_flags = elf32_to_cpu(&segment_info->elf_ehdr, phdr_info->p_flags); + p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); if (!(p_flags & PF_X)) { continue; } - p_offset = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_offset); - p_filesz = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_filesz); - p_addr = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_paddr); - p_memsz = elf64_to_cpu(&segment_info->elf_ehdr, phdr_info->p_memsz); + p_offset = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_offset); + p_filesz = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_filesz); + p_addr = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_paddr); + p_memsz = elf64_to_cpu(&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_segment[exec_file_info->code_section_count].file_offset = p_offset; - exec_file_info->code_segment[exec_file_info->code_section_count].size = p_filesz; - exec_file_info->code_section_count++; + 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_segment_info *segment_info) +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; @@ -275,7 +284,7 @@ static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_s uint16_t type; uint16_t e_ehsize; - struct elfhdr *elf_ehdr = &segment_info->elf_ehdr; + struct elfhdr *elf_ehdr = &elf_info->elf_ehdr; int ret = read_elf_info(file, (void *)elf_ehdr, sizeof(struct elfhdr), 0); if (ret < 0) { @@ -292,58 +301,59 @@ static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_s } if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS32) { - segment_info->type = ELFCLASS32; + elf_info->type = ELFCLASS32; elf32_ehdr = (struct elf32_hdr *)elf_ehdr; e_ehsize = elf16_to_cpu(elf_ehdr, elf32_ehdr->e_ehsize); if (e_ehsize != sizeof(struct elf32_hdr)) { return -ENOEXEC; } - segment_info->e_phnum = elf16_to_cpu(elf_ehdr, elf32_ehdr->e_phnum); - if (segment_info->e_phnum == 0) { + elf_info->e_phnum = elf16_to_cpu(elf_ehdr, elf32_ehdr->e_phnum); + if (elf_info->e_phnum == 0) { return -ENOEXEC; } - e32_phsize = sizeof(struct elf32_phdr) * segment_info->e_phnum; + e32_phsize = sizeof(struct elf32_phdr) * elf_info->e_phnum; e32_phoff = elf32_to_cpu(elf_ehdr, elf32_ehdr->e_phoff); if (e32_phoff > 0 && e32_phoff + e32_phsize < e32_phoff) { return -ENOEXEC; } - segment_info->e_phsize = e32_phsize; - segment_info->e_phoff = e32_phoff; + elf_info->e_phsize = e32_phsize; + elf_info->e_phoff = e32_phoff; } else if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS64) { - segment_info->type = ELFCLASS64; + elf_info->type = ELFCLASS64; elf64_ehdr = (struct elf64_hdr *)elf_ehdr; e_ehsize = elf16_to_cpu(elf_ehdr, elf64_ehdr->e_ehsize); if (e_ehsize != sizeof(struct elf64_hdr)) { return -ENOEXEC; } - segment_info->e_phnum = elf16_to_cpu(elf_ehdr, elf64_ehdr->e_phnum); - if (segment_info->e_phnum == 0) { + elf_info->e_phnum = elf16_to_cpu(elf_ehdr, elf64_ehdr->e_phnum); + if (elf_info->e_phnum == 0) { return -ENOEXEC; } - e64_phsize = sizeof(struct elf64_phdr) * segment_info->e_phnum; + e64_phsize = sizeof(struct elf64_phdr) * elf_info->e_phnum; e64_phoff = elf64_to_cpu(elf_ehdr, elf64_ehdr->e_phoff); if (e64_phoff > 0 && e64_phoff + e64_phsize < e64_phoff) { return -ENOEXEC; } - segment_info->e_phsize = e64_phsize; - segment_info->e_phoff = e64_phoff; + 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_segment_info *segment_info, struct exec_file_signature_info **file_info) +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 (segment_info->type == ELFCLASS32) { - segment_count = get_elf32_code_segment_count((struct elf32_phdr *)phdr_info, segment_info); + 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, segment_info); + segment_count = get_elf64_code_segment_count((struct elf64_phdr *)phdr_info, elf_info); } if (segment_count == 0) { return -ENOEXEC; @@ -354,11 +364,12 @@ static int find_elf_code_segment_info(const char *phdr_info, struct elf_segment_ if (exec_file_info == NULL) { return -ENOMEM; } - exec_file_info->code_segment = (struct exec_segment_info *)((char *)exec_file_info + sizeof(struct exec_file_signature_info)); - if (segment_info->type == ELFCLASS32) { - ret = get_elf32_code_segment((struct elf32_phdr *)phdr_info, segment_info, exec_file_info); + 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, segment_info, exec_file_info); + ret = get_elf64_code_segment((struct elf64_phdr *)phdr_info, elf_info, exec_file_info); } if (ret < 0) { kfree(exec_file_info); @@ -368,45 +379,48 @@ static int find_elf_code_segment_info(const char *phdr_info, struct elf_segment_ return 0; } -static int parse_elf_code_segment_info(struct file *file, struct exec_file_signature_info **code_segment_info) +static int parse_elf_code_segment_info(struct file *file, + struct exec_file_signature_info **code_segment_info) { const char *phdr_info; - struct elf_segment_info segment_info = {0}; - int ret = elf_check_and_get_code_segment_offset(file, &segment_info); + struct elf_info elf_info = {0}; + int ret = elf_check_and_get_code_segment_offset(file, &elf_info); if (ret < 0) { return ret; } - phdr_info = kzalloc(segment_info.e_phsize, GFP_KERNEL); + phdr_info = kzalloc(elf_info.e_phsize, GFP_KERNEL); if (phdr_info == NULL) { return -ENOMEM; } - ret = read_elf_info(file, (void *)phdr_info, segment_info.e_phsize, segment_info.e_phoff); + 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, &segment_info, code_segment_info); + ret = find_elf_code_segment_info(phdr_info, &elf_info, code_segment_info); kfree(phdr_info); return ret; } -int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_signature_info **code_segment_info) +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 *segment_info = NULL; + struct exec_file_signature_info *new_info; + struct exec_file_signature_info *tmp_info; - if (type == FILE_DM_VERITY) { + 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_FS_VERITY) { + } else if (type == FILE_SIGNATURE_FS_VERITY) { verity_lock = &fs_verity_tree_lock; root = &fs_verity_tree; node_count = &fs_verity_node_count; @@ -420,46 +434,66 @@ int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct } read_lock(verity_lock); - segment_info = rb_search_node(root, file_node); + tmp_info = rb_search_node(root, (uintptr_t)file_node); read_unlock(verity_lock); - if (segment_info != NULL) { - *code_segment_info = segment_info; + 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) { - segment_info = (struct exec_file_signature_info *)kzalloc(sizeof(struct exec_file_signature_info), GFP_KERNEL); - if (segment_info == NULL) { + new_info = (struct exec_file_signature_info *)kzalloc(sizeof(struct exec_file_signature_info), GFP_KERNEL); + if (new_info == NULL) { return -ENOMEM; } } else { - ret = parse_elf_code_segment_info(file, &segment_info); + ret = parse_elf_code_segment_info(file, &new_info); if (ret < 0) { return ret; } } - segment_info->type = type; - segment_info->inode = file_node; - RB_CLEAR_NODE(&segment_info->rb_node); + 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); - rb_add_node(root, node_count, segment_info); + tmp_info = rb_add_node(root, node_count, new_info); write_unlock(verity_lock); - *code_segment_info = segment_info; + if (tmp_info != NULL) { + kfree(new_info); + new_info = tmp_info; + } + *code_segment_info = new_info; return 0; } int put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) { if ((code_segment_info == NULL) || - ((code_segment_info->type != FILE_DM_VERITY) && (code_segment_info->type != FILE_FS_VERITY))) { + !exec_file_signature_is_verity(code_segment_info)) { return -EINVAL; } - atomic_dec_and_test(&code_segment_info->reference); + if (atomic_sub_return(1, &code_segment_info->reference) <= 0 && + exec_file_signature_is_delete(code_segment_info)) { + kfree(code_segment_info); + } return 0; } @@ -474,11 +508,11 @@ int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segm return -EINVAL; } - if (code_segment_info->type == FILE_DM_VERITY) { + if (exec_file_signature_is_dm_verity(code_segment_info)) { root = &dm_verity_tree; verity_lock = &dm_verity_tree_lock; node_count = &dm_verity_node_count; - } else if (code_segment_info->type == FILE_FS_VERITY) { + } else if (exec_file_signature_is_fs_verity(code_segment_info)) { verity_lock = &fs_verity_tree_lock; root = &fs_verity_tree; node_count = &fs_verity_node_count; @@ -540,6 +574,20 @@ int test_destroy_elf_code_segment_info_cache(void) return count; } +void test_rm_elf_code_segment_info_cache(void) +{ + 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 size_t elf_code_segment_info_size(struct rb_root *root) { size_t size = 0; @@ -547,12 +595,13 @@ static size_t elf_code_segment_info_size(struct rb_root *root) 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_section_count * sizeof(struct exec_segment_info); + size += sizeof(struct exec_file_signature_info) + + file_node->code_segment_count * sizeof(struct exec_segment_info); } return size; } -size_t test_get_elf_code_segment_info_cache_size(int *cache_count) +void test_get_elf_code_segment_info_cache_size(void) { size_t cache_size = 0; int count = 0; @@ -567,16 +616,15 @@ size_t test_get_elf_code_segment_info_cache_size(int *cache_count) count += fs_verity_node_count; read_unlock(&fs_verity_tree_lock); - if (cache_count) { - *cache_count = count; - } - return cache_size; + pr_info("[exec signature cache] count=%d, cache size=%d KB\n", count, cache_size / 1024); } -void test_print_elf_code_segment_info(const char *file_path, const struct exec_file_signature_info *file_info) +void test_print_elf_code_segment_info(const char *file_path, + const struct exec_file_signature_info *file_info) { int i; - for (i = 0; i < file_info->code_section_count; i++) { - pr_info("%s -> offset: 0x%llx size: 0x%lx\n", file_path, file_info->code_segment->file_offset, file_info->code_segment->size); + for (i = 0; i < file_info->code_segment_count; i++) { + pr_info("%s -> offset: 0x%llx size: 0x%lx\n", + file_path, file_info->code_segments->file_offset, file_info->code_segments->size); } } diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index 463d07e5e5e1..f9ac67a3470f 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -6,45 +6,115 @@ #include #include #include +#include +#include +#include +#include "../fs/mount.h" #include "exec_signature_info.h" -static int check_exec_file_is_verity(struct file *file, struct inode *file_node) +#ifdef CONFIG_DM_VERITY +#define HVB_CMDLINE_VB_STATE "ohos.boot.hvb.enable" +static bool dm_verity_enable = false; + +static int hvb_boot_param_cb(char *param, char *val, + const char *unused, void *arg) { - /* 校验是否是 dm-verity - * /system/lib /system/lib64 /system/bin - */ - char buf[PATH_MAX]; - char *file_name = file_path(file, buf, PATH_MAX); - if (file_name == NULL) { - return FILE_NORMAL; + if (param == NULL || val == NULL) { + return 0; + } + if (strcmp(param, HVB_CMDLINE_VB_STATE) != 0) { + return 0; + } + if (strcmp(val, "true") == 0 || strcmp(val, "TRUE") == 0) { + dm_verity_enable = true; } + return 0; +} -#ifdef CONFIG_FS_VERITY - if (file_node->i_verity_info != NULL) { - return FILE_FS_VERITY; +static bool dm_verity_is_enable(void) +{ + static bool dm_verity_check = false; + char *cmdline; + + if (!dm_verity_check && !dm_verity_enable) { + cmdline = kstrdup(saved_command_line, GFP_KERNEL); + if (cmdline == NULL) { + return false; + } + parse_args("hvb.enable params", cmdline, NULL, + 0, 0, 0, NULL, &hvb_boot_param_cb); + kfree(cmdline); } -#endif - return FILE_DM_VERITY; + dm_verity_check = true; + return dm_verity_enable; } -int get_exec_file_signature_info(struct file *file, bool is_exec, struct exec_file_signature_info **info_ptr) +static bool is_dm_verity(struct file *file) { - int type; + struct mount *mnt; + struct mapped_device *device; - if (file == NULL || info_ptr == NULL) { - return -EINVAL; + if (!dm_verity_is_enable()) { + return false; + } + if (file->f_path.mnt == NULL) { + return false; } + mnt = container_of(test_file->f_path.mnt, struct mount, mnt); + device = dm_get_md(dm_get_dev_t(mnt->mnt_devname)); + if (device == NULL) { + return false; + } + dm_put(device); + return true; +} +#endif +#ifdef CONFIG_FS_VERITY +static bool is_fs_verity(struct file *file) +{ struct inode *file_node = file_inode(file); if (file_node == NULL) { + return false; + } + + if (file_node->i_verity_info == NULL) { + return false; + } + return true; +} +#endif + +static int check_exec_file_is_verity(struct file *file) +{ + /* 测试demo */ +#ifdef CONFIG_DM_VERITY + if (is_dm_verity(file)) { + return FILE_SIGNATURE_DM_VERITY; + } +#endif +#ifdef CONFIG_FS_VERITY + if (is_fs_verity(file)) { + return FILE_SIGNATURE_FS_VERITY; + } +#endif + return FILE_SIGNATURE_DM_VERITY; +} + +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, file_node); + 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) { - return put_elf_code_segment_info(exec_info); + return put_elf_code_segment_info(exec_info); } diff --git a/security/xpm/validator/exec_signature_info.h b/security/xpm/validator/exec_signature_info.h index 3c84cfe7debc..5f74aeb40d26 100644 --- a/security/xpm/validator/exec_signature_info.h +++ b/security/xpm/validator/exec_signature_info.h @@ -8,29 +8,49 @@ #include #include #include +#include struct exec_segment_info { - unsigned long file_offset; - unsigned long size; + uintptr_t file_offset; + size_t size; }; -#define FILE_FS_VERITY 0 -#define FILE_DM_VERITY 1 -#define FILE_NORMAL 2 +#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; - int type; - struct inode *inode; - uint8_t hash[32]; - int code_section_count; - struct exec_segment_info *code_segment; + struct rb_node rb_node; + atomic_t reference; + unsigned type; + uintptr_t inode; + unsigned code_segment_count; + struct exec_segment_info *code_segments; }; -void test_print_elf_code_segment_info(const char *file_path, const struct exec_file_signature_info *file_info); -int test_destroy_elf_code_segment_info_cache(void); -int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); +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 put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); int get_elf_code_segment_info(struct file *file, bool is_exec, int type, 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); -- Gitee From 769e048d790d969f10659bff382bcc8e774e6eec Mon Sep 17 00:00:00 2001 From: limerence Date: Mon, 17 Apr 2023 11:42:46 +0800 Subject: [PATCH 16/35] add xpm anon judge Signed-off-by: limerence --- include/linux/xpm.h | 18 ++++++++++--- mm/mmap.c | 19 +++++++++----- security/xpm/core/xpm_api.c | 46 ++++++++++++++++++---------------- security/xpm/core/xpm_report.c | 19 ++++++-------- security/xpm/core/xpm_report.h | 4 +-- 5 files changed, 61 insertions(+), 45 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index f103e20b5bf0..e4c1082b8259 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -45,7 +45,7 @@ extern int xpm_value(int rc); /** * check whether input address range is out of the xpm region */ -extern bool is_xpm_region_outer(unsigned long addr_start, +extern bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags); /** @@ -64,6 +64,11 @@ extern int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot); */ extern int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot); +/** + * judge vma is anonymous or not? + */ +extern bool xpm_is_anonymous_vma(struct vm_area_struct *vma); + #define XPM_PRINT_COUNT 10000 /* * Check the confliction of a page's xpm flags, make sure a process will not map @@ -80,7 +85,7 @@ static inline int xpm_value(int rc) return rc; } -static inline bool is_xpm_region_outer(unsigned long addr_start, +static inline bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags) { return true; @@ -94,13 +99,18 @@ static inline void set_xpm_region_info(struct vm_unmapped_area_info *info, static inline int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) { - return XPM_SUCCESS; + return 0; } static inline int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) { - return XPM_SUCCESS; + return 0; +} + +bool xpm_is_anonymous_vma(struct vm_area_struct *vma) +{ + return false; } static inline bool xpm_integrity_equal(struct page *page, diff --git a/mm/mmap.c b/mm/mmap.c index e47fdd463508..3d49afe74f40 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1852,6 +1852,10 @@ unsigned long mmap_region(struct file *file, unsigned long addr, * as we may succeed this time. */ if (unlikely(vm_flags != vma->vm_flags && prev)) { + error = security_mmap_region(vma); + if (error) + goto unmap_and_free_vma; + merge = vma_merge(mm, prev, vma->vm_start, vma->vm_end, vma->vm_flags, NULL, vma->vm_file, vma->vm_pgoff, NULL, NULL_VM_UFFD_CTX, NULL); if (merge) { @@ -1878,7 +1882,8 @@ unsigned long mmap_region(struct file *file, unsigned long addr, } /* Allow architectures to sanity-check the vma */ - if (security_mmap_region(vma) || !arch_validate_flags(vma->vm_flags)) { + if (security_mmap_region(vma) || + !arch_validate_flags(vma->vm_flags)) { error = -EINVAL; if (file) goto close_and_free_vma; @@ -2002,7 +2007,7 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info) return -ENOMEM; if ((gap_end >= low_limit && gap_end > gap_start && gap_end - gap_start >= length) && - (is_xpm_region_outer(gap_start, gap_end, info->flags))) + (xpm_is_region_outer(gap_start, gap_end, info->flags))) goto found; /* Visit right subtree if it looks promising */ @@ -2107,7 +2112,7 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) return -ENOMEM; if ((gap_start <= high_limit && gap_end > gap_start && gap_end - gap_start >= length) && - (is_xpm_region_outer(gap_start, gap_end, info->flags))) + (xpm_is_region_outer(gap_start, gap_end, info->flags))) goto found; /* Visit left subtree if it looks promising */ @@ -2203,14 +2208,15 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, if (addr) { if (flags & MAP_XPM) { xpm_log_error("not allow specify addr with xpm flag"); - return -EINVAL; + return -EPERM; } + addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); if (mmap_end - len >= addr && addr >= mmap_min_addr && (!vma || addr + len <= vm_start_gap(vma)) && (!prev || addr >= vm_end_gap(prev)) && - (is_xpm_region_outer(addr, addr + len, 0))) + (xpm_is_region_outer(addr, addr + len, 0))) return addr; } @@ -2253,12 +2259,13 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, xpm_log_error("not allow specify addr with xpm flag"); return -EPERM; } + addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); if (mmap_end - len >= addr && addr >= mmap_min_addr && (!vma || addr + len <= vm_start_gap(vma)) && (!prev || addr >= vm_end_gap(prev)) && - (is_xpm_region_outer(addr, addr + len, 0))) + (xpm_is_region_outer(addr, addr + len, 0))) return addr; } diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index bc49fc12c71e..e88a1db87cda 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -18,7 +18,17 @@ extern struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); -bool is_xpm_region_outer(unsigned long addr_start, unsigned long addr_end, +bool xpm_is_anonymous_vma(struct vm_area_struct *vma) +{ + if (!vma) { + xpm_log_error("input vma is NULL"); + return false; + } + + return vma_is_anonymous(vma) | vma_is_shmem(vma); +} + +bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags) { struct mm_struct *mm = current->mm; @@ -90,18 +100,16 @@ static int xpm_check_segment(bool is_exec, struct vm_area_struct *vma, int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) { int ret = 0; - bool is_abc, is_exec; struct exec_file_signature_info *info = NULL; + bool is_exec = !xpm_is_anonymous_vma(vma) && (prot & PROT_EXEC); if (!vma) { xpm_log_error("input vma is NULL"); return XPM_FAULT; } - is_abc = vma->vm_flags & VM_XPM; - is_exec = vma->vm_file && (prot & PROT_EXEC); /* if no need validate signature, default result is true */ - if (!(is_abc || is_exec)) + if (!((vma->vm_flags & VM_XPM) || is_exec)) return XPM_SUCCESS; /* @@ -141,23 +149,17 @@ int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) return XPM_FAULT; } - if (vma->vm_file) { - if ((vma->vm_flags & VM_XPM) && - ((prot & PROT_WRITE) || (prot & PROT_EXEC))) { - xpm_log_error("abc code not allow write or exec prot"); - ret = XPM_FAULT; - } - - if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) { - xpm_log_error("file mmap not allow both exec and write" - " prot"); - ret = XPM_FAULT; - } - } else { - if (prot & PROT_EXEC) { - xpm_log_debug("anonymous mmap has exec prot"); - ret = XPM_CHECK_FAILED; - } + if ((vma->vm_flags & VM_XPM) && (xpm_is_anonymous_vma(vma) || + (prot & PROT_WRITE) || (prot & PROT_EXEC))) { + xpm_log_error("mmap not allow anonymous/exec/write in xpm" + " region"); + return XPM_FAULT; + } + + if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) { + xpm_log_info("mmap not allow write & exec permission out xpm" + " region"); + return XPM_CHECK_FAILED; } return ret; diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index f6d955bf2ca1..9e7bf79a10d6 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -9,7 +9,6 @@ #include #include "xpm_report.h" -#ifdef CONFIG_XPM void report_integrity_violated(struct vm_fault *vmf, struct page *page) { if(printk_ratelimit()) @@ -21,7 +20,7 @@ static char *get_file_name(struct file *file, char *path_buf, int len) char *filename; struct dentry *dentry; - if (!file) + if (!file) return NULL; dentry = file->f_path.dentry; @@ -152,7 +151,7 @@ void report_code_tampered(struct vm_fault *vmf, struct page *page) event_info *event; event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, - GFP_KERNEL); + GFP_KERNEL); if (event == NULL) { xpm_log_error("kmalloc event_info failed"); return; @@ -165,11 +164,11 @@ void report_code_tampered(struct vm_fault *vmf, struct page *page) } vma = vmf->vma; - filename = vma_is_anonymous(vma) ? "Anon" : - get_file_name(vma->vm_file, buf, MAX_CONTENT_LEN); + filename = vma_is_anonymous(vma) ? + "Anon" : get_file_name(vma->vm_file, buf, MAX_CONTENT_LEN); - page_type = PageKsm(page) ? "[ksm]" : - PageAnon(page) ? "[anon]" : "[file]"; + page_type = PageKsm(page) ? + "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; snprintf(event->content, MAX_CONTENT_LEN, XPM_INTEGRITY_TAMPERED_JSON, XPM_INTEGRITY_TAMPERED, ktime_get_real(), current->pid, get_xpm_flags(page), @@ -183,12 +182,10 @@ void report_code_tampered(struct vm_fault *vmf, struct page *page) return; } -#ifndef _HW_KERNEL_SG_COLLECT_H_ +#ifndef CONFIG_HW_KERNEL_SG unsigned int xpm_report_security_info(const event_info *info) { - xpm_log_error("%s", info); + xpm_log_error("%s", info->content); return 0; } #endif - -#endif diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h index e7536a455283..3121bf284e59 100644 --- a/security/xpm/core/xpm_report.h +++ b/security/xpm/core/xpm_report.h @@ -18,7 +18,7 @@ enum { XPM_STASTIC_EVENT }; -#ifdef _HW_KERNEL_SG_COLLECT_H_ +#ifdef CONFIG_HW_KERNEL_SG #include #define XPM_SG_EVENT_ID 111 @@ -144,7 +144,7 @@ inline void report_integrity_violated(struct vm_fault *vmf, void report_xpm_init_failed(int errs_code) {} -void report_file_format_damaged(struct file *file, bool signature, +void report_file_format_damaged(struct file *file, bool signature, char *log) {} void report_code_map_failed(int err_code, struct file *file, bool signature, -- Gitee From edbdc5fbf388c037e29b7e80f3688c81ae61eab6 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Mon, 17 Apr 2023 12:19:34 +0800 Subject: [PATCH 17/35] feat: add ratelimit for report Change-Id: I44bf3b74fc62dab8c331d06ceff8132e92cb3cb5 --- mm/migrate.c | 2 +- mm/mmap.c | 1 + security/xpm/core/xpm_integrity.c | 4 ++-- security/xpm/core/xpm_report.c | 26 +++++++++++++++++++++++--- security/xpm/core/xpm_report.h | 12 ++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/mm/migrate.c b/mm/migrate.c index af0b7bf83ad2..7ed4eb406d12 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -639,7 +639,7 @@ void migrate_page_states(struct page *newpage, struct page *page) /* Migrate the page's xpm state */ if(PageXPMWritetainted(page)) SetPageXPMWritetainted(newpage); - + if(PageXPMReadonly(page)) SetPageXPMReadonly(newpage); diff --git a/mm/mmap.c b/mm/mmap.c index 3d49afe74f40..443343c97e43 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1483,6 +1483,7 @@ unsigned long do_mmap(struct file *file, unsigned long addr, */ vm_flags = calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; + trace_vendor_do_mmap(&vm_flags, &err); if (err) return err; diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index 9da443544bc0..d7886e2c0107 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -35,7 +35,7 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) return true; /* 2. !Anonymous && Executable */ - if (!vma_is_anonymous(vma) && (vma->vm_flags & VM_EXEC)) + if (!xpm_is_anonymous_vma(vma) && (vma->vm_flags & VM_EXEC)) return true; return false; @@ -82,7 +82,7 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) /* Set xpm readonly flag */ if (is_xpm_readonly_region(vma) && !PageXPMReadonly(page)) SetPageXPMReadonly(page); - + return; } diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 9e7bf79a10d6..1208fd330eda 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -11,7 +11,8 @@ void report_integrity_violated(struct vm_fault *vmf, struct page *page) { - if(printk_ratelimit()) + INIT_RATELIMIT(); + if(GET_RATELIMIT()) report_code_tampered(vmf, page); } @@ -32,6 +33,9 @@ static char *get_file_name(struct file *file, char *path_buf, int len) static inline void report_xpm_event(event_info *event) { + INIT_RATELIMIT(); + if(!GET_RATELIMIT()) + return; event->event_id = XPM_SG_EVENT_ID; event->version = XPM_SG_VERSION; event->content_len = strlen(event->content) + 1; @@ -42,6 +46,10 @@ void report_xpm_init_failed(int err_code) { event_info *event; + INIT_RATELIMIT(); + if(!GET_RATELIMIT()) + return; + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); @@ -69,6 +77,10 @@ void report_file_format_damaged(struct file *file, bool signature, char *log) return; } + INIT_RATELIMIT(); + if(!GET_RATELIMIT()) + return; + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); if (!event) { @@ -109,6 +121,10 @@ void report_code_map_failed(int err_code, struct file *file, bool signature, if (!file) return; + INIT_RATELIMIT(); + if(!GET_RATELIMIT()) + return; + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); if (!event) { @@ -150,6 +166,10 @@ void report_code_tampered(struct vm_fault *vmf, struct page *page) struct vm_area_struct *vma; event_info *event; + INIT_RATELIMIT(); + if(!GET_RATELIMIT()) + return; + event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, GFP_KERNEL); if (event == NULL) { @@ -183,9 +203,9 @@ void report_code_tampered(struct vm_fault *vmf, struct page *page) } #ifndef CONFIG_HW_KERNEL_SG -unsigned int xpm_report_security_info(const event_info *info) +unsigned int xpm_report_security_info(const event_info *event) { - xpm_log_error("%s", info->content); + xpm_log_error("%s", event->content); return 0; } #endif diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h index 3121bf284e59..864c994c616b 100644 --- a/security/xpm/core/xpm_report.h +++ b/security/xpm/core/xpm_report.h @@ -18,6 +18,18 @@ enum { XPM_STASTIC_EVENT }; +#ifdef CONFIG_PRINTK +#define INIT_RATELIMIT() \ + static DEFINE_RATELIMIT_STATE(_rs, \ + DEFAULT_RATELIMIT_INTERVAL, \ + DEFAULT_RATELIMIT_BURST); +#define GET_RATELIMIT() \ + __ratelimit(&_rs) +#else +#define INIT_RATELIMIT() +#define GET_RATELIMIT() 1 +#endif + #ifdef CONFIG_HW_KERNEL_SG #include -- Gitee From d6836ae2d26bd06f7836e7bb01e5e50099213c09 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Mon, 17 Apr 2023 14:26:08 +0800 Subject: [PATCH 18/35] fix: fix implicit declaration errors Change-Id: Iab5ef544500fd937ad195510223160473e8afadd --- include/linux/page-flags.h | 4 ++-- include/linux/security.h | 4 +--- include/linux/xpm.h | 7 ++++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index 98fefde53fb6..0e5735861ec7 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -358,8 +358,8 @@ PAGEFLAG(Checked, checked, PF_NO_COMPOUND) /* Used by some filesystems */ PAGEFLAG(XPMReadonly, xpm_readonly, PF_HEAD) PAGEFLAG(XPMWritetainted, xpm_writetainted, PF_HEAD) #else -PAGEFLAG_FALSE(Readonly) -PAGEFLAG_FALSE(Writetainted) +PAGEFLAG_FALSE(XPMReadonly) +PAGEFLAG_FALSE(XPMWritetainted) #endif /* Xen */ diff --git a/include/linux/security.h b/include/linux/security.h index d00fff460664..4fa856ad7d7d 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2002,8 +2002,7 @@ static inline int security_perf_event_write(struct perf_event *event) #endif /* CONFIG_SECURITY */ #endif /* CONFIG_PERF_EVENTS */ -#ifdef CONFIG_XPM -#ifdef CONFIG_SECURITY +#if IS_ENABLED(CONFIG_SECURITY) && IS_ENABLED(CONFIG_XPM) extern int security_mmap_region(struct vm_area_struct *vma); #else static inline int security_mmap_region(struct vm_area_struct *vma) @@ -2011,6 +2010,5 @@ static inline int security_mmap_region(struct vm_area_struct *vma) return 0; } #endif /* CONFIG_SECURITY */ -#endif /* CONFIG_XPM */ #endif /* ! __LINUX_SECURITY_H */ diff --git a/include/linux/xpm.h b/include/linux/xpm.h index e4c1082b8259..35e4ec4a6e42 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -80,6 +80,11 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page); bool xpm_integrity_equal(struct page *page, struct page *kpage); #else + +#define xpm_log_info(fmt, args...) +#define xpm_log_error(fmt, args...) +#define xpm_log_debug(fmt, args...) + static inline int xpm_value(int rc) { return rc; @@ -108,7 +113,7 @@ static inline int xpm_check_signature(struct vm_area_struct *vma, return 0; } -bool xpm_is_anonymous_vma(struct vm_area_struct *vma) +static inline bool xpm_is_anonymous_vma(struct vm_area_struct *vma) { return false; } -- Gitee From c5326f4b4418548fb6d4f6a5987b5abc0409d1c8 Mon Sep 17 00:00:00 2001 From: limerence Date: Wed, 19 Apr 2023 14:31:56 +0800 Subject: [PATCH 19/35] fix coding bug Signed-off-by: limerence --- mm/mmap.c | 12 +++++++++--- security/xpm/core/xpm_api.c | 24 ++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/mm/mmap.c b/mm/mmap.c index 443343c97e43..743e575204ca 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1461,8 +1461,10 @@ unsigned long do_mmap(struct file *file, unsigned long addr, * that it represents a valid section of the address space. */ addr = get_unmapped_area(file, addr, len, pgoff, flags); - if (IS_ERR_VALUE(addr)) + if (IS_ERR_VALUE(addr)) { + xpm_log_error("get_unmapped_area failed: 0x%lx", addr); return addr; + } if (flags & MAP_FIXED_NOREPLACE) { struct vm_area_struct *vma = find_vma(mm, addr); @@ -2077,8 +2079,10 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) return -ENOMEM; high_limit = gap_end - length; - if (info->low_limit > high_limit) + if (info->low_limit > high_limit) { + xpm_log_error("low limit bigger than high_limit: [0x%x, 0x%x]", info->low_limit, high_limit); return -ENOMEM; + } low_limit = info->low_limit + length; /* Check highest gap, which does not precede any rbtree node */ @@ -2327,8 +2331,10 @@ get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, } addr = get_area(file, addr, len, pgoff, flags); - if (IS_ERR_VALUE(addr)) + if (IS_ERR_VALUE(addr)) { + xpm_log_error("get_area failed: 0x%lx", addr); return addr; + } if (addr > TASK_SIZE - len) return -ENOMEM; diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index e88a1db87cda..cc3ba19dcb94 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -25,7 +25,7 @@ bool xpm_is_anonymous_vma(struct vm_area_struct *vma) return false; } - return vma_is_anonymous(vma) | vma_is_shmem(vma); + return vma_is_anonymous(vma) || vma_is_shmem(vma); } bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, @@ -142,27 +142,31 @@ int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) { - int ret = 0; + bool is_anon = xpm_is_anonymous_vma(vma); if (!vma) { xpm_log_error("input vma is NULL"); return XPM_FAULT; } - if ((vma->vm_flags & VM_XPM) && (xpm_is_anonymous_vma(vma) || - (prot & PROT_WRITE) || (prot & PROT_EXEC))) { - xpm_log_error("mmap not allow anonymous/exec/write in xpm" - " region"); + 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 XPM_FAULT; } - if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) { - xpm_log_info("mmap not allow write & exec permission out xpm" - " region"); + if (is_anon && (prot & PROT_EXEC)) { + xpm_log_debug("anonymous mmap not suggest has exec permission"); return XPM_CHECK_FAILED; } - return ret; + if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) { + xpm_log_error("mmap not allow write & exec permission"); + return XPM_FAULT; + } + + return XPM_SUCCESS; } static int xpm_region_open(struct inode *inode, struct file *file) -- Gitee From 9db311d88ae3283d3ae2a07f743784db2b81c54e Mon Sep 17 00:00:00 2001 From: zhangpan Date: Wed, 19 Apr 2023 19:21:51 +0800 Subject: [PATCH 20/35] fix: fix a deadlock bug Change-Id: I747bd2ef290de8e94ec4ceb3658013ea9b5fef97 --- mm/memory.c | 22 +++++++++++++++------- security/xpm/core/xpm_integrity.c | 3 +-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/mm/memory.c b/mm/memory.c index 50386654778f..84941af7592d 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -3041,9 +3041,11 @@ vm_fault_t finish_mkwrite_fault(struct vm_fault *vmf) return VM_FAULT_NOPAGE; } - if (xpm_integrity_validate(vmf, vmf->page)) + if (unlikely(xpm_integrity_validate(vmf, vmf->page))) { + pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_SIGSEGV; - + } + wp_page_reuse(vmf); return 0; } @@ -3096,8 +3098,11 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf) } } else { - if (xpm_integrity_validate(vmf, vmf->page)) + if (unlikely(xpm_integrity_validate(vmf, vmf->page))){ + pte_unmap_unlock(vmf->pte, vmf->ptl); + put_page(vmf->page); return VM_FAULT_SIGSEGV; + } wp_page_reuse(vmf); lock_page(vmf->page); @@ -3184,8 +3189,10 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf) */ unlock_page(page); - if (xpm_integrity_validate(vmf, vmf->page)) + if (unlikely(xpm_integrity_validate(vmf, vmf->page))){ + pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_SIGSEGV; + } wp_page_reuse(vmf); return VM_FAULT_WRITE; @@ -3502,11 +3509,12 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) * must be called after the swap_free(), or it will never succeed. */ - if (xpm_integrity_validate(vmf, page)) { + + if (unlikely(xpm_integrity_validate(vmf, page))){ ret = VM_FAULT_SIGSEGV; goto out_nomap; } - + inc_mm_counter_fast(vma->vm_mm, MM_ANONPAGES); dec_mm_counter_fast(vma->vm_mm, MM_SWAPENTS); pte = mk_pte(page, vma->vm_page_prot); @@ -3959,7 +3967,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct page *page) } /* check the confliction of xpm integrity flags*/ - if (xpm_integrity_validate(vmf, page)) + if (unlikely(xpm_integrity_validate(vmf, page))) return VM_FAULT_SIGSEGV; flush_icache_page(vma, page); diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index d7886e2c0107..f316def2ee55 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -30,8 +30,7 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) xpm_region = &mm->xpm_region; /* 1. XPM Secure Region */ - if (vma->vm_start <= xpm_region->addr_start && - xpm_region->addr_end <= vma->vm_end) + if (vma->vm_flags & VM_XPM) return true; /* 2. !Anonymous && Executable */ -- Gitee From 88b276573cd6cdd6e53794af7136f7fe1dc239e6 Mon Sep 17 00:00:00 2001 From: limerence Date: Thu, 20 Apr 2023 19:57:31 +0800 Subject: [PATCH 21/35] fix mmap limit test error Signed-off-by: limerence --- include/linux/xpm.h | 12 ++--- mm/mmap.c | 23 +++++----- security/selinux/hooks.c | 1 + security/xpm/core/xpm_api.c | 90 ++++++++++++++++++------------------- 4 files changed, 62 insertions(+), 64 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 35e4ec4a6e42..5d112807423f 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -49,10 +49,10 @@ extern bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, unsigned long flags); /** - * set xpm region info if has MAP_XPM flag + * get unmapped area in xpm region */ -extern void set_xpm_region_info(struct vm_unmapped_area_info *info, - unsigned long flags); +extern unsigned long xpm_get_unmapped_area(unsigned long addr, unsigned long len, + unsigned long map_flags, unsigned long unmapped_flags); /** * check whether mmap vma has a valid prot @@ -96,9 +96,11 @@ static inline bool xpm_is_region_outer(unsigned long addr_start, return true; } -static inline void set_xpm_region_info(struct vm_unmapped_area_info *info, - unsigned long flags) +static inline unsigned long xpm_get_unmapped_area(unsigned long addr, + unsigned long len, unsigned long map_flags, + unsigned long unmapped_flags) { + return 0; } static inline int xpm_check_prot(struct vm_area_struct *vma, diff --git a/mm/mmap.c b/mm/mmap.c index 743e575204ca..3abcd0dd8982 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -2199,6 +2199,7 @@ unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { + unsigned long xpm_addr; struct mm_struct *mm = current->mm; struct vm_area_struct *vma, *prev; struct vm_unmapped_area_info info; @@ -2207,15 +2208,14 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, if (len > mmap_end - mmap_min_addr) return -ENOMEM; + xpm_addr = xpm_get_unmapped_area(addr, len, flags, 0); + if (xpm_addr) + return xpm_addr; + if (flags & MAP_FIXED) return addr; if (addr) { - if (flags & MAP_XPM) { - xpm_log_error("not allow specify addr with xpm flag"); - return -EPERM; - } - addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); if (mmap_end - len >= addr && addr >= mmap_min_addr && @@ -2231,7 +2231,6 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, info.high_limit = mmap_end; info.align_mask = 0; info.align_offset = 0; - set_xpm_region_info(&info, flags); return vm_unmapped_area(&info); } #endif @@ -2246,6 +2245,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { + unsigned long xpm_addr; struct vm_area_struct *vma, *prev; struct mm_struct *mm = current->mm; struct vm_unmapped_area_info info; @@ -2255,16 +2255,16 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, if (len > mmap_end - mmap_min_addr) return -ENOMEM; + xpm_addr = xpm_get_unmapped_area(addr, len, flags, + VM_UNMAPPED_AREA_TOPDOWN); + if (xpm_addr) + return xpm_addr; + if (flags & MAP_FIXED) return addr; /* requesting a specific address */ if (addr) { - if (flags & MAP_XPM) { - xpm_log_error("not allow specify addr with xpm flag"); - return -EPERM; - } - addr = PAGE_ALIGN(addr); vma = find_vma_prev(mm, addr, &prev); if (mmap_end - len >= addr && addr >= mmap_min_addr && @@ -2280,7 +2280,6 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, info.high_limit = arch_get_mmap_base(addr, mm->mmap_base); info.align_mask = 0; info.align_offset = 0; - set_xpm_region_info(&info, flags); addr = vm_unmapped_area(&info); /* diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8279a6a593ef..36500a2d400d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6995,6 +6995,7 @@ static int xpm_signature_control(struct vm_area_struct *vma, unsigned long prot) return xpm_value(rc); rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_NO_SIGN); + return xpm_value(rc); } diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index cc3ba19dcb94..b9628bc3b724 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -20,11 +20,6 @@ extern struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); bool xpm_is_anonymous_vma(struct vm_area_struct *vma) { - if (!vma) { - xpm_log_error("input vma is NULL"); - return false; - } - return vma_is_anonymous(vma) || vma_is_shmem(vma); } @@ -33,11 +28,7 @@ bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, { struct mm_struct *mm = current->mm; - if (!mm) { - xpm_log_error("current mm is NULL"); - return true; - } - + /* Already in xpm region, just return without judge */ if (flags & VM_UNMAPPED_AREA_XPM) return true; @@ -45,38 +36,50 @@ bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, (addr_end <= mm->xpm_region.addr_start); } -void set_xpm_region_info(struct vm_unmapped_area_info *info, - unsigned long flags) +unsigned long xpm_get_unmapped_area(unsigned long addr, unsigned long len, + unsigned long map_flags, unsigned long unmapped_flags) { + struct vm_unmapped_area_info info; struct mm_struct *mm = current->mm; - if (!info || !mm) { - xpm_log_error("vm_unmapped_area_info or mm_struct is NULL"); - return; + if ((map_flags & MAP_FIXED) && + (!xpm_is_region_outer(addr, addr + len , 0))) { + xpm_log_error("xpm region not allow mmap with MAP_FIXED"); + return -EFAULT; } - if (flags & MAP_XPM) { - info->flags |= VM_UNMAPPED_AREA_XPM; - info->low_limit = mm->xpm_region.addr_start; - info->high_limit = mm->xpm_region.addr_end; + if (map_flags & MAP_XPM) { + if (addr) { + xpm_log_error("xpm region not allow specify addr"); + return -EPERM; + } + + 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; + xpm_log_debug("set xpm region info success, flag(0x%lx), low_limit(0x%lx), high_limit(0x%lx)", - info->flags, info->low_limit, info->high_limit); + info.flags, info.low_limit, info.high_limit); + + return vm_unmapped_area(&info); } + + return XPM_SUCCESS; } -static int xpm_check_segment(bool is_exec, struct vm_area_struct *vma, +static int xpm_check_segment(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 *segment = info->code_segments; - - if (!is_exec) - return XPM_SUCCESS; + struct exec_segment_info *segments = info->code_segments; - if (!segment) { - xpm_log_error("elf executable segment is NULL"); + if (!segments) { + xpm_log_error("code segments is NULL"); return XPM_FAULT; } @@ -84,17 +87,17 @@ static int xpm_check_segment(bool is_exec, struct vm_area_struct *vma, 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(segment[i].file_offset, PAGE_SIZE); - seg_addr_end = PAGE_ALIGN(segment[i].file_offset + - segment[i].size); + 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 XPM_SUCCESS; } - xpm_log_error("vma not in executable segment"); - return XPM_FAULT; + xpm_log_error("executable vma is not code segments"); + return XPM_CHECK_FAILED; } int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) @@ -108,33 +111,26 @@ int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) return XPM_FAULT; } - /* if no need validate signature, default result is true */ + /* vma is non-executable or mmap in xpm region just return success */ if (!((vma->vm_flags & VM_XPM) || is_exec)) return XPM_SUCCESS; - /* - * When file map execute permission or xpm validate region, we should check - * the signature of the map region. - */ + /* 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("get executable file signature info failed," - "ret = 0x%x", ret); + xpm_log_error("get executable file signature info failed"); return XPM_FAULT; } if (info == NULL) { - xpm_log_error("executable file signature is invalid"); + xpm_log_error("invalid executable file signature"); ret = XPM_CHECK_FAILED; goto exit; } - ret = xpm_check_segment(is_exec, vma, info); - if (ret) { - xpm_log_error("xpm executable segment check failed"); - ret = XPM_CHECK_FAILED; - goto exit; - } + /* executable vma need check whether in code segment */ + if (is_exec) + ret = xpm_check_segment(vma, info); exit: put_exec_file_signature_info(info); return ret; @@ -157,7 +153,7 @@ int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) } if (is_anon && (prot & PROT_EXEC)) { - xpm_log_debug("anonymous mmap not suggest has exec permission"); + xpm_log_info("anonymous mmap not suggest has exec permission"); return XPM_CHECK_FAILED; } -- Gitee From ddef717c0ada74c3d16c5b4b17e895b268d9f120 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Sat, 22 Apr 2023 19:37:26 +0800 Subject: [PATCH 22/35] add xpm integrity checks in mprotect system call Change-Id: I21638ecaa6cb236288889aa44cef9f5cd1800f4f --- include/linux/xpm.h | 21 ++++++++++++--------- mm/memory.c | 24 +++++++++++++++--------- mm/mprotect.c | 8 ++++++++ security/xpm/core/xpm_integrity.c | 31 +++++++++++++------------------ security/xpm/core/xpm_report.c | 8 +++----- security/xpm/core/xpm_report.h | 8 ++++---- 6 files changed, 55 insertions(+), 45 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 35e4ec4a6e42..43c0f47a0df9 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -74,9 +74,12 @@ extern bool xpm_is_anonymous_vma(struct vm_area_struct *vma); * Check the confliction of a page's xpm flags, make sure a process will not map * any RO page into a writable vma or a WT page into a execuable/XPM memory region. */ -vm_fault_t xpm_integrity_check(struct vm_fault *vmf, struct page *page); -vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, struct page *page); -void xpm_integrity_update(struct vm_fault *vmf, struct page *page); +vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page); +vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page); +void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, + struct page *page); bool xpm_integrity_equal(struct page *page, struct page *kpage); #else @@ -124,20 +127,20 @@ static inline bool xpm_integrity_equal(struct page *page, return true; } -static inline vm_fault_t xpm_integrity_check(struct vm_fault *vmf, - struct page *page) +static inline vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, + unsigned int vflags, unsigned long addr, struct page *page) { return 0; } -static inline vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, - struct page *page) +static inline vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, + unsigned int vflags, unsigned long addr, struct page *page); { return 0; } -static inline void xpm_integrity_update(struct vm_fault *vmf, - struct page *page) { } +static inline void xpm_integrity_update(struct vm_area_struct *vma, + unsigned int vflags, struct page *page) { } #endif diff --git a/mm/memory.c b/mm/memory.c index 84941af7592d..3a6e0db5b320 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -2944,7 +2944,7 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf) set_pte_at_notify(mm, vmf->address, vmf->pte, entry); update_mmu_cache(vma, vmf->address, vmf->pte); - xpm_integrity_update(vmf, new_page); + xpm_integrity_update(vma, vmf->flags, new_page); if (old_page) { /* @@ -3041,7 +3041,8 @@ vm_fault_t finish_mkwrite_fault(struct vm_fault *vmf) return VM_FAULT_NOPAGE; } - if (unlikely(xpm_integrity_validate(vmf, vmf->page))) { + if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + vmf->page))) { pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_SIGSEGV; } @@ -3098,7 +3099,8 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf) } } else { - if (unlikely(xpm_integrity_validate(vmf, vmf->page))){ + if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + vmf->page))){ pte_unmap_unlock(vmf->pte, vmf->ptl); put_page(vmf->page); return VM_FAULT_SIGSEGV; @@ -3189,7 +3191,8 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf) */ unlock_page(page); - if (unlikely(xpm_integrity_validate(vmf, vmf->page))){ + if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + vmf->page))){ pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_SIGSEGV; } @@ -3510,7 +3513,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) */ - if (unlikely(xpm_integrity_validate(vmf, page))){ + if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + page))){ ret = VM_FAULT_SIGSEGV; goto out_nomap; } @@ -3625,7 +3629,8 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) if (do_uxpte_page_fault(vmf, &entry)) goto oom; else{ - if(xpm_integrity_check(vmf, pte_page(entry))) + if(xpm_integrity_check(vma, vmf->flags, vmf->address, + pte_page(entry))) return VM_FAULT_SIGSEGV; goto got_page; } @@ -3704,9 +3709,9 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) setpte: if (vma->vm_flags & VM_PURGEABLE) uxpte_set_present(vma, vmf->address); - + if(!pte_special(entry)){ - xpm_integrity_update(vmf, page); + xpm_integrity_update(vma, vmf->flags, page); } set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry); @@ -3967,7 +3972,8 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct page *page) } /* check the confliction of xpm integrity flags*/ - if (unlikely(xpm_integrity_validate(vmf, page))) + if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, + vmf->address, page))) return VM_FAULT_SIGSEGV; flush_icache_page(vma, page); diff --git a/mm/mprotect.c b/mm/mprotect.c index a5ba7333f163..847ee238ce00 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include "internal.h" @@ -138,6 +139,13 @@ static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd, !(vma->vm_flags & VM_SOFTDIRTY))) { ptent = pte_mkwrite(ptent); } + + /* if exec added, check xpm integrity before set pte */ + if((!pte_user_exec(oldpte) && pte_user_exec(ptent)) && + unlikely(xpm_integrity_validate(vma, 0, addr, + vm_normal_page(vma, addr, oldpte)))) + continue; + ptep_modify_prot_commit(vma, addr, pte, oldpte, ptent); pages++; } else if (is_swap_pte(oldpte)) { diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c index f316def2ee55..5ee502619969 100644 --- a/security/xpm/core/xpm_integrity.c +++ b/security/xpm/core/xpm_integrity.c @@ -40,39 +40,34 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) return false; } -vm_fault_t xpm_integrity_check(struct vm_fault *vmf, - struct page *page) +vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page) { - struct vm_area_struct *vma; - if (!page) return 0; - vma = vmf->vma; - /* Integrity violation: write a readonly page */ - if ((vmf->flags & FAULT_FLAG_WRITE) && + if ((vflags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && PageXPMReadonly(page)) { - report_integrity_violated(vmf, page); + report_integrity_violated(vma, page); return xpm_value(VM_FAULT_SIGSEGV); } /* Integrity violation: execute a writetained page */ if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { - report_integrity_violated(vmf, page); + report_integrity_violated(vma, page); return xpm_value(VM_FAULT_SIGSEGV); } return 0; } -void xpm_integrity_update(struct vm_fault *vmf, struct page *page) +void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, + struct page *page) { - struct vm_area_struct *vma = vmf->vma; - /* Set writetainted only if a real write occurred */ - if ((vmf->flags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && + if ((vflags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && !PageXPMWritetainted(page)) { SetPageXPMWritetainted(page); return; @@ -85,23 +80,23 @@ void xpm_integrity_update(struct vm_fault *vmf, struct page *page) return; } -vm_fault_t xpm_integrity_validate(struct vm_fault *vmf, - struct page *page) +vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page) { vm_fault_t ret; if (!page) return 0; - ret = xpm_integrity_check(vmf, page); + ret = xpm_integrity_check(vma, vflags, addr, page); if(!ret) - xpm_integrity_update(vmf, page); + xpm_integrity_update(vma, vflags, page); return ret; } /* * check the integrity of these two pages, return true if equal, - * otherwise false + * otherwise false */ bool xpm_integrity_equal(struct page *page, struct page *kpage) { diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 1208fd330eda..341f70fa8f3d 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -9,11 +9,11 @@ #include #include "xpm_report.h" -void report_integrity_violated(struct vm_fault *vmf, struct page *page) +void report_integrity_violated(struct vm_area_struct *vma, struct page *page) { INIT_RATELIMIT(); if(GET_RATELIMIT()) - report_code_tampered(vmf, page); + report_code_tampered(vma, page); } static char *get_file_name(struct file *file, char *path_buf, int len) @@ -160,10 +160,9 @@ static int inline get_xpm_flags(struct page *page) return (PageXPMReadonly(page) << 1 | PageXPMWritetainted(page)); } -void report_code_tampered(struct vm_fault *vmf, struct page *page) +void report_code_tampered(struct vm_area_struct *vma, struct page *page) { char *filename, *buf, *page_type; - struct vm_area_struct *vma; event_info *event; INIT_RATELIMIT(); @@ -183,7 +182,6 @@ void report_code_tampered(struct vm_fault *vmf, struct page *page) goto free_event; } - vma = vmf->vma; filename = vma_is_anonymous(vma) ? "Anon" : get_file_name(vma->vm_file, buf, MAX_CONTENT_LEN); diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h index 864c994c616b..32c38f284568 100644 --- a/security/xpm/core/xpm_report.h +++ b/security/xpm/core/xpm_report.h @@ -143,15 +143,15 @@ unsigned int xpm_report_security_info(const event_info *info); "}}" #ifdef CONFIG_XPM -void report_integrity_violated(struct vm_fault *vmf, struct page *page); +void report_integrity_violated(struct vm_area_struct *vma, struct page *page); void report_xpm_init_failed(int errs_code); void report_file_format_damaged(struct file *file, bool signature, char *log); void report_code_map_failed(int err_code, struct file *file, bool signature, int codetype, int prot, pgoff_t pgoff, size_t size); -void report_code_tampered(struct vm_fault *vmf, struct page *page); +void report_code_tampered(struct vm_area_struct *vma, struct page *page); #else -inline void report_integrity_violated(struct vm_fault *vmf, +inline void report_integrity_violated(struct vm_area_struct *vma, struct page *page) {} void report_xpm_init_failed(int errs_code) {} @@ -162,7 +162,7 @@ void report_file_format_damaged(struct file *file, bool signature, void report_code_map_failed(int err_code, struct file *file, bool signature, int codetype, int prot, pgoff_t pgoff, size_t size) {} -void report_code_tampered(struct vm_fault *vmf, struct page *page) {} +void report_code_tampered(struct vm_area_struct *vma, struct page *page) {} #endif -- Gitee From 8a3c87c67d5141e0888795d4119c6fa58102cb86 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Tue, 25 Apr 2023 16:54:50 +0800 Subject: [PATCH 23/35] add readme Change-Id: Ic6df02062bec4e3a36a6672c5f2d7396292068b1 --- security/xpm/OAT.xml | 74 +++++++++++++++++++++ security/xpm/README.OpenSource | 11 ++++ security/xpm/README_zh.md | 78 +++++++++++++++++++++++ security/xpm/apply_xpm.sh | 31 +++++++++ security/xpm/figures/abc_check.png | Bin 0 -> 56374 bytes security/xpm/figures/integrity_check.png | Bin 0 -> 49464 bytes security/xpm/figures/xpm_check.png | Bin 0 -> 93572 bytes 7 files changed, 194 insertions(+) create mode 100644 security/xpm/OAT.xml create mode 100644 security/xpm/README.OpenSource create mode 100644 security/xpm/README_zh.md create mode 100755 security/xpm/apply_xpm.sh create mode 100644 security/xpm/figures/abc_check.png create mode 100644 security/xpm/figures/integrity_check.png create mode 100644 security/xpm/figures/xpm_check.png diff --git a/security/xpm/OAT.xml b/security/xpm/OAT.xml new file mode 100644 index 000000000000..5764102e1703 --- /dev/null +++ b/security/xpm/OAT.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/security/xpm/README.OpenSource b/security/xpm/README.OpenSource new file mode 100644 index 000000000000..35cf3e9c6b8d --- /dev/null +++ b/security/xpm/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "linux-5.10", + "License": "GPL-2.0+", + "License File": "COPYING", + "Version Number": "5.10.93", + "Owner": "shituanhui@huawei.com", + "Upstream URL": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/log/?h=linux-5.10.y", + "Description": "linux kernel 5.10" + } +] diff --git a/security/xpm/README_zh.md b/security/xpm/README_zh.md new file mode 100644 index 000000000000..c1b63d6bee95 --- /dev/null +++ b/security/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对相应的应用做标签化处理(execmem_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/security/xpm/apply_xpm.sh b/security/xpm/apply_xpm.sh new file mode 100755 index 000000000000..4eb0c74e526d --- /dev/null +++ b/security/xpm/apply_xpm.sh @@ -0,0 +1,31 @@ +#!/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 . + + cd $KERNEL_BUILD_ROOT/include/linux/ + ln -s -f $(realpath --relative-to=$KERNEL_BUILD_ROOT/include/linux $XPM_SOURCE_ROOT/include)/xpm.h ./ + + 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/security/xpm/figures/abc_check.png b/security/xpm/figures/abc_check.png new file mode 100644 index 0000000000000000000000000000000000000000..01e2dff3871fb2a6dee309b049e9c0af1aa55fbf GIT binary patch literal 56374 zcmbTebzBu*wmwWrNQ2UONNJ?I+n~EaknT{ryQL*06p$`yqz{rJ-6cqeg!H?QerM*+ z%)NJh_x(dYA36K%SbObN&$Eb7ekO-`pX5Fq92};CytFDD9HKKE9Q-mWGWgB%hLJxU z92J~`w1m1R{BG90=>_S_vnm9;1l7pqWXXgSRyG*|506JZ2IzY8h5QegRSQN}A4EQ6 z+_%>wATS^R|IAH1?tU7yuXsIkIi~Mhq-9{(%-;E;@a!>hquk?j;Spi3^dx))NpVzE zcxo!7V1FE(-~S0nkuShBKH!=3v*dsM0zCDoDQY;hPCMXHqk9IOZ;zkm7qrcXX}3-4Hl{; zLV!54Y8oq+|8=ha@}cxIx4#`i{2@|#`|axwQba{q-BSTfPvzr6{Bh*r;c*)uh-<|E z*|9PU61RUG z?Dc*C*fw>fO8Q^2<;Jb-C_z3RZ#M?kq6zo$F;9^|<~EnI3>Ydxpg6e7+{1BCmf z@Ix*7?=y?j!{+Li>HcdjXaO}79=P!@I5-K+K~jEy6Sk}LXXKA0-Kp5CAz_D=^)MQa=*(QrbF&aPQmirL~ZDx_IVpgHc$PT#fuh-+K+pU=1uqsR^} za?}b;CW!eC;V-?&qcINBz+4?bIqH=11lRqVGWQxSKo|l<->0gQ@ke4*TK{s98bKPS z!Qz=z;JWGWmD&EKjBBt_LBEbAS`Kk2CV5F53|_0Dq6Wy~;w0ES5NKsU`aPRn5DoYFqpjd1ZZqIF}j@5bJmZ7?$PV-~4B;TFQUE$g`b?Z_KDiy}^nZM#zc*f2~ON z&lf4+(5aY>!$=D*GK59*h2kHvCjPUoCYXDvX~R~8SqVG-{?xx#%OQ-QTFmVAfe)p|m;2c^%6ipP|JNAiMA$m7b(vA>^VR)ra> zwnp)1BXDq&x;CumXLAk=icQaikEHo_N^6>IDq4BJIx?>~Pn(1__S0t$E;tXcruXPP z5_#ooVMoIkqk)EtU>6K-37w$cEkh?he5EVwgQWi1v>CR?cJYVzc=aotxp_8I<@<|X z2W3Lp#UHX~V@P;Pc10WyCd>3@M4~>10BOO^?b1n zW5`~X$D$LdZDoL-L+dFMMG!LPvB^qKb@P7Hjv(1uU)%vsPLJ291WE+7RWI{oVpi6k zu|liiG^%KGhvI>}=kqSj zKIaRc_C1?iw^aEO=KSt%Zs39>WDot7+3KfF%jM!I$gwQEu|-Zso4#4TD*bMIT6phc zQz8qJbC7=>4e5`ad8hchFU%DUuQs!$@N-SNLSH4G{921^PS!GPe7}~c#P*0rlfC`A zTF*s(`vVYg0k~N<4ext;;afvITKQ zN-#WUxdVk#BYRoIBDs;WrSH_fd2b_PI^8mROJRmS({>ew1cy5oOw!BAOimZDyU_f} z%wN?(U}9})TM1p$jZO!39U0z6t^>ZbTnOvTp?Qy&Z$e$I(dvDAuSTM$M%Vs~WsaK? zOI7fJ%cptg0qsr=Rx%oA=JJL-p}Xs!H%{X!au<#=>w+mpJi__edUOB!&@4oPE_83I z2~1*mCLL3ad>h4%pRH46$1@k0$p5kyi61+n*jd&_Z0PaU(Fm#aWL`p7*zRqG}+^%@O47ABMm1D!IgzawG`FAoduu-r+I3wu^F#7X$M zi`dWedkk{+dHMy1ouK1cNS7PZ(0*F-^@@P%kaf7wlq9#r1;@vUPT%V?D_gOx8Pase zZclnH+KBiucd4MFtZn_>J`<)SRRwBgO5`n85Q$3g@T^J#%DTcPCW5Ae-`SWFd!t?b zOz-(fQBl5P#8;+pLcFHe-?Ss3dkt%`hWSAUD!+2sa5j$z*bj1mw4b^4893*r*J8us;v-$(YL(way;)OB+Ba56-XSVl?9y&?^i zIjawuXpJC%ZFS|42nh`{(cMXRKt|532l74#3NzDsx&6_%jO)P2Z9*f~%_Y877wcLR z;;dT6l3Kv4D_3e4JlBBR0Ce-~RFIBEI^C|!9dM36U|u^5sgk%t@59WG&ux?EBy`TD zXy_l&?Hg7?eOWVlFcX4mB^-AtED4}4lo7;t)HbD0&7Mk)8jbTH;Ykv?9d`7t(r(A7 zr0{>5vlp7GFluj#jp18tV0kK!qNB%uwOiwLs;(YL6dgZFEfvZ?`4Z_HIb9*T==1H_ zZ6&VllGO(YWr5=HP~t6AJV6_|7%caw+eN02rK+;CKL&tV)fBtvMIs9;<1%y^;u+YV za~WP?MXvN$cx8sID)M_M@wqX`H=XM(3B8^*V&S38}og2U?AZ`==H5Xh&v4 z>rOcWYL0OexPn~?5c+}tOeIT64>8ZOP({3Ha0Dk-S*bG2b69GlgLKL9Jh9zvElf3> zCHMWcvdCUjGr-ntyCw2dZG<=(OO#6c8L7x?S95J`C8<116lC>EZmYX4OLbo=-wsy} z(jk;6cuc1}gr>tzbT2Wly}DE_W@<5aW6{h2%scj$!_Ml;z!mp1aCD1@>bPhD1kzNI z&>6S~l6|>iPaX#%-nqB?-keU6YclKTVc1*LjX9dA#=2QPaixB-CaPDRYijS?X?gi# zd_*VndNqpu^8AP8j4qY8abcq8R`VWl@B3(|jt=t3n@7kx@6$~0i=h$DyXs4q`$7oi zmN8s_*V!dXUZ02M3s;csG;z{24|ji~y_;bifdaKY3a?VBv3miMJdYb>F(4?75sHXR zDMGuIL4Li1td!%7es%Lw(0lJJA)tiIIkUc%)fO}*+Yz=-L_XyvF zlO@X0da9sU=f#VnGjG*Qq{i|nctnQ#V`jMSzqD_wNfRen^jwS4Ud5${?ESiH`8ga# z>-@2zt*L#a;9-M!E+aO5H`P~@($Q^kI!dupAuKWMyG^e^BW}sSk!WbuB&g1+Swfe1WCAt8rySQYg(})GV&}=i9&)Zhjv%8N z-1E1>p+AT2tfjG5dj>072L$pX#St|E;!UzGaNgh*v|@X}(R(sMcB#re@lH_;e>EN!^{n+Zi4=h7;A z3U1nXmPB6M#_bn&FRYe`6xO$QJhTy+z{xC*7jWTyj)Y7TuY@M8=sBSk4`lPDV=+zA zXD~uB8`1Fv#*G4fcVCA*)O3`icvavRDvh<3DuSLv443s^pWQmyhaCYD4d0I=hP3>$_ZHL#WC>`s0 zF*)ikFLo!ho;NV{h0(LV-h`Dsc2uEm4CnHO1)>!jcFP0H2h19HPFZ)jNy(?DE)8j< zhX*gs+*pXP3mBfP|7r+h<&7og>LnFw+$=di~GGYrsmi zmBWaSjD9UJLM*)|Ipt9)8Pa9-yH@CqK$xJ$Tm~7z0AW{eE*4%RRY1*XDfvB= z%BFq(X>2?Jd9n=RaRn&RQb665;hfS2fswb<^HnNOVL8^3$zhW$JU7?Fp-AWgh{Ims zY2h};z97|FDznqpt8z&jjertAZqJcf|8g|i-V4iHFh0F+dy!><7j&&?0>nJYSIKEp zc>el)f3NVRY+L;B5hoVD0I%-%uGoUeLv736zO2*x1fn*3+D3gMTsI>qsiz(UZ5J_u z3^MCfZ?t*M-igdTay?jFI4~iM<%)zRJR75Mt0bh@vFG?kj>TMjr)iX-YxVGz1qjc1 zka?upUKTzki>%9U=qTO46FmgS3nxa373>dqW4DoRd4|{YJw!nez>`p+e^j=d}CL2~|iIKS3sc4tAN3wsXBNRz;va@&)4~F|F{pKC^$wMY;MnrcQYAa2NeX}1V&^%p{k}h|zCSXfl_Ol_K)Lb1F zyZvQ0pI_g?4w*{n$@Y9NvyK2u~u zPp|#<-S%cD4hH%u-ueCNq9n^8m*3YG3!_u2C8r8FnQI}87K(-Lfi(=J5X!K|zp2cb zHwUH~zAoOq(T&L(-8AdIuK?Nk#qlOYwhshi1_Xv2c1^cgP`x?%wuy&A(zuPLsScWr z-w4H+3-F`+1czu*t`tH}w^H<)9Kx_Ymi&$*u6!@PFK98Br)}R^-nFZ|DHwBDp`x>u zPP9DO+_F8T>tHI%b&iHfKg4WLKImLQwSkHt)xbS{Nsp_cm{(RwTd!qIk-4$W=jwHJ zFNyUeL~>L)c{8I&*s+AQ7 zy0Du^fT6mvIqF$ZN;TZ3FSzmR41OG;|A@TFsIzBVNpj+O$2u7>cdqxLvkAy2M~Ii) z1zJSUO(@4W_A6S?GQ2=`w*X@Ctx>qG=u3K&_Uq#TUvf&DS^*;Su(k2KAJw1g6P^t) z+ZREcdsydKT<9UGbhBtZz^>UVz}DX^Z*P;PrE)30CA7*6c0(f2G{P(Ky-&XhtWcqv z_+%`1S{%h7dg@tE&;O)$jdaeq=8{%++3TbKx?@zOD?EZ^AJ-Xhmmj`A%EZKz$w;U>`@of`~p(#DEYhHi%^YP-Ok5i@tp#jZ@<~K9QC@j zS{}?1HVVo*yGz7^p~T#5`~E9X0WWm zFH~n|nX6ofn=-jg=%D4>5cshueFn=9&;87>8xEbiF!`k0C^1}$3sam?D7_su{5m|w z+UhS8l~2_;1mwh}*|HF--3vOrPmB#4YT4N)dB?0?^Z;K@5x%_iiY;>H|`!FvUBC9dV(Q30)i*3kwZ%Lr0%yonM3yVGZwz>lxNpf zW_LH|=k!Ghy)&)L0+BEC@**y5!WmWDih?!^uwAAmI2uAat#vW3x`s|t4-=T}XDrgWa2r{^K4WCt;jV!oB(D;4 zSwv_0UT!TEspOa7rRK+p&K^AM_Lb=93dh@A_GuPf_DA%d234N?Oi%!5DTonSR1Pse zfGAN0$G7_T#!(*dty(xh#wpYTn>^Qq;=KuU?xX|&GS+NRUI@yerwAzf=&^d32dCHzv``z6dE*yu`#5(>Qc3h8^cU>{XsV%KY%mb{)!CF9K{!h{*rPW8)iOwH&!$!u?dA_G{!i9Q zC*EJtNj#fFShYV+L`oNV>po=>+vCI9pgR+>Yq4V}jgK%WE=6gDy>;p(Zq=nrVPcJ4 zzKEB?a%z2jdR&q)XWv|A#6{>$(IRVi461J)QEW{`MjpQlC{`dOXq_tE;o=#2z4UbU zAKnw~zGZB_NJ)$8{@AfEnb2lF4zHJ2TNGOIUq7^JL=96BZ= zyag88`R0fHyof{$5-%Pz*%vs~hrpG@%a2tm_Xj+P;juc);Tl2P$@bcJYKao0OJU%Q ze%wdq;%UavEi|R?@@nkfQKV4iVB`6sm)v`1rZQMTEL>mm)SzP=h7|dDPHfLz8vGZz zso3Zxwl4u&qxMuD0XRll6G($Ws|I`1b!XtsX7}ikFPR>uWS-9L^)qO zZxyCC0Rh!ZPv>=tu0?YR!v~kn{1iEx?jjYbx_b+X$odYIv@JXcnv5!^<_iWnqCWEq z3|Z>;8GiX5X1IBr;xtx<3>j<9-|%O7`&|M9a==g7uc^UgR@R8vN!ZtaHDHK!@}dA{5<0~T=4xfBKFBS zN8nhEh|NegW$Vrms~B18;$ICrWdZCEErcQ)yZZra4QHpBPcjI)-UwjRBCk)rc{-J|h)iVG;yD~EAQt`q&5bRKV##o9>) z1s18W1{N`$oc|vlksitEXs`L8%^s+#eOiM$jWfXkO`Py5tD0$6bpj%D3PDO1wX^({ zwwl&f(ZqEB#b%CClH!M+oPT#8$UnaZy;>P-Yta55VlJ5bP$HB6!oh+NQQYG)`Y6}A|LoqxHOytO*(V)aAG|-E|G%4xvViu^Eal|NIy~8bwbK7N zrYw`De>1s}AoZ_)=s!kbPZRPt67n@W7yos`|4}Szn>$$?MdU$$B|N|N>6GU`FPJ5i zm^Q?>H19ygW-Dl#^j{-1QJJ8N9~~`@R!09_B2d4oNFKmu(A2Qxd&PR{`5*uX$_&!s ztvAsCF8<&A7<2hJ?vL5Jk5@no)Z*lDQ3G0F@b$BP7#jVb76irW#hb$}?Bc$B)L?PL z-%*=6KuJ5t+Ja?XP#T>5H?gFiQ(>%l9EHd{-;#IIYvD+7)b)q3q(re7&=~%=$l#&Y zHtNat!~pQ`TL0GtKkwFz?27_mXM9Zs35G7f5|YON^EDQI&B3leOGxBw20DRJrYmvkl9V9(0ZV6TzdB{Q1}(rjLX56vP`OWQ8D?K^|f z9`)wthKRSpX7jjUbo&j|1K*}5@|V+BCu)>fj`O?DCl&Mcwjaw6Mhz&14JL8w`OUg0 zq6GWD7r*>m#pX9PiHWaEn<+$W-z~=^nAafv5)Jz1tixBa!bmdcNZgm^8g(A0rHm*)90Y# zw5H9*v9PervjNF3Dbh)96y|`k~Dc=P^BcFYMrLmIR z+EThRWVt40f?#{T{r0rtFn{chXHvThl%8r&me{B~zdaXg_1dlK+tr-805ugiP%F?i zK*1ZV)Uzr~7JjyJ^<3oi&Cc8>hhOg0cTv!X)!K7>YMJ&;#mzs5h2{w1zPIcF!P#6f zQNaNY%S#9X$YN@Qz&Q=h_ZMvF?VXU~I8dWFl3e)~T$6+@f8^g(0qDC))aUZey}Qeu zie?aQO~M?AJwPE@ye#~v{0f>ueqs;sNM`a>edh>05PP)Na(d=~YgwFa_XR0x6Aaa2 z1+gv#w4`5Lb zy!yRIF#bppJp4Esp}u3sGyDdQDw;vsPnwtK?RR^~*c7`iCnw*Y|Ax!o2+=y;T%Nix zWP9dG$>1h>OzK!BXS)Qr9j04k^e=#fZ$FZ`+_9qV(t3+Xz`0;&;$y1U^-?{p^oL~fUk?+aB%hoIi$BBp)qZy+ zw)8FwlI6gQIu8B( zBGq8yeTH>Ox5C>5!A|;E+WDp1Gd{k}w=S2t`)ybDmSg45#csxv_-U=<8RtPMVeR$F zkCj-Veg5;MyW6R!Z~4F8$(5V~Eokf^G@2)O>c(9>xH@P*fK@^g%Mc18gc;^@(eWC6 z&Sv`BDS4jJ%rCZjU3mr_xD5FeM8IeSVp#mkaB*TI((9csx7u-_@CYD)P1iz913J93 z&-e<32cZ*?^<-qh+lCf?Y(WF08aX8G=N7@tdt6R#lkWr@$5oXsjLU&IpNDfm5gNOG zd;v@aRf3*Ohqf)c7(^5-SHL+*Xa*OnI3jkjj{j+hckb5&sjE1{%KVEowg zJW6LMwLQoX>F)HABFI)Gz5sfMWmv#N7r<7@$}|Zjeyeb_E4m4aK8*q!XA)C{WZCNTd2)>;E|Vb zBD&&HHJM3?r*JGS$+lsPhxgbBb^TJaJwi&kb7sEL;zpzjiGxhPW9*% zi@1|4Ih+QP3l0~H(ARWOK@w(ra(s9WuF$L1Wn4TcC0p60VY}p`FhSZkOwl!x8#k9C zSq{Y05x?7S_8qSj^Pmp3>0dn&O3Ju zr)sV%9-Ty``4YWqF{aK+{sHkfj*CIRV+3 z{Z6}&&^_J|m2BtdsJ$ee%U<1-dqvA}zGwIm#j&RpXa!JRQ2UyJ-cRNu_C3ITL zu3XK5Jo(mH+NvWF0~W4=h|FAe4lPHa*nz|)%ddr{uUrRzxa70PzS*5J@?(|hd-7le z5Q2;=EMxD#*9piA$^dnog?ohh%T(6#C%eR(%NAL{>_=%m^#q3fEjbH!+u5v*(%qh~ z@&Vb{86d5^K1CiIs>2nqsQOF-=_}Pegbwr;(owms3@CDdpx}?Zc0n%W7NZt#c+Y?= zp$j{yUHe5T{T>abLIa?KELNCF@!&op+@E(DaRURJ+e3*su{x&vfrH!xtFeb&(qQep z0S+crjKe-qg5YX|*p3!TJs@oBRm2G4GwDQX7Z5a$L)(Qt*N{&H{>p|Rf)h3L)jsRc z73_5aRVU9a-+1WEP~JW#3&fJH)WsfW8U%Pd+hkSi-KB%~nM;>@j$C<9bz>pJ!-55x zX-7SrYWJ-2zvY{Cm%NWS-O5cUcvte$X6o0-B$}&gd>f32FanV{qw< zci`jzmz7EcNtvtE{7 zWAr0n+eH0_XkF5{A?Z>O!nbciajMm6es~g|e#r&EGe$Xwy(A54-*m*yjzy*&Z@ZP} z;ld$L(zocVzrGsh!$Ix{x;%6?TL?w+P`Kk8>f-n*Pcb_{gIacPO?fj-hK z*vRrQ%+VU_TV1KlK&JPn_A7D=CEq4YLn>ci4JWhNqYAUgNaGlFk$Z?(&5$aF#Dps# z8atGjr<$6#Om8-)M7#l#IQ(2i7;W|{Y zS^O1RQPoRgD3jnImMB*GB$iTaF;0r8JvY5eJJ=3|>h()vYEE<^`dI4BjY_fzf_Q;T z?@n}UxTC>vGACoAdw6^ngG63KIj=&cjbdip2G z`@3afOwx3Oa_jPZS&ya1$W$dtXeDzKtL^ zu5Bu=(kI@PzJU_r^SJu$9bRN6eX_MGDgB+dzv5~=VF5Jlq(>NRXoF;1> z$$W|GP7UG>D6nlko&0Hsh{icZCLfq!|I>s5Q0nf|RL`}WKg*@f=+LeftyI2ZaT~+R zETezs;v|hVL1q#7QJ~Yp|M2?fh*t#$Rhn_QkhU7r@0>-R1isePb`VFD%PBGilSFXE za^>!%untdvKApK(yEpx!g6ANLr8@kMVwp0C`s8XOt-(xE@QVbMjwarGqJLQ=$B*6& ze)`2c`iWnmxuN~i5PaXaHUcb|1x~-~z>L$=-Pum|!B7}L?eLlcJzmBkYs;}#V7W|YJ4WJ=s` zt^}us&xPNH!;f4KatAAvo##brrh~zl0%)YCvf&Ih^72D@SmDfDk*|AOpYqOhTTXW;hlXBzJbq8T z%$>ZIAulz*vDiJKpyE7;ZnQ&Jviz;XmN>=>p7TVVDb%BKkmAUnmgiN&;Tum_V=MPs z57W6PcGCFUKn&4Jr%)UIi`TeB%zN}4a=IpJcAxBO_~>b|D-Qj^y_;#@T5q||S4H2& zGI0G~ z)}#EJ%_k@!uCd;^LeD)hBJ;7Llj5IuhQG#Y>?Th5O=0}<7FXbIR6r~~>_j4%`lCcL z#?O2gQJmbCE_)DJp5YV8FQo~{M23=7E4UR)jjM^4Z*Xi4zD6M<&`8K2PB0t&95zpO zmoQnypB;fZ>6cN0Nzlu&+m-hUt2|xgy(l%gIdY~)6 zeMx|D663UnGS?Sf%V{EGM7lY(pYIDb*W>9sgFAcn>q*A8+sTmkPn}qV029@>4w8k4 zkPs=i#47n#lr;huZ<=ipPY)2;YxKQ(f&r=eIKfFESv?(DzDC&o(B&QXCvJp6eaaNz;czlMQNdG= z1Vn5CmY*GHigrvP#g;=7{;$bhy&2QzbBC3&i|~t{&i2;GkEOZ#OG|XecT*p|7z?gu_<_vitVA%29>coPCP_&OIlhjDIUJL}q$J%$xqHa|{jK89mNR;WryC zI=4Lz&qyBFWYT=5Vx509<0mMNoHuwo*V@2^cMl`n7`~W5tJtM3z>IZeIZT4d8l_0% z@kcJ_b@}#@`y*6h^@d?}vN&|E!|7<~LgN#>)5bw#b{0?~e-u_x&Sk6vIknf+jvfAF zR&?80Eb6k`gquX4_4JACOx_RkPS$$J3r$a%;e^I%kLNCSjtjg(V$5em0c&*vmaV!j ziui)*4klET@-e+sQ6?x>)$mnI@sFdclQi}b5X&!d_##}Gjs@1?kF!rYs5)qS$3}9j zKqPp9`%0q{*R_j1Gs_&E6&W6ck3q@kCjs6~Co#c3p8viQadOzEvSf-wT3s)8p`*<;@N~_2;H6V@rY-Sv+Gb2%a~A z+fe7ME}d+ASrJErT*kH&k@088U}7VQ?+G28v7SG2orpvwAYl(6w!6WbMXch;L3sBG zkffBI4|IlwnfK4ZL#z8qZ+@d z18p4rk2GcLT5Li{MeQnBkgx!}a3WgDo%|xs$NZlw0R_8s%_x7cbkQ;B)EdUk6pJ6@ zgzvlqtp1A#1dPnw`T@A^Gi=jU6KFhj_Mna~a7`0%Hk{f&(Y~5ueyAh8&dVUKBZzhX zOpCnr0#{^5rVC}uOeAz6&e|-6A`4K0aS&ptMp4rx2SOqy8aTri`mlZ1^y%=3&tvxq zNdtbc`&Rudo&SyjZ*g3PkDkrhi^KccpZi^gl@SX|8|bjmaEUmfW@8x}Cq|huS&qzC z!zu}UM-mZI#2&iY!wPXrog}l8coZe_U7sj9>iu(gu&S{eo7mmWV+Ng|9 zmAPr4_sp%5#9fC?IK_6V^Y5x%}Ul`bcUj>R^DSo#p@;A6-7dSSeyIeL(z`d z^$NCVS%;-IiF8K5EGfEIg1xb(sQnEYo>;w|@kbbb4j{6c2G7)+XXH zEy1Jcg_6%)5KM*wyP9Fw2zv{aBlM3L`>;8!t&H54TB_Vsr>O|TxvtJib9}r%W%h5Gp<5uN4r#t z{=yj^p`R*nk7v-D?mz-HLH=i3xPEb(D>bk=NjOF3b$A1b6J!Npbw4{e<;HoIMwboW zzTB3+STm&2WLB_DgPMHJw8H`&9L>{R$0#ahUIBo<+iibwIuPATBqXlA_Zi1vTE++e z5Wg@P*EjX<2_v3X^8qW#O;6nK3ZWuuH*TZ#dt+)V?$w6an5%dZ(g(71VhRw#hrS<| z(8~=<_<&DQZaw(ow}z^GkWZ#+T~Jp<30VkfSxG&$jdu$dLK!o+c)2IWl%!os#$&B+ zbp@~P!ru04wfZG+KSOH2@`q@V?3T9CxIW8AM@0Rij+?JZHw49hRajJ%5dpUoz1B-! zAE#e;QuSSlHBsO!uSP^LX> za}hsQ&iUjccl5DU2D8e1_^Z*id8a0$WG+1tSUUkj-KFUHWAT_B^~N|7i_w>g98bpi z@)zx->mC!81I?+y$rTMFS2aQE3 zCl@QZ{I#|VVM||Kv1(`p$aPAML86$tnd8@HwA1TOv?0BYfd(^wFX<6>gNS+TmnpWt z*Xg=j;9=GsHB|13DsiVZl-Cvnb5E>I?<9$4aws59l^T>MLruc`>4={!f9=aJgYS4D zOi(KsogfG`a8%XyikehYow#woUe)rG6Ac#6Lsft5-pQiEV@V$A_FBafISs=&C>D#6 z)r_IT)0GveTH61T(jC?|WrW}FqYj~V;2$UMiu&1AlDtb8c0{Tl59Pf2Qd&-4>IAD; zUdv@+H6O~}w6Y}DGt(QqSOgQ1ato|G8?fDydvY4w{bDtDiWBt)1p>u{xc2oLu*sJm{O^)xbC&1$tr7 zBv=08J2eU}iznmxVxgwbBa63%T!!r|um*<)F%SIXH`sj5>&Fb}z~8)gLR* zjcYuYygUu@9tzgsv6RO0d!ifCRdgz5Wi>7XNZS`?MJ(Rp^u%4prtQIq-GdT)@u0MGlH@TZXw^J9WSG{^s5g+f8zp#h=0H>s;BlW z!=(~1KB%?MqD(m^8l-Qe_T-N#VvxpVM^&byVOw$B6)VAxpBU!1xS1AjOf+-g0|CK3OOF%WY#^_^lI$+XF_`q8jpAhpq-f=dY(kk2))4(i?z~CRH5;Vnd3nB zu;477kHKpFRkCa?L-0)59c~0IBN>9nfr9ik?a8G@nvu^(e%FCcsXliz!eU2Ebr#|| zb!QO1h=o7Z_jEc}Km;NnPLR6E7Hvtiro*?z5paoiif7anO)oOTe(pkK{OaVZ+>Ozs zWFM};9;#OEeH{qL8ca88#cmcJ0+yqqHWu5)pGt;(lS6AG*+&<#`YycXxd`0kV z5-T+uK$BhatrW*l6(+nY?^$paq7?r7Sj$`fDCoVf&?O$X=g^@?%`KoOc#zd#esb3~-|LbtYR{-56pL0L3 z7sNbuE`TqTBR2is0-#2xQ|tY-&QFMJ9KOmbvR;z=9OXNGU5LgNzowYdaiTmD@ha1R zF;(K1p5H^$p;K#3UVW_LZ|x)_BG=SQLet6MI|)heDtRzRXKaj6to3F*PWV`O*P7b7 zUV$r=1w@=q#jN_W9zLMy@h4aCt_fl>7YZQxV?nGyK*wpmr*fzbimW~igCfup{WhH3 z{rTQ4iREfNElR%8=^}@oFTfjLNOswtmZ%2zpB#*Wv07T)TeY zsfxR?M!cI`3%+nTde`H5Gkn)ZZ))+Zl)u{YZE=hrNqMrU4EUWjmYK&?s}iXUFp%`y zJxIs|l@cq-DSG!kTc^rJ0ox<~Qe7+Gz37cGfF#=Z-DHS!hYwl-wasg*Y5R4$Lbvn? z;_pfE(Ir`seKPy&;Tp>~k)v*WUoJ%@dfBHOK6;Oyc*)M!?cbTM%n^U|mY;iq*IPU=1WN_)#bo8qLSGB8NTq+2UVkRIMf)6!I^4Yl^fBH}=!TO;#VatdMMB?Q z;id>`Abi$rThoUB6DHQf3wS^5njUdP-?9 zz(oFb=&6^_44AA;S zizlJ~AVF#YzuFR|X&*30ukAp%K zSLMg-KpL|MjR?`Pig|ls!V(zyW54-ezfX@SO$tAfjzn8N)+O2}P_sDC;r zw|#!vQxTisZ65s@w4WQ`A5~+;4adazQ#&fLH5R09Ydl7`J-9d0ogUm3iGTUMj%Jt<_TXl0b=LJD|BwKb0aIlkuNT?fQz5YV3 z;lN)H!JLnwt!fAzv=e9h{+9^;(3?=P0R1?H!#z-0P`Ql&GE2 z#J1=9CI$yc)nAoy0F7*g$GNma>W*UVQISs|sUEZAkS|BP0rUX$TCGoYawheb*Q|fjfRW40TlUd zR|f4ee7aa;hRK(_mHaO*7f-W?jM1DJ6xIV@foA>Z!cKbLM?FeRiDzU8;XF!0@$(XJ z0%R3fM0@AbIKF3i z37hQ^`+g4=FGKwiWyGv{JjIO!iqLz++x}T}j=>VzOW=<+FkMK<`~=O<=b4FS)`WeA z&bGZV&45lp#uNqmlI!m%Z?`ZKez)38qOb_o^Ed~`-3o0JYv*)3(+eJ|T=f08kF28n z$vnxqMwj?mez-nSg7*z!!hqH*MxNi9Fe8#JWn+lnXO;R z|H)9YPY4IUQ+}Yz-{Z4C{G#`P~IpKvQ=xH$e{%-s?0+gh~!|wI= zE!5oGus2)LT*HTx$tl{VXcZZ7@T;G5QO+w{f+?@*tFX#ddtraRq6r}2gcr9!n2XHj zSUb-Of`%G`u$Z62I(PxyrGl^au{jjy6TslOT`ONP(2nq4(NoZR?dG7En436FS4|*n zy;vZ%PSq~#zI>qf5NCKo%V_UlW+N+3PB6G@{LUM+R;I=|D>MBL z#QvsQ6On)+TPELcq(tT49rLul#$CRy@|MU|&%UwHC@GX_6L>vJZ}p~)R&KJZX}ih} z@@>HT@Faou%}tA86=2 zfhZ&VUR49IbUxls5FUrMmSIx$EA3GEzw%Z`0vgv0IDu2;bQRu!;54cT$X|7;R^R!` z09?;XT38B?6#u{kUhyyT*`Dt4mrtN5PzIy-GAC>Leq(~9E>)|1ArXg@f_2YIAv$b7 zq*lg&5w=ufzl4CXJutS%^!H4Sed+aTN!=s!z|Vk(vJSd1QWW$frBT;$0X0H6C31`w zM$2IYO)KSO*f%!6n>QW5=?Taz13>vSh1n%Tm`(|`AQr5=jl=whM)m>5+Tsqr|5<L6GhSk&uusDG5bM0SN(-lu$yt8zfaa zRHQ)~q~Rh2B^3muq(c;te&@u!_wD_^|MzpeU!UVS_@Z!KYn|t;S+i!%{N^`l@e~Z# z2>^4jox#$0s@q?Y0$1wK;bi$C1|L*z?L7dfncMO~2e!p8=--w;zse~|jWIENEw1Ff z)-1?EFe-JjmjlhP*7-r>(avr4n=kKT`#Qs{*Dl-%uTO^Js@qI)x`l0gz4WPu_7jj?YBd0z;g59baR2O@f`gp6$4yYd_qE6vcAq%r5$Pg|w03jE>gz3e9yMpuhpbrC2s*0i z?Aed6vL9_su6v&x++nr4!7Q8c3BD&-hkhOb>B(k@!WW16oDus0!0Oj(0SKKf#GH0BE9EObt-yJlf2s zlFdNDW02N3CkR6%C(I8P^@d7W%^oNn&vBT940<0_dhs&~gQHP*$7_$Jnf!eG-z$0w z+&KfYycCT=hnG^$c*X1Jd%BSU1W-x*luF1(muDbX9?O>-9;}Dhww(o0hObE~fj}R6k<{E0ThXo#h zhw7g6gp2tE6q#u~ywpE$>~a;opcdKL)A zR${w+al3tsjq&s+)L?w%%nDXM-pq=pS39XrH3E2XfogXIq-OUz?}Z}(ON;A(+=Pme zXL#n0Wva_(B1P(0m5*e*NDkj3<$=QP7k`2g8V$hQF0(q4Q;iw>~p_H&0Fmm7%J6uCe#kLZj|_lt{Glq)y(Vs zBFep#N6qJ!z5upG@1b;wMXJk4nFmq^HXV4h4#8f@6o7OLnpFeQRW*XEzauyBk#Y_k z$gp_no%MbPYdbxDRBAVsi&rm$|auV0FXK{!bZFF<2)| zhCb1saA?I#tIig$>Mp!gq#|Q%exGzB9EQbrI?_g4L0CY2*c8SfUT=( zZtcR-@Bdj_^|=FSZ2rqhvWB<1$MSOkLw!h?KBRbi`ptS%pJ)p+#(qTrUn{PFYpCW$ zFMqG~>REh^*Og=UxvIcheTdZ>@)@BQ`bZ7sIaMzHm;?`jWXX*=k3!fkMNW(OlwN3w zL7qqTncq@K9YP&)O`fM)0|CMW&-VddwTVH?24GO@(mtEiW9|8cKT8^5AS z|L{3oL@s@eJ|IUv|BD@{#V2gf7ghSg1`5So5Nx>EVunBUlLCZpiUv@oEZ#pY;|>rI zX(nS09M5huLGH7_9I>GM1!$RO=mQYk%di>L^-9_daWcdtrfpu~?AI-fsHJN#ZjH{iJ|n^ERLjdJYwp zm;9z(fZ|3)(?w(YeAP*5?UMx=JFXXM)esT5X&-KMhufRc5&3z7s<5Rgi7F`vs*DYQ zXdc-iw?WT?oo&rx6)$Vgho2vG?rru;!P48TQml;POC36BxW_Ca0C974)ZwsW5TPmV zpDPJ5c_K@rNWG}K^sGfoTdzIUp#|IMPv_~t z3p-v2k5a&$W#!I$tHKOjIlI6K@~YXG){S_B?=^ex(Z^?azDs0$cS zQD#G>o$HafMMVB68M~|j|FC&{vDGi>GVHM>SNHNC5|Ci8JJOGcm=+~oZ(xMZ+)*Gl1V<(0#^vpZtDp_h<4Op5oK4exbalWa9> zxFxaqlLg+e|ByCsUb7c3b&e9bFrSs~rk=#;>T@Be>8|!Yi@7goOU`N{p`VOuUt6{ zCB;AR?_zJaisZ*8OyWtke#9hW_1|T9WWs0}r092CZDbNi`WY&wm!=dMgMVzt4EpNpiElHB@2v9berbRU#+Kx^?r z<8VUGa#{aIRBrPL*nfIeXnkwpGWt&;X+kk&KBb6L;-#V!5f|6nXwEu@ctIf+ljB?)1lsN(WN@|Y8a+ByHP!BGB#BXj< zTDEbyy4K(_?xAgN>5?npf4O1&hHR3h!O||@y2=68B-WGf`B2Y!DH1P`ufwaH7*(LptK;EAO1dyd11llp6}1??85ms~du zpTZrcXWaxn44CE&2mr9>32BxnK@_#>j}IG;o=_&bFOuf=l_FUFQnJ^+&E?B}yIT)a z7xtf^YoEHA|Kk8rz}!vs+~^bAC{PlCV(glM?~tl~heVchPmod|4;WDNu=5L26MJ~{ zY9Y~r*|^RA$(0tIplB({IYMP-gBbSl&UY=Zwy9YiE8cecn8J~{qj~L?RyJ6V)Cmfvb$_){&hz%CsZPId;GIfu z27_BypkQyQEF0(uyc!%gj}Z@)ZOw1D`l}}pK1796XpG*GIw(lT4!r9r_8{xvcslsz(Mj*o zZc+$?h)r1+E4~?*(t6B#yK>)alM(hxX?hOf9ezd%gxgCKgwx! zo0;^O5bn9S@b-?o)$U=%XziO5w$x+BKuZ@EF8&Nx{Uly}!>(52N+m0lSkXQ82wI6T zI?udrR{&S8~vRAB2bL5qkNAdOtoZZ?nkT&F9 zJ8|pCdTLc(`{dTdO_BCB?}8%dhZ!$+dsv*^I!Sw?3+})%vsdqo6W*4}(F&pU{$h<* zQ)YRbJiS}4ntt8mm*)2 z#f!gay4vL04H@3K6K}ZJ=az8yil_Ycl)&sxTvJ(i7S72ydiHhfyaKChlsfrbjK=LO z#>xqW!wG%(LqEK%iE519N=eVHgi}r6;G5kN=o87lvwQGCk#6{EJpJuc)U}wd$|T$p zuJy+*mfAuIS8?O<*i`VM%-l!a#}Kh}04CdI2jh^iM>AL!gwYoNTEu^M>~k=W3eVG) z7gz2Yh)f_5@j9uyf3U2wEqmt|2ZwjZ{pOh@Sl}9kQ(NXwhxxLibZR zijvs_FXys9U0Tc zx>t{8*AYf3{Cq21p_#*5bq*0snGN&ZL_P5O zye1|e(V(w*JzouaeHbjFG#B&GB-O_EPqIy%5xqCG;w4c)!wg}9=7k&b>j>A(O;x@? zwyc7x5YdmX9zr=|!80jdX%=kN77@Xf#$i&Tk{RNXobHijl}U`s!n8D)6HUHnkI-p=>w!jbE~fq) z_hb6Z)?}O~t;Qm5wSCr}%IBDR(nC^0494+i;`Z-xrHs&g)KmP(?MN8$2&uEgdGEjb zq}uC?FQzEr6|%fts70Ru*Aib~=oQp;nt4+7>k(=kA{Ba5Z-jU*KJJAwLPI*tyO;W= zd@*SkyD|f*7W&uA$>~&Az03Q5x3y0wOE^-dSX|uNN&OVO1B1?vp##hE?)_Ms5*lu? z-o5Nn=|-x0I+w^Xu^g^ty$TPLQ0nmu1#ky5O85?87#Hv3@9AOz*78vsXqZd5*VbCT zr9lPe@i@`j^rREF!b{52W)N&be&XPQvf|?s@QxLGRidr)1Ube-tEnMMWC!-8xXV5n zRr&{=i^@n-tUa>}a+fokXhRNiK}`Xn)cGscbJvTnKqyqSty9_@;-lXL`eQ_x?VxnW z^r_UymJBXmrYEFz5+(8j#B_bSlKIT;d%e_jP4%?+&4er8Z+hXdr8Zaa`1I^K97 zo%oRri;2a$pfqS4=&~f-1g#+oIIE+=tmkQJaG8)KYVIEX;!By1-!BrsHdO7AiBF0Xkvq~RDZU*z- zC7#^*0)F?8Da<#QlT&8v{y5WXoyzt1z2{#JM}e}8h@`3-l+JYsl8}V}KlQ~0shr2E zTr^6buE${)#o}~G0R+2h(tX8k>4JQ3!PT-NG&43JOs}k)jS^!VRH$z*sg{J83bplT zB&K3dM6#`v<747oafnG$lGkoO=2h_9wP8!N$#&Qod$ha5xWM4#c(?Hpy~$2NYTxe> z1h(xD+9HKwC2$tn0%g?Wkz^W&rZdRnQQwasN6*`?hWzxk>2lh20NObrEyBAcr3g>3^!L-mE5SSRVto9$5zwp~8eZ+$=e4cQ4nU%2%x5r)HJ60piIYbGT)J za}h@~oHURmYaDkOH9TVc2`6o$Eqzq?{I^cJjSGX>+SsA3C0)P;Me0@(B9-!@`otJb z=s|yZJs8M;91g3%W;?Fj8>f+qJO4x~;C}R*iPj_bdsR)(RiF{#PUcc9ARo2)aVpXl zo8H96oY(8n)jLBrl#|(d5}{14N(Z0Wi^Oq!kt(YaYC}9^MS!Pzf7@j)d!Y1-AC9|A z&nxk(6K6??u2c->jCp8J>o)%Xh*LpSe~Wh6U#a1KWI3X9XSg$}1RdHcptj5XEDPDC zoT#H&9)xW8O$i(I*NS&^3y^e~LIUNJ_bG0lD`5i%Xh?A7+Y5-Dy*^$wY2tK6b;9;NhVl8=hKr>AitVm8Acf9rl?WiWOB@y|{v zQ6Y3;Z(V8s&+QcA&2&LG2EgCd=P_d|9Dh4jVgJVKe~%yj<11#yCR(I^${~##tmtTj zN%ztF!sotfs^4%;wq3T4KeM5>h#|9W3jb+_A+f#v) zjB8CrJ$_>hy8;e&jMS~$sX17q%j9{qYAvt+j-tY=x#Fqbr%OIh%C&-3&Up60w-UiW zG>hThH7u|Hu@!HY`Nr3i?3pII+t~6%{8+TaG9Com-Er!oU2!5pIjP94E8=-_;};4> z%xO8#@&TDzYpp_b9Ny4zAqH}9G6*MyjgiF{&+W_Qb5Y*XpuENA8|a`$*3aC{+;8%~ z1Qf{RGxVu#THXo$F?{%qT1!a?y;EflVXIR`_1{CccZOsF zwzLkU2Sr;hV;pY>g+60?am2@{xl*@^hZJz@h{yN^?t0Zh{EWtWit_w{h2ovSn*3pmY9RQ|0Ike&D|H?cx9)Z=X(SEkV24}otD z$AFiwboCAMEZ=i94l9Q_6h_4^e_)RIC(s5{URtJ?-FxksVMSxYbk?I`(CBX8a@d&8 z+FHJ0X<2QS%yy293$ZR1Bx6*wRJW)Vka%LTgM1(Vi|^a&u5M4vDFD{J%bW z9t|oE{Sa%PO}mta%u1gG0}5v9{=Dw+_~&%6T8QC|UmbV;?wkd4BB|F3cWln1e+~4K zFqzlHZ!})UOyLyA_MY=}n4BUAnRRyh5d{C$PY8Kr1N8SX({&NWLLlxQC&Om}Gfpxd>>@#fnGy%=mm?0!>A6WxA`K6wiQnc5hlJ(|^BAPfwj}Gi z&$bAvVvWvVy@21e1~^=dGXC`|;fT=glI@=N-NFXxt8crHb|niEg)G)oYO(jis4@o`JdV?Uj zsy**LmmyY?RjKWvU3<@HH||Ab~vAfW3PU?i7jmVQLMzEecJ zOPpuz-_7J}|7-E}5c+w+nJ^9bKnuN3{GZBa286Htt*-FByGU1}#Cu#k@Vj$?K6yQv zZ+}&~{?gLlGs@DLge5LTgrfhauWR3rG{_9odU{G=gsdSkq5|@K9I-GLpiSaXPp>wJ zzqnyV{Ldx`L!N-IN2}&vzxuVYGMeDKq}2`yg18t%e`4tqcFO(}ZA$*<3U-bW0 ze`!h(Qm*gomh-0q^{td^!upKOmh#09GeKR04mln@4^AqH)9WF4@50eUw~ z1H2UL&>N`uSpgs?+YAA_S9%Qh7V$?Ai0zcD!i26mj}Y#lqe$^71P2C%pRxUZ!VKu% z`-ACzRgA?DmAthJI4(uW!FP9|_}W2+zq10%u&kQvGO}dSb)bEt>w?mI7tjP59RN;% zc+or~y9kI8kZp9kFq^9suHBw}CvuYm_mXue(?wS$lU-Qi`Z)FaQ8>v5j8v7ljD&=@EF3*XXN2^R#&k(7s%J6NlL@w)7wUcYrGbwb1;$4ZHO9 z9Jpt)(HbmL;{GYNwM#FC>uV79YycB53`{ki9N9nWxmZ=rXV!7qnS4Fgx9YJw__ftXNrts|`Vqu3zZ2us=`7(=w4LAyfBw?o308I;n(=VcK;ol{ zO%K8iZhE}PjdKEFkwF^3R3RKx2)q7j%@Knwf{!CWn%g){&jJmrBUBSNc;n}tlbV8L zEH}G%6r&>$()b)eF6G*B8M7k-uhbAT_jfLyoP`!s7jx@qE>W^LFgma4-j4rOrGns? za)S$H$gZ_0`w~vw03EOGqonh`B$?K`&vFibk%Ykk$U{L*8b){z2OWsY zxP%8)1rlO;Lll58v>u%cOBUfOk+i&?hM$)A3XN20}Y4BIwADam?%*}v>><{xd&cMuBHW4Mx#39EB=!6q~M$Elt z5p4V?6>F8_`zxU&Y{01Q;e9YZ)!lM9p9FP9f=cg!^SQouA%;Ot{3(Al!WW^_$=sMj zqNQ?6&c6-MpADh^I)Kd&mHZv3E>tYj1gz)A&SxhLZ`%M4_ET^J=G?kQO%Kl1Y9Hw1y)x=Iz%P!8k6Ek@MUF2*`4b4wEGft;hoQD3IeBx!?7Q zeW-S2K0ZqD-VOX&mKY^wxoejioGD_(=HqWtW9T^)c*;c*FOjw3a3vrBK)G5{N!>e- zCR|4M4yC_lQ3n~&m(1+}am_ZdD2jPcXt=W^jCC6RFR>1mXcrxKrehm4b3Xga36dT~ zSj%#)l)eNZ7V~L%IH`JspY$7gv9I)@G0X+FvN@bJ8TVP36CNpH zGFz>FER~$xq^yC+NDoQ9Lp5kCFlCsA>*vr|v_qc^B7#JbW?Bxe`G8!tZYG<{tSK+D z4*PLnXL6}TYD%7-=Su(+OyYTa&JgQ7*%<*5zkTC)oF@WlCqVq>0x|JgHeP^_DL&=2 zcz;R$&z0nYKpat}Pq4ohBjX*UAO}hrNhRDDCmceONdE34HZg0?wFj71IHEbmbKw_B z=nV*;l`OL#*C>kXw%k29J4Ai4&)8LlaTmoD?b?&B)Z;gP77~lEpZ~6)_)3dDf|r7RYT9Jnnpse2|K^Q4m+BnAi(cXn2#usp z^J19ZjofyU(Yij8P?}C@{ej-0z)J4ipT1^>sjGEj!3pqII4wG_@)N) zF@0>gDv@2xy+Fu{#$P0C;jv{@tmQ+KJHDZTS5$}dGI#i->YOhZWoEVn#<*fNt;Erf z!O3Sf4c7eWka@J!Trf@!_zqTA!1dbbsXoa9VpJ!@^cHJ?bS0K?%vn;!5uZ}LKRDe? zFt4nPRajeHyucaufXWNHifD4}WC*liHv19EhV;S%3<#l;X~;LFN#i7@V`v%i+Dg^V z9K8KBCa~k=?-MJ8k%SNp>2$t1!=NcNQt=?8Oqp`EF;!Vg{u{*7cb1b(vKh7suiWBP zX1bqr;m+ey^D1ZcIFv8Lfa0cUv?SI=1+A={*j)6AFIV%4tFs7eq0>WcAelZ2q56xh zcnSEjw@1N|m{;A8PlT;@Pl_A9`=sT*8fl<)e8TKEmx7K?6hr;z`Rg(gK1%1A{b{A8 z`JXVWJfWwjenR~x3EC5XzYMf2KP=bnkPX9)hK;2LNlzo!BCi#lPJ4B=Bag3A0+#N_ zG;j6tbu=F7=;(MhSnnR+-*~NWQ`+9*+1o;Sa!+G1k5pSXb9lWbYPW{Nq?M2M*d9GX z+LMqsHow2lSuLVTi4cj&w*)Yg%N$}gnsJ=zxYgbmpVB@$aN=e~p&0x!oX&{9g>Uw1 z+fBx#uU%Qg&}%?NMC~J!z_l%=AF+CzS687LD5pf))m_RA=(@f|^UAHObq>h*Y?hRs zmzgZ4V^n;_8XgHQ(5IToK3f9L z()U)wnyr0LvON1n6~x7&#XM}?>8!|?Xq%wh-jZ{3tGXrl9JWIW z>(0@;3Qbt#zMS;Nj5>i;bNrOPPw1tSY0~%txiz^X%*5?`zmlJ9q4XY09H%FdF?K{G z_i`!|NUqeiH-gdOr@uic8DY>u4J`sIzZlNYx*zL#NlRgYtM!?tin5g?NyVPgit4;Jv z(lB-6J`H#wJNA?AB$@Q)sOzZK;c!BeR_yy}mf%~ZDHujh;xj0#`cj2Cc*((sQ7doi z#4piqA$&uYgwi8k$RSjYup5!SRSpJ<0D%P{#O&8La+`Ok2RgZ+zO2T>`Ab^ot&=`s zk)L6Td5Y4*itr&?q&UNR;)BYaeSzS0rEAYeDFwOB6%6`s4e%EMhpILx0FE_1q5f*%w8V>wwhO*n7<|Y(#|2hw(ATpBZKj|@`#?!DbrjvsbpGcWGRYzrFe-j1D(pC{Mi!TA>?E+q9 zUtDX+Xy#MJ-5lxr2-VZX%)8@_13KCsYKKtwH(h)3#A#T^qk~IEY0!Py{ppSxi-S3v zGvrZq@Aa!%v?bpf8-Q>}!m=kA_!Y@cIH^~zz0I4^fyDg?KKevSw&msinn z^caY6&9L6(P!m?hv&*1Nv+@pRB+L#j_Yt%txc6?dm)wwyxP?mXD}DUO?xL>8md-tv zT^W%BXcggPupYW^kM>A^Qza?k{d7%`GatsAaROo?Bg%Ct07}Vu^+xqDOE||58wNbdJR7H1hGYl54?HCjqW$J)JZPE8ume+ z+m)_DW;6NXllpH%F8eZ!%7gtFp(L>@3vWd((suA4seakQHh79P0aqPD)Gd`cUCn@P z252a>Wb?^936IJO$mAwR_(qlFJVL#DS5@?dWVw=bO+$Ip9zxA5p0(J(UYlTK&oxWm zs~Qe8G}Gzu+zJ_CSji}@Z-q6zprsKqKFatzPyt9-ro_&wKxxEYZyAxgWX?4A=( ziRjqb74x-_<<7yvS);Nx)o4*>h z_%XNxg8Lqp7HfeUxOZ4927QoWr%O4zqt}E`;o>%j9QBs+(aZLectx ztq8t8C%6&Y<2?TiAlud_7fMAI0=0{(XTmb$Sk*hd6R~{h`CBi_t5Gq;rReBhHnXS# zE?66YH)FRw09W-Lxp4UCq!g1e96(w`Z~!eocd9vEUj^fPSUJou3p(ZUH$(UtRmo)o z7jV$hh--EmHayKjsAtDbi)pR~qKNQQk7+??Vx-Djwt#{wvBil(I7~5WduH>aKu>}9 zsrj!(%zu*bk~>&In5qfPLqc`#JY}^Xi8B*Jbgy>a_LL?grWH6X?Jc$T+&E1_z0Bxd zFjwB_d<*62T+&2XIRSYEomYoE`|``xIDQH7cQlvN=$q+}(kz1;&628u(5BO=R@Wp z_l;$WA%0Ot8%>?eAjKOEL=(*An*1KQ9YYOpk&}?n1KOOqw-jw9 z=epv`5eMt#VJZqaldfB!tIl;FJ~R+Q7RHB&92?D?#m=fm0ZqY|po_^-Jc`emun=J7 z7ZIjx4<6qT>fNulrHWA%EGL`6Qa@6vNKWP49pw{h@M$Wh#}XNZ7cKDD!(G6aZzfoj zpL697@006*_(8)eAVqF!un@%_K=H_=&?oHKX%rW$Biu=LlEtA$@*OYVIqSZ?(_vmw zf^4-s%y^1_$rhK!w~V;zje%V8&k%uCc_bv$Vl$AVY|^2EMIP>#4(Os!K$%=s#Bx5? z15kA11F7D}dlE!7FuG8V89(V?8wwHRef!>dFkOfsGk-lRLBkAFA1{j|A;(CBe|=%? z^6O8)gt3CF+2MMnkI14+6K(47t8h7hyrVe^PXX|RMMw;RAny6SGLoo0q`8yH6@Q4d zT;@jWBd-T~0dX@K$Fx?r|2FM)?U}?x>qSz!2SIS$Jk%vvhkNKb0WI0AkBGxx>7XJ@ zdkI$a)Y^?EL~YL_8FKBs4RCWDXtFvtfH=}V*Ak8N=#6ysR_HFb+n4_|aj?j|x6n0P zZjH=lYP+NH--ZZRIUvM2>)_5-4ybn~h#(BNoTy(lS@MDV9s4*)s|OVD6dR=1#TH0yA?*!$h)(64ppMH2vL zxSaWywUC}AQo_Ee7fp4Ng^Q>b4F3lHY%Mb_g)HA0ruVndyH!39I0|kbRZsr<6)zdg zUz+?g;@9Ud3b>rd@Ra$lO(7^t z&!TEBQA9LX6!z;oy^NsY8{ry zGrsWoFWJJ@`~054CFs`|PPg%?l*oNe!K8DM-C3~vHUDoPAQy#+<${MOep$)Ctv-UEQ(6^S$cCN6a|-iwEs<);sR`pJwae)`*%a|(%cy60dORlZRggfJ4uIOb>1JWRET z`Ar)1w_jq3b-N|f!Dw5JOi2#XFzI3D_u#&NM3Lw#+U;YLdPa?9O8Wtf#(9ipc-V)f zR5N_zL7lpg(m#?lridvV{6x8xAd2|c3svKrwaPVnatj2hzX{eQ9>jqsyhnN4lcc8O z?f$-wL3d$P*tkoXjOo9`0rTzOb}2HUg^l3$>Zid)czAdwI?>nJvPk|La@I31_94u)w0{w^=(fV9H4U?=LE|$~<+`6DECqf^X=L|KCQV zQDsOkx&p5t=-sSVloo%RhO}zsu7BIV)(HfwR=NAk;1Nj;Yva+gQN!+cbfVsizpc-! zY+TexUXAj?vWqk*_gP*^L(l*6l6h~3zf$7*f%T$)wU?$sPe1>aAXUowH$@z}0UO{} z3$GJH@Za{p{J*_p*+(Ou&U;|+s4h-=jbRZXt7*4MkjmRT(YWcJX!`uQ-SUvlee6MX z&-s46;&~eVF`eVjL3*Q)xzdCZ@aPC8@gq}95e*mZ@_B2nzI%R^3Pv>H?0qrMf@`)leX=$;?AqIwZMD|A?OASS zsq~(PCsXc0T9BfpWKlp>j2ku4|n&{4dtN@@e#l z6}-{<(bnl+LWOUT|2ie9=JWlVZVzMnhmqa7-#FVba=p8-_gwWk^Khww<0*B6h)z{; zzcUN8yB!p-2g>bm1jhYJ7bC8czID^$${sT|i);we`7B>Fjdp!u`C5&Z8b?xLy^xA1 z6-$Q&tB{@jyYC&D`KuM_pD+ptIy$p#rm95a6>F>dc2ZVGC}7IIk3Ut~VeRt$v(iwa zA4MppMEDd0_iqTvA~3gw`$A zWv?njd&9}a;8DN(W-2HA78`ZBg~SJ=3w*X`E=JZevzMqL~X@6p_dup=zWE-O4t zaYWRrXm@%5mk5+Vlqc8Uz|8hL6#~+xk zIn)BCebs&c3c;@irA3Hu7T9?qKf)*exfG$lH+@r|f{s&Uk=Nwu&QtT8-tJwq9T~el zycw6arl_4#StFWzf6Fj%>ouPCCnKj%n2JsEb`GnLExQn1@?+$2qOf%q99>*qn5b(I z3~y4l{A6u^^gcQ*=CiAmxF^HJZO6FYb}lT?KtCUsN|I<~V)F8x=EVi332Xfhl@#P| z0qL6I;n_zJdoV}JCG4I7QlF^~7=|5-Z$#aJYWPn;*^>gzUT)S{z1@3fZvpUTi*>=_ zStu{;MR|o5ax&CHMl8Q_285#Nzejnx$=8_?7ovN2<)BLx9~d=HAU`oYt1{) ztc#Xb(&`^O_pKF@GZo^B8rSnHVblvhSYd@)iS|XTX;I?VZUPP(*rmB1EIjW^;B2Pq z$NIM2N0$Um9*}CLa!A*U*g(41h@LWr-}4JnyJrJ%fPQUVlR$^Ho;$~mE6)a`TYy6* z6t)hrJ#xd^iF#C@y=L&(aI!QWCF2aR^XS#vQ~TPklk6d%pmaa-F#A0ZiTzI<~xT+|%f8$P>2PB>?OQSUPUaCD$^xV$Bv-a%5F2XEVW5wPN z(bQCz<;4$9_hw<`AKB5W4BBc~-gvo)(G4}Wy96nZsup%U-ic9K9plNIiBy==|h2SHBB&%ZD(JB2bE$U8tLn zBRbBrG=Q7ybTZ+!?m7r2nB!c0bA1QMo-B~e1rAAfF5YKe<_e-HgsvMy<0Wd=ru&Nb&u4#wGD=rvDFo@(=?=aG3E6K6pIeIpo!E{bAbS9YY9z%NdzW!JrF<&L3=m;{bQR@-~IS16Y9M9WH|d-sqG!3Sgs# z7tL!q*T9>4_d0f9?jX>lk~MPT+`+^8`>f{w`D}eflTLysQ@I`^C_eN2v! zF4RHc$hHYU>Gwg*20%usBg8|P7qCg&QL~4D5*gt?f3-|>yIO7z39Enkvkhl&NyA$- zo~46?8)@2sn;q-8{H%r5&8vR;4B>}JIVKLV=_iCkB6RbePMebrM{D-|07{RFf$V$# zyCw%HI_v_iPk!4fkRa^Qr4l!OjbueKWK=ToxyTZCo+jIG3vIT3^w%qVee_<1TErz@6@oV@4s=?zT`_(+>sJCGR=p zZoe!)XBcY-P=5slH_cv6#$cnXQ>Du3vg$7QjL*OFg_*5jk<>iodZ*Y59;F>QyJ)l~ z1y2WmbQ7nM2jjPo|<7g9^PYNBrs+)2&wTj-}FI^yznu)Ujp}!mQkS&{Vk` za6T;Yc*K8lgnj?=69n1C;#jv{Yt4Q(2M;a3ECeM}umc7=W(res(^pzW`D`-+g7VkV zG?JX?IZn_q!Zt(az?_u9B=onf)k@C`>^iC7pPLT50^PTEyIX-&T`Rd_?DIgcsFYKJ96+;ia0* z4O$_=AE)mdq$e_i%G$4)r?lAf9WLfFe3d0d;(ybG2WLnE!5)#Gf1uaHj(f1@FzeRp z^Tnvc?)~^-mxdR=Yw9LS#(e;9#@82(<63%P^HmXgiM5EU3GdhZPtX!>O zsOSyoqSY`IZ4MpqXW9Nr+aj5F0UcM9YeTwr3LrtU$z(yyMi}en{?gso%{4rmZ!GZ2 zyOA;L_20bWK9QsqWmq(Wnx_}d(s5gkhpH)~L-nPZ?>SeYa|YJcyAkZO7E5h<&{MC) zyWYw9@v8~xG?Zf6m3ifZtOkJ>32)x?*aP%Mrr04uC#Le9w=;g{*k%A1UC0cOQIJL* zusANB!Qw`btKFm{pq7tPr;%+LjI4c%A|0h=l^uPBrygH8zo5@qzaxh(mOIVvf- z4H^vMyVzS<4_sBUud7%TGE>LCm)l}&3%UG_{7MwVJ^r!y;AP_I*d8Xw*+6YhsU7kM zF)VKA-?rLEXHTqLS>!jt7kHP9M`E)j)be_c&$s};+J_E<^FW=dgZrm(#8&hTivOhd-m2Aek&>#++|kYHvcLW_tUzn2}^;?26B87ci_ z(^aNpQ+H5kMps_VMB1raF9~uZHN=h7 z7i>qsVOUXQ5VbuBwuou>YWx;yk3Re7Sp}*|I@zD}4CExPDARt@SvlT_>KzE-HeUV0 z8_#Qm3Z~s~o;`#l?vy(|EswbK90^IyPZ9evlI{*&v2GcC+pC0EA}EGKY4_uOmOrgF zM^3sgcm0&cq7O)pTnTtEV>l#%xOANXMUHq(0#Jmcc(Cz!L2UX896`JKdrSJo9#!c( zJA>%rIhGy?u_iD4=TJR@le8P2hc+S9sZ=&^E}`Co2Srgo;F_o0( zg=QGT@}23{gccIedu`u0PmA1QZ*UFQN?a18>=0w^jeT%`3@g9wOC~HB#s@zX-x7u! z-%YAX4)?+<7D2)tk9ogS(~7$1VN2X?{n_F=$mcDq;q-!k~cIg*6wzwNZj;z*VO5VpY!s<33Q~wk9%VL*iS&>*p-DLm^@Wg zpP1zCeBd|5es5tj9oiO|P-<5V&uJH``?O5L3W+Si=4079A7zNECHb99IdTv zwbdQxO@~Or2hLCGtpc=&vFRBV-8(jBwr@(*5nUM*-zy76@iASDMGKS67{pG^JA9A* z4P8-`AU2!FdB;IRPr@<2%5JKYe@L;iuUa9NWQZ8_MgOg~&N;ZFbZ6YlH^894Ia+1V z2_Pbx%y}2~TGI%mu+S;QKjFBoE@ibdPy6m219GeEdZPHGd z7nA(+TqXIrS7+VB->W5re`}bNVN2{QRkY8vr}uXqa>J-)wSDgFnkg{&ojQ)7r(XKY zWwK~2j1E+$hAYqR3#tV+&NsvnuMaPBTYt*+u-y|R0m)gs-BlRU?=CFpuHu|u9z_l0 zFp}fO&|2u-(j_*Dac$&%!JCYQHz#t*04qD>I^&4RO^U@r3t9Zwiy@@_CiN);_bxGZ zid=B-Tk2x9HM*C(o@q&y9c5N;@T~Uv!AJHUdif|zGlLHY{Sh(lv>qLsCe&r^qr7NH zM#PwW%ZBKR`*HIhaxo`!$}|k`TnR)jEcAPC{^_A+^P~!sDb0*=R8>}pYo9%#Dl1xW zM|G_%UBWg{owXxrrt764(Zk13DjIYGSTyU!_a|+WH9LkgFL-aor3ZYE`A}c#AAy;|&S+We$ya-| zQk@A`Ove!y47nNox6oP$Y6)>+gl%{_;c~>B8MNwe`-AX;eFZ$wAfaM zOr7U3QuQ)!dweyRwA^a^8MJnj7sJCZ0cnoOm_tW(`)-`Nz-`l2Zs?c_sn>=%a;Mtp z_G)Duy&;?D)B@j`n1jWY$1laUHkq!{?y(cV){&Lp`9~IMc9q*lBFm z?NstbuNImv&UHLr4JhGVwr13cDAlqwB<>kwKbF(JcWrDDCG+r`vlBjnU}ua#?gv_X zO7kI!oE4XBGmgSLs&k8nCIxvyEp1)T@wrmXqSrMg;rO-apHp7Mar=|vrQ*lCDHz4f z(@z)Qx<*jPk$&q%${sH^tY7W7!cFgsyuV@Z2vVvZX@BS>v5(~8r0P>@Wx<6W{^Y}1 zYuBo~s$-_iOGf|bSww&t?xhgQmArWhlHRu=!O_-oO!>wf(wjrznpqu8iE2u?xLxS=ZC%b#8bsiHr1^kgZKFJu0R#g_rDQp9EKJd&_|+dnnIt%&cx;5Q^yH+6i*F_myC^pqTG;EhsO` zVvzT_Z;kiFO%G1RoN*(Tl<%_8F~q4uj+Y>zAYO9nzNW-sXO3N1{5CSiG%%kyjmF$^ z)?VOx>V+CO3^V7Jd?oh0O<<547?dtGT6vX2KcCzZYU2OkOe(*#;vi}O&J;;eQhl2l z2nyQ89%pAqiLH)NaIGe4NlPv&Rx7JKAEMb{Q;+Ib7*-*X;wix?hRR1J;P4lVeXR*Ch%qgm!6ke)w0<$$eMnma|i zE8opw^y`T17w@)=;*1*mzz;(XiZC7xfh#K}Z!Ur)qBz1ZgtA-uC6~-znn$?(Ib0nK zg6ysV7<+~Z1d#FXH<6@$W;CF-WTVHqEaEttH{A%yh_I4f+f_<#<0h9I?HUzz z=&_xKu1S!hBEQE;SDfq&`lkXkMmSPosl;uxPI&t4{*!FjvuvY%E5ur3x4~COq9fLzhNx(I#kNm4Ncv)ncSl)B%C;BMtq&X)_~AT|O5Xi- zQ~FD+o{L{B7Vm%jnxef5bm4(8Iho)kajd=(KXmgY41lfk)B3;DUDM&&WWwDWes#l- zyA!q^W`AYA7LW^)C9*FhB;0HhASGI#KLHYu)|Cpl-x}qo(Ru_{=CO^Lvn~*LGO6(3 zoPk^7Xu_|G`UU2@X-`)SXuo0RD&~1+svEN-1X?tZoW!I#}^b63XtlKa4IAEJw+AH}^IDmj_0jL3Mu*=E*1^fRt> zW4$pM`6r=UZHWT4+dB|z$nF|q9`H?W_q}e=?MXoA-%j?(dW#RWg;=M2#O4LB;+NM~ zOZbd2Z<+9)R(5Lfv-YNhNPW!GMLK7pw zqDpUH3jF7Ya_mNqXZceSaog|$cmIdFQjhb9_7@#2V3bkfDC{0n{6QnK{(x=oeP<5E1@vE7P5M0HQj}$eB5!?B&t=wX5q=rp~vVdsli`vdB@VQ>6uvYP|rKY zA@P6zp|=_sqEfeK%O&gXOd_NDK6>5L17a$@qV}$)5E#Elo(#Dwst8rO<=!yTVrJgj3OEdrdpbX7V)a7tvW8*^GcgWG$dbqp%g%aD2sr)AdE z?ZNJ!KJ+$B75#7oV8R;1xd}~GY+65TLHN>_djOzqQPi<)AM6xfNRvN!18UG=QZ>IP zQGF_To+@DN6RZCZLlU@1Huuy7-hX{17D!kP5rLIGz~OVZHS_j|gtpIPbYF(=PQDY; zrpBN`|>v!9?9+74=>T7FE^+iGu>+?@l0%Tv&J4kE6XKmgUvf zN1C;4E$)PU?W+Q6a}7@I%~X_Ul%?MDS<$1Nd-@NU*~Bb$S8YTUw0GRBO^2|j zLXKv^L+2?kuvVY%h?}MP03CTU2kMUsb3jKpOrCX#+TTK_ z-Iw!zzTwTPKEZ6x8E0G?D3mUhcA1l|^fZm7%bXff3ZAPTYX+IU-f(ZcyiimE^v_tT z^z=7>?fSIwa~|Wdho4ytanD&mdDPKw4f+^ob^Uhbwf0XD^T%2ycXb057riIhTqaXt zQUkr7Y1O?{FHNrei6deEH`-61fpl>W0AIY&t+WUwoT{;SZDr!`@lo?ScR~sJ$AYf; zs|QoRAVA0$^htKCjP=4E##^;(>~{RWNekq^=)}LsQ6)bT`Cd#T#T;x2G?^^2Upk$g z#1X&fIX=JDNNBA5H?{ul7eK6+u1aNnHzY-ooxlE=-Ul{{fAJvyd?jVC2(#4%U}$sB z4V+>I{~}Qz`Y9>}O9xB;&7^`%0Ui~JuG-S?i3G-Sd?xz4ge0lMLqR7OtyYo$%bxcL z0gQK~9aOgHZynuU80puQ7EjL|P9IGFP3&8`k+}H8poN-U5~r!KF?3gpM4^*gZ_|P~ivTV8;9Ob16UGB98$ple ziXC97A6^6>khlG0k^B3>R70S1QfCA`&r^{aLT{Ak!17l^{`)I?{;?DHaYnZ)(xdx- z8vk1Y@)|`FN2rygaq5c$qpiKCVwyWgVrc39`_NEh{XKLHRDfUCcKvR(40V=ckrIl;O{@){$GNGj4LIyn8hrq^{75|^}hnGg)5F(LW zO+Ui)*K~{}p3B?cq#Dl%-g%lHwHxC*RWQBTW8qP;Sd3>oG}~#nNDRQ*DxNv}^k2lr z=adh7%7mO&5G5K<1X>)w<0hDiymH591+{rA=~3lo2OSkvq6%Aa`5VVu+ra-Uv(oM7 znq#{sXLm%2(sDan@5c>9Pgax;31#w$aw@l_M;s=KoGW%2C!$Y1<%XkW~w)g zGi-ALZLsdc-T_jJGpj`fPTz5vIn60ej@8cMn89SjkUNcjN370ca*O+gytJ`LqiW@4D0e`BOl*Mdsp0dj-mSs_J&d$B?rw1Krt>p~-#e*hs$%aFt;o|H3 zBv>?PfrW0q+gBmGuVsK|TtKIuYo7yv?}bE9H=H_sTcl0wKeAtV)I^XD>6_U;GgGPO zs&s!!+^7fwiQ;U5Qv#_1%u^WZ$Kq8ONnF4f+E5^S71(0c3pI02sG-)?eF_1!KgGY> zlLrz$tfFU6?$DppB(qARzl~B$DuL!cnRFYb7H76#O#~#>l$9^Tg`55~KAn+-nj)QX z<0<@oqv?2MW;13$dA-Y(FtK-VQa^`E80Ylvt8sd7F#4=mmRM;KS?Ow8lv^5fOkP+% z0@#_gt2*&J#O+~@=V|0`WX7P|k(Dppo3_tHtB;<-(St#|Ux+Ddep}4dhfJn|^s?wf z=hK*r)t~mDT+oYEsE#W_j%kBghj)ilE7V)PumUa zP4UCQw&%uMw(7^^dainm#&tE$;?wWQua$AS9=@OTM{N)~RX=X;NZTP9`zeJqIQQh% z{hlozHBDN?7CYtrm)PXPP9QXw&Wei;Sjx&BT;_6te=tsc4{B*=xS{6sW%4=@(Ii=w z>jdF7#IM0ts%tG_I%>3{Mt^p-yZY9S#`RV5dt8%mruXy*EghDt(I)1gyjHox2w)<& zZZ#I%mAP%o_~?fOE=aU64MELp9@zb2I2do5yy%P=O%)CQv$LVVlE7_)*yFN9I>g1R zsPJ+3mj6oi;XL_(zms1RO`D3Uy*neDW)B(`&6~f_E}BZX96U^nh^zM zGC~a2LhBODt%RMYz}IK-#G)}e-~j&zMVzB3+)n&#X*x$RcHCZ5S+atTchA_HUe&z7 zN4t7=oHFtfV>G=Es3isH$*SZVW>)jb?=+aK>n2n6O%AQ-hfJpWc$pPhx2iF689gG1 zlj0{Frd_{YE_FBr94d+==bWk6j{cq`o1%!aamqo{RuR9IDl$SdlWgP@w;aeG*ljOl zYMJXaZzr!te{^x3q%e!ef;kxGV;X9c46>Q7rQ`Iru3h9fwol=I@JA*q zzGwM#bJ92se0owt9v0|$!f%s6^qb7}#ImdS*OU3ZnepORvRT@RmY$F?Kf65JjF|Kr zqPG+7O&af0Y}N%8d;taequ}v3WYIT7AsmC3f`9X>oAD)N;EGF ziypUM0-;?$=SrMy7WO$^M_m~V)D5F7c+)$`LAg}#B`0K6G%i^E{ z91$!XEayc_S-R7Zs8K;~v$A8wA?pxN%6|sGdl^iS!jdT415T#4=Un7h#^ugx15Dtv znY&`^S^RIo`IO)_zYVnoy$zF@fVX3Q6~iJaE(*8?=w|Rg{I6=e{DcxdnN}%ALd{2I>iJV764vseExs2xJK-6QTbu?rI ztLJaOg#@X$bXls@XIj+`t2gp_Wzz(y2rv?lgP8K6(XXSyYP5mdTio%{Z0u?)&R_Mp zVo}m()q~0%O{uI`^IjRh;oaKWtDoNubIS9f!e~4og<-c`8$x$efgXHYM@JcXUfrm| zWafrrwaUWi^j?^AK2J~Rc?phWI5pc0sve)CIE9sw^`D|Nv1xZJ+P`t(;(MMB&4p_K zJs~i&)D)GeeD*slB$U3qyIzKd2oL*g$fLo6+x$RS$l4S_FN0V3(aVVCpr*P#D<^x) z`vPPsOV3Jvdg(T5$sr(CV?xQiWuOacCIYZ3{a^@P=o?S9u6wK8Bm(VKWOwXOu5_g6 z?~savj>QsH?L4KXtFox0aD9oaG${;gHA7L$pax8;^XT;iS7@cQq(~T%xxxa__5Nqd z!Pgn0X?GcC%JcucH^b6 zK9HTCV)_EyZ&P$-8b3b5Y!hZGcS5Be&@u)rWNMP(M1t%};h4MT;Vvl}4_(pyyH&-E zgI@mS*o?-!2Mfocn7Osc$BWK2E#@)W6>2)W#SgCbN82|})=o9fOh1Q*U!VV>o|r_J z!a8@jVavPo5Ua5Mdn4kZT7RYTt7lk9!hVv-bQi}3vgDOg#;9We8mFt3fmM@&7ICp~ zjC+3NfV=D=R!=f#72>$!SK@b6=NrUhQrbM4>o`htK477t5a*Z%z%+fF;WLU9P1X^~ z-!ebaaqb>Af0eoR3QwcGK#{`(yTvchriwxHdhCqCr>rtRTZw`Bb-Ti^UA>~N$zZNf zvbPaVBe4!Pk9a5=VW)!P%HoOj+gDkrd3@2l8KIA0wz5N7=~H~%vO@C6AMYgiPX5jf z$p6JY5;Re5b7?oeAZwq~BX;i7F^V(T8$SOq^p7-UpvNOYNFBSbri!T=e=#;&>?v<$|b zYh|(SEo&mcTC0J^wD4V%53k{uFkTNBU3A6~vblzBKNLCTX|pBrX_CM z(wlPWCvn>8^iAsVbRnbjP@RZMhYZ+Bat438?*#i>^_0uI+X>HBk|g8f7tOscnQxnK zF(k*yWkaSl0sxG$Bd%&Vxm`wfLOfiLl|wb1au9rs;MaOX5vjGXiFp+KhR#SrvYaZ< zbGiPYf#~bP+Wt+wR%hH2t(de|0@kjG2@tXE!u;DVoH|w*iV&bqsU{TQ18pGpV!-p9!F+k+}Cr-^Qqu#Po^zXMvbl)uc<8v z3M|BDDG~O-D|}?PIAr20C6ZT!6Whj)V>&6Y*YZG4!XcqwHM^EM+S*pDAtB=u#H$$Q zOTyD!k~R0y_JhqscW58kd%JB?R5;u3b+`3%3blqug~A4a!2;iEH_rFQ9p`VxJ(`UbO#I-PtM&~q{+ zcgNI!-UO;RsMYadZE0kwIkv1XkRf?d24!&?b&MUt$h@pG^-QZ*MB&=LyMPJ3;;mXj z)c`M@PAU$F1RT>c+`W32-f#I%;s`~(+7i?}6R_I8Ls2WBr!2fndSa~unjpf7=n$$2D0h z+91-4!DQ<%JgAUVOM0lvqPm6Y+~64EOC)hch)ZJc#A3x#xg3zdl|wuMtA0%0d@y1D z@*dGd%<_$JLH^)Wa(Fbpa>k&P26@W@T1(GE{%G;cg%!Rq|4<_Ba#asVxi4YNfv)v# zGHdYhV8FAs{Mtn6g!ilW1~MBstlREFO>6G><#e^b<>ZkzHz#;coNKtHpn*?_X2cIt z(wCs(`}1%v&5hoQ4HsYjSV3pUL&Fk8(ai)4wFKglY0tavD7A&UNRhW?Cu2;E_37o= zXX=hwpdkiZ`W3!@<%ONV#o}%quU-yaZd~+X%lEkgDjSjJb)v-Vy+I?)C`&eB0ePE* zE*6_-P)*VS+ul8)!IIxte`ojU{lm6mtKaI#D=e%8@3Z0idHE~*ptgu)o}#2m1n4R( z=F`dY51oK$V}R>l;N2-vofk`|aEKji;!TZ4)t+^P;>z)A2qTozA;P;oy3Or7tMpsX zGLx=KKen%3?4Vg;%Qb&5O%Ie0VDZz`bU zN^WYm9WJ{r`0Ool@Lb#}Xi8WP*F!u5N_m{Fc8}j)ah+-q;P-%`)k@>Ncn)JQb2mGV zv9kQa%3}^enszySPcP(ssM^t9NX?gLH$lxFvn%St>Q&~-QN)JJ)Dkky^I@?(}nCSS=lj{`!h zPozNf>Ps^K=KGK0hoHpGb@)NX?KwJ%y_?Ki`Ny-F(fAJ!2=j zNt+;lq$?<$a>KjFWcg@Z!sjlsBEIB@{ZRQ~`8F0#gOQRjL}p3kJA9cW1cuHZ-PnBQ(a*LcxVOs3F7GodnaI#&#*vA&5kr<<}sghg@HOtGNT!Sve5T1qmsOW6+ zE5Fct(>BDJRF6V&Jq%BhXiglkMd#>A6-S%{KT1j%uQuNs0JowU;wO4PW|=I&FDXClvBwxKXxV|zznzcc!wx0(Ec4O zH@vmGVD9yWkB4U|3Lg;=Uv&JIpjN6NQ3!`?$f9QjK2pa@2Hfm~I@a)(N8I9ZJ-;|A zll>^I6`Pgm^VoKZAi3M1 zN8KNt={0uTV@=G{HXnDdZbLEY_7@98v2u(-3po^=S9DAzaxA*Pvq4nN!Z~7inN67J&;mw zLuNmd#?y3G%hR_yhoy&Brh~(rtoI$VcMzqj36v?BZ}G+l8I&y6IFSd#YoIVB<++C9 z<=q>*0x%&C&alA zDkMhc(i8iqWWXos72|PpffOtzV7SsLntN5C^9wVN28I@{@nrVdc5VEvGnOg3P_2r^ zJ|2wP5XL@K|tfi{2+(`~zjCR?__Lq+n?x@H96te^n35L$%+P;-W?C&938Qg4q_ zxw}V30sNP7s`K=BcOa7f>gUI$S;l!iAozf;o=Z4o;2t_etdU*fg9DUZ0;8-4f=5MB zRIsFwzqLxBXGHfYJwA2*cKO^n)-PIW%8x-#1axw1aG3dl z+OfZ9DldN^G|u5iJ9W^YQC7-EpiMe|QuN9%qLiCH%j-MUwMG5B_~#oC!!PnybIUSy zgg@U7s&Fo6=2THG%dOF0z;eY_JXd!NN>}{wgLE}Sl%s;-w2zR9Jz_M)^TSmDn>6q@ z5plHmiwn7XLU&D_TJ4Ux^sUZ%lV^*vbWmoLQ$y5%CdIWmBqE*5ps@g+9MC z&k>--4qu29zM*+#j3to_+#&Q1n%J_|EcVF<;s$KHveewgwsiGYRo5^Amu@$sL+nb2 z=YJyBGcP9uY|d2L_uZc6<=H=Upo`qi%M41`VsLriiAZA-<)zy5$g{7gowK`hHBLIg zQ~NteibBX-BflFKJh>5~Mp11;ri6OVy1p~`#D7cvPqxD+_*Q65@)N%*@({5Y>>+V; z@_|0s)*6WzRcosvyb2>{JoMrfzGe3wuef*6)$#kCW9j93_1fd{Oic=`w|VIUQzkp7 zspnYW5-17bEF}zLrZ?Ap=A`sfb&0uuKAfL}JDV%-i@2zBV1ghe)8$0$VaV=e{ePuj z9=8)84w_Iex22T0HDANK02hqL5iTuT3Nv#+px&_1z6mWqsx502m_<96PX8sN0%axq zsT(!F3O!EpXf_68wVAB(hXtnqwY;I*Rjr8FOr_`77M1IusVZ@IV#5j&NAHNTEWS}d z+%%w#=2)P589kO@y!4Gx^%cPhNZZ$}v|pghFxSW6?#MmA6t!i8&-cnZ%4I zuOr|mqtkQeoF`~go)7G^hl4rNm!PQ|@OpUk-evyVf!w1Vw+KkCdP&+vz?q^<#fF}>uz!XHv z*?+K@Kv%+VcX_KNaC=<&8jb{N%fxjb*A7)8q{I_ft7dKxzr2<*FHZf&wc(e)b5M_w zVi)mxU6YsChV;(wzkxcV=j}H;>U9khh^M_O1)YP9zJ>2Sy?V)2aV>W*!v(0bm5_*mEb-ff`y^MjaeB<{{=hsH>j4{MZwC#`j8 z^NZY$x?jfHaz2}S#T+*i2JcQ#+zS_*`!OuLwpf`^pehipl13R)IJ zGk_kyACtQy+U4GLJx>b2Zu~H;ZCU{fijXTj->I9IxEU;aVqN%p8yI8mNvDu=Xrn?o zWslx|#+xc~e*%k~S%}C3e0wcAS7?+}w4!|Tz&ef8p&F%iiL~%ev1{KzqDfN7)N#84 zd3f&A&CSo5-Zk!YxVvIc#k47`tO1$wMpD8TdtnZBIx_A{(J@zj4n~q&m0Euesta1r z@V5yXzra`xBqd5A#TjIWtm-OJ7vynlsv`^T@N^C8yV0OXju~MURXERAfF^q`p|IPIY zT?an}J`zMc;LVFnk!;WnXlp2D93T(Z|MT?5({3A89uMKmZM}899C7{DZ8st2^AWv+ z7rn|?fpp+J<7f*XEc?WvVu^f*iO4c`g)q;NU_iE3PiNhGNyifTm2;YsA)e9%s-EEP zlcXCh$gbGRT7b(MkwA&MHfDU%w+-mCX@1_O`27EoQpaiQ8wHHEz+BzHT z!#UhJE^?cRD=jZa@W0jTN@7;I!tDqlf8Po!$}qumJwmK-g~y4u=q<(b_kJE$f5(U$ z_97w*)7};dC*9l~f!o~niWSpFx%4E;FUG{=wiV1vo^-wA)%73HUmdEMj-K*4=9p-c zX-b4+L<@6HHX<;OERgF;b1@lC(#iv z$Y&sKs;-rV1%h!2ge3`ubXp-z-XodvYNBrjq3Rc}N$CQAR-7R{aJFAQzv3jo0f1vw zf|HQ+FfmT*&^i|o9sM=GDzRTng#PUvOHIpxPVOhHA&5jLfXb-dvSFjZS(OUTe)g)K{_*&&7Z+U~9wR!1KdeZ97h6i{RzD}n#G{1PKO0pU=D z6g4>i(mt&h#ZHz6Hi=d!Bz=+~Jju8(#sEOA4=()){;l~hrAH;P4b0Fw@C^|i zkWT6Q)b?~g!gzjb<}w*O_mc*7jx9d0IvFlt~5~J3eNTb+JD))tkTU-jrQWJ zZ9mox>Z%V_2Hq`1Swp)<1h3~U)g(zpQ*80`WTaCj22HKwR{lJ;RN?%= zE`L55cY__|$2ymW$W1lmNG^&OH-|a=^yTpgm#*@<;BZ91To%_7^qU_q`Sg6A8B)RZ zPGIWn^a&*Xa=6pHEia6%DvCLTOe?RFiIZI)Oo?u+p!bze8PH0P9&|4~CFA`0<<3N* zKBPeOw;Y7p)OH2g@p(&CI|?}Em(63SB!z(lC$dm5uj^;x&jrSQ7b=<#%u6*(n}KcG zO8p;P7lqyXK=A9R76vRtMt?(!Vx&552W? z3RzyBt(n$nMmXaF`WyDU;6q;Rd+3luliFX_5_T+n1*#%V?<iJNL{w&G*k0PzdY46k78xk1q*s1gLWWEMK*?hB?1sWI&Rfays%OwJ$0YHyYKFG= zV2|T`Z{q{3J4)Gw@S+nxPfRlKy0DhEN0F?GAE^HVWF%kFONAi=79ZLnCTjyso!|U` z4JgjTcT!XJG%aHv-r{ZBrB>Vj*+~Gee+R(UKkPP}PAC9tLa6a-`SU}HjE;wThmubGNwiTXt{Jj4U0^NY#0@);qYUZG~tyf|YiOPF8EyRzTIr9Z4;>GA z_eD{=OR1$I&74Ct^^x>1;j4F$!v@R52Q8-oncvPEn|cm^s(#PluCr3PYfgSS%WNU< zzWo@V1aB;cTN=^zla{#k$*S;(#`nyKEE&hLEsA>PGp-vv=8uQ+-t;h8ADnF))G+rB zU}Y%dNoE`cY~d-?m5(*q-joH@Y-C`-zeUXNizH$SRVCj?=K~%Bt~X0G6xWxzRK2{O zHOZb0YrTOlT+&m2I>E}_zf6{_swgfZ)`SM%2Ps|YCeNdfo>FY`Pj&x7BTaQUtcAV{ zaXY|v=+*fCh>};M)#c#(JgL6)A!Z83&H*<{BUX7I{lk51aF50CeQx;KAaDHF2a9%h zida^PPJLH$%N^)$wLSY4+!jhUF6NfX8GuYfRL&h_pWzsg85du(fpi-BIp|)X-?+td zxS@~1A2}9NrPqB8O*2kU6p0L)<1Mq>N_!Ooxlex*wxy@D@8n9vsG7QHf+KHzm;L3O zWQ8<*Y?YDAs1?64ecXHhr6DEn^*67qSxY|+h5Uhxp|&rJUt+-u$;~CxgKH&)<9B#G za`>$fJyg03YJBwQXFTlXrD`tye!u(5&pKNc(^U^U12jW>(9H>I2}w38e!3{!nUv|0 zT5^=^O^+HlhCYvW8C@slmeLz$ue~t9h$DU`*MgH*J|0970@Yc0N{pXpfiwEP3*zPm$9?J2MDbJ9-BOf_?#T<|4 zgyjxga)`~oLi^4EJOX} zv-*CJgM#~?%g(k-;nRnSXW|Q#hbG=(588>4tFnJuPS0zECxnBjqO;S5G-KH2CM~JP zwWMcmJvv`FyjWl^jEQYaUH0Npd6DEc8fD|bfZ9ZSUS4`g9^KRSm>6!lA~Rj8Cep;g zxeeu)9_)CC3<--aF|RquPsO;?xXzPAEIpUb^Y7fan!#Mv|1^!>7Q))#Y^fxmbK*p- z82zeWt7Ad-igAn#2+$@_+J=09#JZ2O_<2z3ht1}Exw*1cIuoaY>AfCgH+if=`2~Jk zVP3QhUu;s%&xf-<8@f^3ad)a!_R=g)Z;eH-0hJDGO`&| z+L?ER-QEHedlZG8;Tl~YO3%QDyqxY{=lAK$GySP>fj}OKU}urOUvDI;gSAW4pvQ9A z_L$@YL74IsSLtnXcv7n_j=_88*e+=MzhtC|PpcK47x~YARUbav?tPP}VNd~)$8Qzr z(7}IlsRrah;9xvsi%$rs6v!b{n$I477t~pqrb@=;-^+L^m2`*y^+OJDa^w1nE|~O|dbt;eE~nM2Rq}DJTKB_~mK(D= z32oj0p27$OJZ1( z0Iqfs7jh2NA#$4wi}myI-hh`n`UUi08vN*W{ifc1OuyKwiaEd$q|yans6BVC58fw$ zzM(vhOl7@mobo1ac$OO(0;gI-8!pb^FO@-oM{wYY`H8vu&5JOj3&}%!{b^77HiNwb zf<=MSprYpNTQ<~sxrW*clI!tVBBFkOe$>_wf)KWXyx%q5nrYQU$_t2%eI{g)q9k## zQ$_u17^%9Qj8Aj*0trxS2cOVIf$x?WygHxGWQod-&PCgJe{4snx%AM?I>jf#O-)E| zuTy`hL z_tpzm*q|%2ea#~7Ht{ti=|Q%0EmQL3X@{pr_hVr?9*?px^`9K961w7AmmOVWuDN{0 za>LPjiwyNe6?shSR@3#Y=BM8?@e%?2e&{)WKpUKdHCjKJ-9T9Ya?bw}8I$ZSO^R>R zLys`$xKr{sG(6D#c%1M&%0mn-|*}5 z5Q-=!tC3>OugwWQ-j`Cj-eLme&=MElNVSP~v07wkEMNk@!gTVQMhiz`(7tm2L996VDJds*efL{ ze6gDUqRHCfd!;0b_ajej+EOF(Dd8PLT^%?5xYO@3$TD%`>plDAEGTxT0=Jsy4#LQF z9e8t6f2g^P+9+*I2H=eBNgu8xfi>tC7o~8y^-dI^U%j9FD$k+0_Whd?i_Bih#w8Cm zP21AWcU@GK!m$`abGo^A-^1o6?1`Qf9uSib==P*E-v1FcolDqgwTlKa++a6@MElEVQqe_#fOOK2ooCyT>YoMzd#t#;z zeDt$+D{Xm5TuGYLPt$`?5`6=L%o>r5A^=wc523hoaW^eAUJ<3ir3F9$TbW5cJ59d8 zm%hSuey34dN@(~^%UC}HQ{nAbm`9P$`?4~r*EhHwdy5z6e{$4s^*o(9-jZ#ph#{3<{HM%f zQcduQ;xOQy{I+EKOQ_{Y->1{F!rtO+oY;F)J#VJ_N6W!iH_m;b_^pQDoy2E(!(Rw{ zuzVHS^1yXE_Kl{wA1&dMV<%g)awU)IHRWa`f`WFo?0VzvF(6_|0j`JAfuo4EOY(X={j=+rvi%-bzt%x6lx&Cj_#BZNTooIdG@H02?#=8smq${%l zQ|goKek`Z;-;N3+vK}sAf6RV{ukSafpAvrv2gVHW*PD@rY~J~IkxXD|)RIaC{=>_` zT07(wd`55bZ!-l@Pm_$KHg}HeE9Sfg1S87H+d;*oHTHkBc!Ke%PY#im&6pyE-&WJn8>&aFiGSXYtg(cbk9Jh5g?Gsz52*znpZ9 z>Hk@Zb%a#j>_!vJu=8JqWB*mymQ(@Rjzr2M_gA0(XEoS||Mq8H|IY%g+5bZDs~sKw vqZX{v-wN2j-~T@!_w0Y{DE~h;=1g&-$bfBDvc2&f@K5WWj#{~j<;(vMaj^_{ literal 0 HcmV?d00001 diff --git a/security/xpm/figures/integrity_check.png b/security/xpm/figures/integrity_check.png new file mode 100644 index 0000000000000000000000000000000000000000..19af000e028f98e7e8b9d82f4e3387beed3dd196 GIT binary patch literal 49464 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 rfyAF)i|$zD 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 literal 0 HcmV?d00001 -- Gitee From 369a2bc09ac40feff6e3db30f1b80d461b69e8ba Mon Sep 17 00:00:00 2001 From: zhushengle Date: Wed, 26 Apr 2023 19:27:09 +0800 Subject: [PATCH 24/35] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dinode=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E8=A2=AB=E9=94=80=E6=AF=81=E6=97=B6=EF=BC=8C=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=9C=AA=E6=B8=85=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.修复编码规范问题 2.修复inode节点被销毁时,缓存未清的问题 Signed-off-by: zhushengle --- drivers/misc/lkdtm/Makefile | 1 + drivers/misc/lkdtm/core.c | 6 + drivers/misc/lkdtm/lkdtm.h | 6 + drivers/misc/lkdtm/xpm.c | 78 +++ fs/inode.c | 2 + include/linux/xpm.h | 10 + security/xpm/core/xpm_api.c | 5 + .../xpm/validator/elf_code_segment_info.c | 457 +++--------------- security/xpm/validator/exec_signature_info.c | 435 ++++++++++++++--- security/xpm/validator/exec_signature_info.h | 10 +- 10 files changed, 538 insertions(+), 472 deletions(-) create mode 100644 drivers/misc/lkdtm/xpm.c diff --git a/drivers/misc/lkdtm/Makefile b/drivers/misc/lkdtm/Makefile index 0d768a13e2bb..db60ec84d94b 100644 --- a/drivers/misc/lkdtm/Makefile +++ b/drivers/misc/lkdtm/Makefile @@ -10,6 +10,7 @@ lkdtm-$(CONFIG_LKDTM) += rodata_objcopy.o lkdtm-$(CONFIG_LKDTM) += usercopy.o lkdtm-$(CONFIG_LKDTM) += stackleak.o lkdtm-$(CONFIG_LKDTM) += cfi.o +lkdtm-$(CONFIG_LKDTM) += xpm.o KASAN_SANITIZE_stackleak.o := n KCOV_INSTRUMENT_rodata.o := n diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c index 32b3d77368e3..740c9ecde734 100644 --- a/drivers/misc/lkdtm/core.c +++ b/drivers/misc/lkdtm/core.c @@ -174,6 +174,12 @@ static const struct crashtype crashtypes[] = { CRASHTYPE(STACKLEAK_ERASING), CRASHTYPE(CFI_FORWARD_PROTO), CRASHTYPE(DOUBLE_FAULT), +#ifdef CONFIG_XPM_DEBUG + CRASHTYPE(XPM_ELF_CODE_SEGMENT), + CRASHTYPE(XPM_ELF_CODE_SEGMENT_CACHE_SIZE), + CRASHTYPE(XPM_ELF_CODE_SEGMENT_CACHE_DESTROY), + CRASHTYPE(XPM_ELF_CODE_SEGMENT_CACHE_CLEAR), +#endif }; diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h index 6dec4c9b442f..c277bdbd4d0e 100644 --- a/drivers/misc/lkdtm/lkdtm.h +++ b/drivers/misc/lkdtm/lkdtm.h @@ -102,4 +102,10 @@ void lkdtm_STACKLEAK_ERASING(void); /* cfi.c */ void lkdtm_CFI_FORWARD_PROTO(void); +#ifdef CONFIG_XPM_DEBUG +void lkdtm_XPM_ELF_CODE_SEGMENT(void); +void lkdtm_XPM_ELF_CODE_SEGMENT_CACHE_SIZE(void); +void lkdtm_XPM_ELF_CODE_SEGMENT_CACHE_DESTROY(void); +void lkdtm_XPM_ELF_CODE_SEGMENT_CACHE_CLEAR(void); +#endif #endif diff --git a/drivers/misc/lkdtm/xpm.c b/drivers/misc/lkdtm/xpm.c new file mode 100644 index 000000000000..238299f191d7 --- /dev/null +++ b/drivers/misc/lkdtm/xpm.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ +#include "lkdtm.h" +#include +#include +#include + +#ifdef CONFIG_XPM_DEBUG +struct exec_file_signature_info; +int get_exec_file_signature_info(struct file*, bool, struct exec_file_signature_info **); +int put_exec_file_signature_info(struct exec_file_signature_info *); +int test_destroy_elf_code_segment_info_cache(void); +void test_rm_elf_code_segment_info_cache(void); +void test_print_elf_code_segment_info(const char *file_path, const struct exec_file_signature_info *file_info); +int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); +void test_get_elf_code_segment_info_cache_size(void); + +void lkdtm_XPM_ELF_CODE_SEGMENT(void) +{ + int ret; + struct exec_file_signature_info *file_info = NULL; + struct exec_file_signature_info *file_info_cache = NULL; + char file_path[PATH_MAX] = "/system/bin/dmesg"; + struct file *test_file; + + test_file = filp_open(file_path, O_RDONLY, 0); + if (test_file == NULL) { + pr_info("[%s:%d] filp_open failed\n", __func__, __LINE__); + return; + } + + ret = get_exec_file_signature_info(test_file, false, &file_info); + if (ret < 0) { + filp_close(test_file, 0); + pr_info("[%s:%d] get_exec_file_signature_info failed\n", __func__, __LINE__); + return; + } + + ret = put_exec_file_signature_info(file_info); + if (ret < 0) { + filp_close(test_file, 0); + pr_info("[%s:%d] put_exec_file_signature_info failed\n", __func__, __LINE__); + return; + } + + (void)get_exec_file_signature_info(test_file, false, &file_info_cache); + (void)put_exec_file_signature_info(file_info_cache); + + if (file_info_cache != file_info) + pr_info("[%s:%d] get cache failed!\n", __func__, __LINE__); + + ret = test_delete_elf_code_segment_info(file_info); + if (ret < 0) { + filp_close(test_file, 0); + pr_info("[%s:%d] delete_elf_code_segment_info failed\n", __func__, __LINE__); + return; + } + test_print_elf_code_segment_info(file_path, file_info); + filp_close(test_file, 0); +} + +void lkdtm_XPM_ELF_CODE_SEGMENT_CACHE_DESTROY(void) +{ + test_destroy_elf_code_segment_info_cache(); +} + +void lkdtm_XPM_ELF_CODE_SEGMENT_CACHE_CLEAR(void) +{ + test_rm_elf_code_segment_info_cache(); +} + +void lkdtm_XPM_ELF_CODE_SEGMENT_CACHE_SIZE(void) +{ + test_get_elf_code_segment_info_cache_size(); +} +#endif diff --git a/fs/inode.c b/fs/inode.c index 9f49e0bdc2f7..179a13422009 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "internal.h" @@ -258,6 +259,7 @@ void __destroy_inode(struct inode *inode) security_inode_free(inode); fsnotify_inode_delete(inode); locks_free_lock_context(inode); + xpm_delete_cache_node(inode); if (!inode->i_nlink) { WARN_ON(atomic_long_read(&inode->i_sb->s_remove_count) == 0); atomic_long_dec(&inode->i_sb->s_remove_count); diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 86601345051e..cc5048d78355 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -64,6 +64,11 @@ extern int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot); */ extern int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot); +/** + * When inodes are destroyed, the corresponding cache must be destroyed + */ +extern void xpm_delete_cache_node(struct inode *file_node); + /** * judge vma is anonymous or not? */ @@ -112,6 +117,11 @@ static inline int xpm_check_prot(struct vm_area_struct *vma, return 0; } +static void xpm_delete_cache_node(struct inode *file_node) +{ + return; +} + static inline int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) { diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c index b9628bc3b724..b6b565f53cba 100755 --- a/security/xpm/core/xpm_api.c +++ b/security/xpm/core/xpm_api.c @@ -136,6 +136,11 @@ int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) return ret; } +void xpm_delete_cache_node(struct inode *file_node) +{ + delete_exec_file_signature_info(file_node); +} + int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) { bool is_anon = xpm_is_anonymous_vma(vma); diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index f8febdea0068..ef7bf9c63800 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -6,10 +6,14 @@ #include #include #include -#include -#include #include "exec_signature_info.h" +#if ELF_EXEC_PAGESIZE > PAGE_SIZE +#define ELF_MIN_ALIGN ELF_EXEC_PAGESIZE +#else +#define ELF_MIN_ALIGN PAGE_SIZE +#endif + struct elf_info { struct elfhdr elf_ehdr; uint16_t type; @@ -18,130 +22,15 @@ struct elf_info { uintptr_t e_phoff; }; -#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 = 0; -static DEFINE_RWLOCK(fs_verity_tree_lock); -static struct rb_root fs_verity_tree = RB_ROOT; -static int fs_verity_node_count = 0; - -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 read_elf_info(struct file *file, void *buffer, size_t read_size, loff_t pos) { - size_t len = kernel_read(file, buffer, read_size, &pos); - if (unlikely(len != read_size)) { - return -EIO; - } - return 0; -} + size_t len; -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; - i++; - } - return i; -} - -static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) -{ - struct exec_file_signature_info *code_segment_info; - int i = 0; - int count = VERITY_NODE_CACHE_RECYCLE_NUM; - uintptr_t *code_segments = kzalloc(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); -} - -static void rm_code_segment_info(void) -{ - if ((dm_verity_node_count + fs_verity_node_count) < VERITY_NODE_CACHE_LIMITS) { - return; - } - - 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; - } + len = kernel_read(file, buffer, read_size, &pos); + if (unlikely(len != read_size)) + return -EIO; - write_lock(&fs_verity_tree_lock); - clear_code_segment_info_cache(&fs_verity_tree, &fs_verity_node_count); - write_unlock(&fs_verity_tree_lock); + return 0; } static uint64_t elf64_to_cpu(const struct elfhdr *ehdr, uint64_t value) @@ -185,9 +74,9 @@ static int get_elf32_code_segment_count(struct elf32_phdr *elf_phdr, for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); - if (!(p_flags & PF_X)) { + if (!(p_flags & PF_X)) continue; - } + count++; } return count; @@ -207,16 +96,16 @@ static int get_elf32_code_segment(struct elf32_phdr *elf_phdr, struct elf_info * for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); - if (!(p_flags & PF_X)) { + if (!(p_flags & PF_X)) continue; - } + p_offset = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_offset); p_filesz = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_filesz); p_addr = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_paddr); p_memsz = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_memsz); - if (p_offset + p_filesz < p_offset || p_addr + p_memsz < p_addr) { + 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++; @@ -234,9 +123,9 @@ static int get_elf64_code_segment_count(struct elf64_phdr *elf_phdr, struct elf_ for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); - if (!(p_flags & PF_X)) { + if (!(p_flags & PF_X)) continue; - } + count++; } return count; @@ -256,16 +145,16 @@ static int get_elf64_code_segment(struct elf64_phdr *elf_phdr, struct elf_info * for (i = 0; i < elf_info->e_phnum; i++) { phdr_info = elf_phdr + i; p_flags = elf32_to_cpu(&elf_info->elf_ehdr, phdr_info->p_flags); - if (!(p_flags & PF_X)) { + if (!(p_flags & PF_X)) continue; - } + p_offset = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_offset); p_filesz = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_filesz); p_addr = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_paddr); p_memsz = elf64_to_cpu(&elf_info->elf_ehdr, phdr_info->p_memsz); - if (p_offset + p_filesz < p_offset || p_addr + p_memsz < p_addr) { + 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++; @@ -283,62 +172,59 @@ static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_i uint64_t e64_phsize; uint16_t type; uint16_t e_ehsize; - struct elfhdr *elf_ehdr = &elf_info->elf_ehdr; + int ret; - int ret = read_elf_info(file, (void *)elf_ehdr, sizeof(struct elfhdr), 0); - if (ret < 0) { + 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) { + if (memcmp(elf_ehdr->e_ident, ELFMAG, SELFMAG) != 0) return -ENOEXEC; - } type = elf16_to_cpu(elf_ehdr, elf_ehdr->e_type); - if (type != ET_EXEC && type != ET_DYN) { + 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_to_cpu(elf_ehdr, elf32_ehdr->e_ehsize); - if (e_ehsize != sizeof(struct elf32_hdr)) { + if (e_ehsize != sizeof(struct elf32_hdr)) return -ENOEXEC; - } + elf_info->e_phnum = elf16_to_cpu(elf_ehdr, elf32_ehdr->e_phnum); - if (elf_info->e_phnum == 0) { - return -ENOEXEC; - } e32_phsize = sizeof(struct elf32_phdr) * elf_info->e_phnum; + if (e32_phsize == 0 || e32_phsize > 65536 || e32_phsize > ELF_MIN_ALIGN) + return -ENOEXEC; + e32_phoff = elf32_to_cpu(elf_ehdr, elf32_ehdr->e_phoff); - if (e32_phoff > 0 && e32_phoff + e32_phsize < e32_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_to_cpu(elf_ehdr, elf64_ehdr->e_ehsize); - if (e_ehsize != sizeof(struct elf64_hdr)) { + if (e_ehsize != sizeof(struct elf64_hdr)) return -ENOEXEC; - } + elf_info->e_phnum = elf16_to_cpu(elf_ehdr, elf64_ehdr->e_phnum); - if (elf_info->e_phnum == 0) { - return -ENOEXEC; - } e64_phsize = sizeof(struct elf64_phdr) * elf_info->e_phnum; + if (e64_phsize == 0 || e64_phsize > 65536 || e64_phsize > ELF_MIN_ALIGN) + return -ENOEXEC; + e64_phoff = elf64_to_cpu(elf_ehdr, elf64_ehdr->e_phoff); - if (e64_phoff > 0 && e64_phoff + e64_phsize < e64_phoff) { + if (e64_phoff + e64_phsize < e64_phoff) return -ENOEXEC; - } + elf_info->e_phsize = e64_phsize; elf_info->e_phoff = e64_phoff; - } else { + } else return -ENOEXEC; - } + return 0; } @@ -350,27 +236,26 @@ static int find_elf_code_segment_info(const char *phdr_info, struct elf_info *el struct exec_file_signature_info *exec_file_info; int segment_count; - if (elf_info->type == ELFCLASS32) { + if (elf_info->type == ELFCLASS32) segment_count = get_elf32_code_segment_count((struct elf32_phdr *)phdr_info, elf_info); - } else { + else segment_count = get_elf64_code_segment_count((struct elf64_phdr *)phdr_info, elf_info); - } - if (segment_count == 0) { + + if (segment_count == 0) return -ENOEXEC; - } size = sizeof(struct exec_file_signature_info) + segment_count * sizeof(struct exec_segment_info); exec_file_info = (struct exec_file_signature_info *)kzalloc(size, GFP_KERNEL); - if (exec_file_info == NULL) { + 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) { + if (elf_info->type == ELFCLASS32) ret = get_elf32_code_segment((struct elf32_phdr *)phdr_info, elf_info, exec_file_info); - } else { + 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; @@ -379,20 +264,20 @@ static int find_elf_code_segment_info(const char *phdr_info, struct elf_info *el return 0; } -static int parse_elf_code_segment_info(struct file *file, +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 = elf_check_and_get_code_segment_offset(file, &elf_info); - if (ret < 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) { + 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) { @@ -404,227 +289,3 @@ static int parse_elf_code_segment_info(struct file *file, kfree(phdr_info); return ret; } - -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 = (struct exec_file_signature_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; - } - } - - 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 put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) -{ - if ((code_segment_info == NULL) || - !exec_file_signature_is_verity(code_segment_info)) { - return -EINVAL; - } - - if (atomic_sub_return(1, &code_segment_info->reference) <= 0 && - exec_file_signature_is_delete(code_segment_info)) { - kfree(code_segment_info); - } - return 0; -} - -int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) -{ - struct rb_root *root; - rwlock_t *verity_lock; - int *node_count; - struct exec_file_signature_info *segment_info = NULL; - - if (code_segment_info == NULL) { - return -EINVAL; - } - - if (exec_file_signature_is_dm_verity(code_segment_info)) { - root = &dm_verity_tree; - verity_lock = &dm_verity_tree_lock; - node_count = &dm_verity_node_count; - } else if (exec_file_signature_is_fs_verity(code_segment_info)) { - verity_lock = &fs_verity_tree_lock; - root = &fs_verity_tree; - node_count = &fs_verity_node_count; - } else { - return -EINVAL; - } - - write_lock(verity_lock); - segment_info = rb_search_node(root, code_segment_info->inode); - if (segment_info == NULL) { - write_unlock(verity_lock); - return -EINVAL; - } - rb_erase_node(root, node_count, code_segment_info); - write_unlock(verity_lock); - kfree(code_segment_info); - return 0; -} - -static int destroy_elf_code_segment_tree(struct rb_root *root, int *node_count) -{ - struct rb_node *node; - struct exec_file_signature_info *file_node; - - do { - node = rb_first(root); - if (node == NULL) { - return 0; - } - file_node = rb_entry(node, struct exec_file_signature_info, rb_node); - if (atomic_read(&file_node->reference) > 0) { - return -EPERM; - } - rb_erase_node(root, node_count, file_node); - } while (1); - return 0; -} - -int test_destroy_elf_code_segment_info_cache(void) -{ - int ret; - int count = 0; - - write_lock(&dm_verity_tree_lock); - count += dm_verity_node_count; - ret = destroy_elf_code_segment_tree(&dm_verity_tree, &dm_verity_node_count); - write_unlock(&dm_verity_tree_lock); - if (ret < 0) { - return ret; - } - - write_lock(&fs_verity_tree_lock); - count += fs_verity_node_count; - ret = destroy_elf_code_segment_tree(&fs_verity_tree, &fs_verity_node_count); - write_unlock(&fs_verity_tree_lock); - if (ret < 0) { - return ret; - } - return count; -} - -void test_rm_elf_code_segment_info_cache(void) -{ - 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 size_t 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; -} - -void test_get_elf_code_segment_info_cache_size(void) -{ - size_t cache_size = 0; - int count = 0; - - read_lock(&dm_verity_tree_lock); - cache_size += 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 += 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); -} - -void test_print_elf_code_segment_info(const char *file_path, - const struct exec_file_signature_info *file_info) -{ - int i; - for (i = 0; i < file_info->code_segment_count; i++) { - pr_info("%s -> offset: 0x%llx size: 0x%lx\n", - file_path, file_info->code_segments->file_offset, file_info->code_segments->size); - } -} diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index f9ac67a3470f..e616e62a029b 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -3,102 +3,228 @@ * Copyright (c) 2023 Huawei Device Co., Ltd. */ #include -#include #include #include -#include +#include +#include #include -#include -#include "../fs/mount.h" #include "exec_signature_info.h" -#ifdef CONFIG_DM_VERITY -#define HVB_CMDLINE_VB_STATE "ohos.boot.hvb.enable" -static bool dm_verity_enable = false; +#define VERITY_NODE_CACHE_LIMITS 10000 +#define VERITY_NODE_CACHE_RECYCLE_NUM 200 -static int hvb_boot_param_cb(char *param, char *val, - const char *unused, void *arg) +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; + +#ifdef CONFIG_FS_VERITY +static bool is_fs_verity(struct file *file) { - if (param == NULL || val == NULL) { - return 0; - } - if (strcmp(param, HVB_CMDLINE_VB_STATE) != 0) { - return 0; - } - if (strcmp(val, "true") == 0 || strcmp(val, "TRUE") == 0) { - dm_verity_enable = true; - } - return 0; + struct inode *file_node; + + file_inode = file_inode(file); + if (file_node == NULL) + return false; + + if (file_node->i_verity_info == NULL) + return false; + + return true; +} +#endif + +static int check_exec_file_is_verity(struct file *file) +{ +#ifdef CONFIG_FS_VERITY + if (is_fs_verity(file)) + return FILE_SIGNATURE_FS_VERITY; +#endif + + return FILE_SIGNATURE_DM_VERITY; } -static bool dm_verity_is_enable(void) +static struct exec_file_signature_info *rb_search_node(struct rb_root *root, uintptr_t file_inode) { - static bool dm_verity_check = false; - char *cmdline; + struct rb_node *node = root->rb_node; + struct exec_file_signature_info *file_node; - if (!dm_verity_check && !dm_verity_enable) { - cmdline = kstrdup(saved_command_line, GFP_KERNEL); - if (cmdline == NULL) { - return false; + 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; } - parse_args("hvb.enable params", cmdline, NULL, - 0, 0, 0, NULL, &hvb_boot_param_cb); - kfree(cmdline); } - dm_verity_check = true; - return dm_verity_enable; + return NULL; } -static bool is_dm_verity(struct file *file) +static struct exec_file_signature_info *rb_add_node(struct rb_root *root, int *node_count, + struct exec_file_signature_info *node) { - struct mount *mnt; - struct mapped_device *device; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + struct exec_file_signature_info *file; - if (!dm_verity_is_enable()) { - return false; - } - if (file->f_path.mnt == NULL) { - return false; + 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; + } } - mnt = container_of(test_file->f_path.mnt, struct mount, mnt); - device = dm_get_md(dm_get_dev_t(mnt->mnt_devname)); - if (device == NULL) { - return false; + + 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; } - dm_put(device); - return true; + return i; } -#endif -#ifdef CONFIG_FS_VERITY -static bool is_fs_verity(struct file *file) +static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) { - struct inode *file_node = file_inode(file); - if (file_node == NULL) { - return false; + struct exec_file_signature_info *code_segment_info; + uintptr_t *code_segments; + int i = 0; + int count = VERITY_NODE_CACHE_RECYCLE_NUM; + + code_segments = kzalloc(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); +} - if (file_node->i_verity_info == NULL) { - return false; +static void rm_code_segment_info(void) +{ + if (dm_verity_node_count + fs_verity_node_count < VERITY_NODE_CACHE_LIMITS) + return; + + 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; } - return true; + + write_lock(&fs_verity_tree_lock); + clear_code_segment_info_cache(&fs_verity_tree, &fs_verity_node_count); + write_unlock(&fs_verity_tree_lock); } -#endif -static int check_exec_file_is_verity(struct file *file) +static int get_elf_code_segment_info(struct file *file, bool is_exec, int type, + struct exec_file_signature_info **code_segment_info) { - /* 测试demo */ -#ifdef CONFIG_DM_VERITY - if (is_dm_verity(file)) { - return FILE_SIGNATURE_DM_VERITY; + 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; } -#endif -#ifdef CONFIG_FS_VERITY - if (is_fs_verity(file)) { - return FILE_SIGNATURE_FS_VERITY; + + 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; } -#endif - return FILE_SIGNATURE_DM_VERITY; + +need_parse: + rm_code_segment_info(); + + if (!is_exec) { + new_info = (struct exec_file_signature_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; + } + + 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, @@ -106,9 +232,8 @@ int get_exec_file_signature_info(struct file *file, bool is_exec, { int type; - if (file == NULL || info_ptr == NULL) { + 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); @@ -116,5 +241,177 @@ int get_exec_file_signature_info(struct file *file, bool is_exec, int put_exec_file_signature_info(struct exec_file_signature_info *exec_info) { - return put_elf_code_segment_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); +} + +#ifdef CONFIG_XPM_DEBUG +int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) +{ + struct rb_root *root; + rwlock_t *verity_lock; + int *node_count; + struct exec_file_signature_info *segment_info = NULL; + + if (code_segment_info == NULL) + return -EINVAL; + + if (exec_file_signature_is_dm_verity(code_segment_info)) { + root = &dm_verity_tree; + verity_lock = &dm_verity_tree_lock; + node_count = &dm_verity_node_count; + } else if (exec_file_signature_is_fs_verity(code_segment_info)) { + verity_lock = &fs_verity_tree_lock; + root = &fs_verity_tree; + node_count = &fs_verity_node_count; + } else { + return -EINVAL; + } + + write_lock(verity_lock); + segment_info = rb_search_node(root, code_segment_info->inode); + if (segment_info == NULL) { + write_unlock(verity_lock); + return -EINVAL; + } + rb_erase_node(root, node_count, code_segment_info); + write_unlock(verity_lock); + kfree(code_segment_info); + return 0; +} + +static int test_destroy_elf_code_segment_tree(struct rb_root *root, int *node_count) +{ + struct rb_node *node; + struct exec_file_signature_info *file_node; + + do { + node = rb_first(root); + if (node == NULL) + return 0; + file_node = rb_entry(node, struct exec_file_signature_info, rb_node); + if (atomic_read(&file_node->reference) > 0) + return -EPERM; + rb_erase_node(root, node_count, file_node); + } while (1); + return 0; } + +int test_destroy_elf_code_segment_info_cache(void) +{ + int ret; + int count = 0; + + write_lock(&dm_verity_tree_lock); + count += dm_verity_node_count; + ret = test_destroy_elf_code_segment_tree(&dm_verity_tree, &dm_verity_node_count); + write_unlock(&dm_verity_tree_lock); + if (ret < 0) + return ret; + + write_lock(&fs_verity_tree_lock); + count += fs_verity_node_count; + ret = test_destroy_elf_code_segment_tree(&fs_verity_tree, &fs_verity_node_count); + write_unlock(&fs_verity_tree_lock); + if (ret < 0) + return ret; + return count; +} + +void test_rm_elf_code_segment_info_cache(void) +{ + 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 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; +} + +void test_get_elf_code_segment_info_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); +} + +void test_print_elf_code_segment_info(const char *file_path, + const struct exec_file_signature_info *file_info) +{ + int i; + + for (i = 0; i < file_info->code_segment_count; i++) { + pr_info("%s -> offset: 0x%llx size: 0x%lx\n", + file_path, file_info->code_segments->file_offset, file_info->code_segments->size); + } +} +#endif diff --git a/security/xpm/validator/exec_signature_info.h b/security/xpm/validator/exec_signature_info.h index 5f74aeb40d26..d6545073a109 100644 --- a/security/xpm/validator/exec_signature_info.h +++ b/security/xpm/validator/exec_signature_info.h @@ -16,7 +16,7 @@ struct exec_segment_info { }; #define FILE_SIGNATURE_INVALID 0 -#define FILE_SIGNATURE_FS_VERITY 1 +#define FILE_SIGNATURE_FS_VERITY 1 #define FILE_SIGNATURE_DM_VERITY 2 #define FILE_SIGNATURE_MASK 0x0000000F #define FILE_SIGNATURE_DELETE 0x80000000 @@ -24,9 +24,9 @@ struct exec_segment_info { struct exec_file_signature_info { struct rb_node rb_node; atomic_t reference; - unsigned type; + unsigned int type; uintptr_t inode; - unsigned code_segment_count; + unsigned int code_segment_count; struct exec_segment_info *code_segments; }; @@ -51,8 +51,8 @@ static inline bool exec_file_signature_is_delete(const struct exec_file_signatur return !!(signature_info->type & FILE_SIGNATURE_DELETE); } -int put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); -int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_signature_info **code_segment_info); +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 -- Gitee From a5772dd11d186380beefeb3662a886e68de1ba07 Mon Sep 17 00:00:00 2001 From: limerence Date: Fri, 28 Apr 2023 09:58:11 +0800 Subject: [PATCH 25/35] add hck hooks Signed-off-by: limerence --- drivers/hck/vendor_hooks.c | 1 + fs/proc/base.c | 55 ++- include/linux/hck/lite_hck_xpm.h | 30 ++ include/linux/lsm_hook_defs.h | 2 +- include/linux/mm.h | 2 +- include/linux/mm_types.h | 2 +- include/linux/page-flags.h | 6 +- include/linux/security.h | 2 +- include/linux/xpm.h | 116 ++---- include/trace/events/mmflags.h | 2 +- mm/ksm.c | 2 +- mm/mmap.c | 28 +- security/Makefile | 4 +- security/security.c | 2 +- security/selinux/hooks.c | 73 ---- security/xpm/Kconfig | 12 +- security/xpm/Makefile | 31 +- security/xpm/core/xpm_debugfs.c | 27 +- security/xpm/core/xpm_hck.c | 337 ++++++++++++++++ security/xpm/core/xpm_integrity.c | 110 ------ security/xpm/core/xpm_misc.c | 6 +- .../xpm/core/{xpm_main.c => xpm_module.c} | 16 +- security/xpm/core/xpm_report.c | 342 ++++++++++------- security/xpm/core/xpm_report.h | 169 -------- security/xpm/include/exec_signature_info.h | 59 +++ security/xpm/{core => include}/xpm_debugfs.h | 16 +- security/xpm/include/xpm_hck.h | 16 + security/xpm/include/xpm_log.h | 29 ++ security/xpm/{core => include}/xpm_misc.h | 2 +- security/xpm/include/xpm_report.h | 89 +++++ .../xpm/validator/elf_code_segment_info.c | 360 +++++++++++++++++- security/xpm/validator/exec_signature_info.c | 69 +++- 32 files changed, 1327 insertions(+), 690 deletions(-) create mode 100644 include/linux/hck/lite_hck_xpm.h create mode 100644 security/xpm/core/xpm_hck.c delete mode 100644 security/xpm/core/xpm_integrity.c rename security/xpm/core/{xpm_main.c => xpm_module.c} (79%) delete mode 100644 security/xpm/core/xpm_report.h create mode 100644 security/xpm/include/exec_signature_info.h rename security/xpm/{core => include}/xpm_debugfs.h (46%) create mode 100644 security/xpm/include/xpm_hck.h create mode 100644 security/xpm/include/xpm_log.h rename security/xpm/{core => include}/xpm_misc.h (92%) create mode 100644 security/xpm/include/xpm_report.h diff --git a/drivers/hck/vendor_hooks.c b/drivers/hck/vendor_hooks.c index d3e3e2befd9f..4fd1a37c61e7 100644 --- a/drivers/hck/vendor_hooks.c +++ b/drivers/hck/vendor_hooks.c @@ -9,3 +9,4 @@ #define CREATE_LITE_VENDOR_HOOK /* add your lite vendor hook header file here */ #include +#include diff --git a/fs/proc/base.c b/fs/proc/base.c index 46f0208e0312..93e62a07615f 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -100,7 +100,6 @@ #include #include #include -#include #include #include "internal.h" #include "fd.h" @@ -1577,6 +1576,54 @@ static const struct file_operations proc_pid_sched_group_id_operations = { }; #endif /* CONFIG_SCHED_RTG_DEBUG */ +#ifdef CONFIG_SECURITY_XPM +#define XPM_REGION_LEN 48 +static int xpm_region_open(struct inode *inode, struct file *file) +{ + struct mm_struct *mm = proc_mem_open(inode, PTRACE_MODE_READ); + + if (IS_ERR(mm)) + return PTR_ERR(mm); + + file->private_data = mm; + return 0; +} + +static ssize_t xpm_region_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct mm_struct *mm = file->private_data; + char xpm_region[XPM_REGION_LEN] = {0}; + size_t len; + + if (!mm) + return 0; + + len = snprintf(xpm_region, XPM_REGION_LEN - 1, "%lx-%lx", + mm->xpm_region.addr_start, + mm->xpm_region.addr_end); + + return simple_read_from_buffer(buf, count, pos, xpm_region, len); +} + +static int xpm_region_release(struct inode *inode, struct file *file) +{ + struct mm_struct *mm = file->private_data; + + if (mm) + mmdrop(mm); + + return 0; +} + +static const struct file_operations proc_xpm_region_operations = { + .open = xpm_region_open, + .read = xpm_region_read, + .llseek = generic_file_llseek, + .release = xpm_region_release, +}; +#endif /* CONFIG_SECURITY_XPM */ + #ifdef CONFIG_SCHED_AUTOGROUP /* * Print out autogroup related information: @@ -3460,8 +3507,8 @@ static const struct pid_entry tgid_base_stuff[] = { #ifdef CONFIG_SCHED_RTG_DEBUG REG("sched_group_id", S_IRUGO|S_IWUGO, proc_pid_sched_group_id_operations), #endif -#ifdef CONFIG_XPM - REG("xpm_region", S_IRUGO, proc_xpm_region_operations), +#ifdef CONFIG_SECURITY_XPM + REG("xpm_region", S_IRUSR|S_IRGRP, proc_xpm_region_operations), #endif }; @@ -3798,7 +3845,7 @@ static const struct pid_entry tid_base_stuff[] = { #ifdef CONFIG_SCHED_RTG_DEBUG REG("sched_group_id", S_IRUGO|S_IWUGO, proc_pid_sched_group_id_operations), #endif -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM REG("xpm_region", S_IRUGO, proc_xpm_region_operations), #endif }; diff --git a/include/linux/hck/lite_hck_xpm.h b/include/linux/hck/lite_hck_xpm.h new file mode 100644 index 000000000000..ca66e62152ae --- /dev/null +++ b/include/linux/hck/lite_hck_xpm.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + */ + +#ifndef _LITE_HCK_XPM_H +#define _LITE_HCK_XPM_H + +#include + +#ifndef CONFIG_HCK +#define CALL_HCK_LITE_HOOK(name, args...) +#define REGISTER_HCK_LITE_HOOK(name, probe) +#define REGISTER_HCK_LITE_DATA_HOOK(name, probe, data) +#else + +DECLARE_HCK_LITE_HOOK(xpm_region_outer_lhck, + TP_PROTO(unsigned long addr_start, unsigned long addr_end, + unsigned long flags, bool *ret), + TP_ARGS(addr_start, addr_end, flags, ret)); + +DECLARE_HCK_LITE_HOOK(xpm_get_unmapped_area_lhck, + TP_PROTO(unsigned long addr, unsigned long len, unsigned long map_flags, + unsigned long unmapped_flags, unsigned long *ret), + TP_ARGS(addr, len, map_flags, unmapped_flags, ret)); + + +#endif /* CONFIG_HCK */ + +#endif /* _LITE_HCK_XPM_H */ diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index dade0ebf8295..d7c61d382c4a 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -161,7 +161,7 @@ LSM_HOOK(int, 0, file_ioctl, struct file *file, unsigned int cmd, LSM_HOOK(int, 0, mmap_addr, unsigned long addr) LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM LSM_HOOK(int, 0, mmap_region, struct vm_area_struct *vma) #endif LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma, diff --git a/include/linux/mm.h b/include/linux/mm.h index bb42a45eac06..9ed1be47c8cb 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -335,7 +335,7 @@ extern unsigned int kobjsize(const void *objp); #define VM_USEREXPTE 0 #endif /* CONFIG_MEM_PURGEABLE */ -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM #define VM_XPM VM_HIGH_ARCH_7 #else /* CONFIG_MEM_PURGEABLE */ #define VM_XPM VM_NONE diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index ae8607b93904..3eb93c487004 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -605,7 +605,7 @@ struct mm_struct { u32 pasid; #endif -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM struct xpm_region xpm_region; #endif } __randomize_layout; diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h index 0e5735861ec7..7b3212b93d3e 100644 --- a/include/linux/page-flags.h +++ b/include/linux/page-flags.h @@ -146,7 +146,7 @@ enum pageflags { #ifdef CONFIG_MEM_PURGEABLE PG_purgeable, #endif -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM PG_xpm_readonly, PG_xpm_writetainted, #endif @@ -354,7 +354,7 @@ __PAGEFLAG(Slab, slab, PF_NO_TAIL) __PAGEFLAG(SlobFree, slob_free, PF_NO_TAIL) PAGEFLAG(Checked, checked, PF_NO_COMPOUND) /* Used by some filesystems */ -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM PAGEFLAG(XPMReadonly, xpm_readonly, PF_HEAD) PAGEFLAG(XPMWritetainted, xpm_writetainted, PF_HEAD) #else @@ -855,7 +855,7 @@ static inline void ClearPageSlabPfmemalloc(struct page *page) * Flags checked when a page is freed. Pages being freed should not have * these flags set. It they are, there is a problem. */ -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM #define __XPM_PAGE_FLAGS (1UL << PG_xpm_readonly | 1UL << PG_xpm_writetainted) #else #define __XPM_PAGE_FLAGS 0 diff --git a/include/linux/security.h b/include/linux/security.h index 4fa856ad7d7d..f940ab809ad1 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2002,7 +2002,7 @@ static inline int security_perf_event_write(struct perf_event *event) #endif /* CONFIG_SECURITY */ #endif /* CONFIG_PERF_EVENTS */ -#if IS_ENABLED(CONFIG_SECURITY) && IS_ENABLED(CONFIG_XPM) +#if IS_ENABLED(CONFIG_SECURITY) && IS_ENABLED(CONFIG_SECURITY_XPM) extern int security_mmap_region(struct vm_area_struct *vma); #else static inline int security_mmap_region(struct vm_area_struct *vma) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index cc5048d78355..4e9b93ba7dfd 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Huawei Device Co., Ltd. */ @@ -10,71 +10,43 @@ #include #include #include - -#ifdef CONFIG_XPM -/* xpm internal return values */ -#define XPM_SUCCESS 0 -#define XPM_FAULT (-1) -#define XPM_CHECK_FAILED (-2) - -#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_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 - -extern const struct file_operations proc_xpm_region_operations; - -/** - * get return value by xpm mode (permissive or enforce) - */ -extern int xpm_value(int rc); +#include /** * check whether input address range is out of the xpm region */ -extern bool xpm_is_region_outer(unsigned long addr_start, - unsigned long addr_end, unsigned long flags); +static inline bool xpm_region_outer_hook(unsigned long addr_start, + unsigned long addr_end, unsigned long flags) +{ + bool ret = true; -/** - * get unmapped area in xpm region - */ -extern unsigned long xpm_get_unmapped_area(unsigned long addr, unsigned long len, - unsigned long map_flags, unsigned long unmapped_flags); + CALL_HCK_LITE_HOOK(xpm_region_outer_lhck, addr_start, + addr_end, flags, &ret); + return ret; +} /** - * check whether mmap vma has a valid prot + * get unmapped area in xpm region */ -extern int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot); +static inline unsigned long xpm_get_unmapped_area_hook(unsigned long addr, + unsigned long len, unsigned long map_flags, + unsigned long unmapped_flags) +{ + unsigned long ret = 0; -/** - * check whether mmap vma has a valid signature - */ -extern int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot); + CALL_HCK_LITE_HOOK(xpm_get_unmapped_area_lhck, addr, len, + map_flags, unmapped_flags, &ret); + return ret; +} /** * When inodes are destroyed, the corresponding cache must be destroyed */ extern void xpm_delete_cache_node(struct inode *file_node); -/** - * judge vma is anonymous or not? - */ -extern bool xpm_is_anonymous_vma(struct vm_area_struct *vma); +#ifdef CONFIG_SECURITY_XPM +/* xpm internal return values */ -#define XPM_PRINT_COUNT 10000 /* * Check the confliction of a page's xpm flags, make sure a process will not map * any RO page into a writable vma or a WT page into a execuable/XPM memory region. @@ -88,51 +60,11 @@ void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, bool xpm_integrity_equal(struct page *page, struct page *kpage); #else - -#define xpm_log_info(fmt, args...) -#define xpm_log_error(fmt, args...) -#define xpm_log_debug(fmt, args...) - -static inline int xpm_value(int rc) -{ - return rc; -} - -static inline bool xpm_is_region_outer(unsigned long addr_start, - unsigned long addr_end, unsigned long flags) -{ - return true; -} - -static inline unsigned long xpm_get_unmapped_area(unsigned long addr, - unsigned long len, unsigned long map_flags, - unsigned long unmapped_flags) -{ - return 0; -} - -static inline int xpm_check_prot(struct vm_area_struct *vma, - unsigned long prot) -{ - return 0; -} - -static void xpm_delete_cache_node(struct inode *file_node) +static inline void xpm_delete_cache_node(struct inode *file_node) { return; } -static inline int xpm_check_signature(struct vm_area_struct *vma, - unsigned long prot) -{ - return 0; -} - -static inline bool xpm_is_anonymous_vma(struct vm_area_struct *vma) -{ - return false; -} - static inline bool xpm_integrity_equal(struct page *page, struct page *kpage) { @@ -146,7 +78,7 @@ static inline vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, } static inline vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, - unsigned int vflags, unsigned long addr, struct page *page); + unsigned int vflags, unsigned long addr, struct page *page) { return 0; } diff --git a/include/trace/events/mmflags.h b/include/trace/events/mmflags.h index d38e69ef8aad..d3a4a32c0807 100644 --- a/include/trace/events/mmflags.h +++ b/include/trace/events/mmflags.h @@ -61,7 +61,7 @@ #define IF_HAVE_PG_PURGEABLE(flag,string) #endif -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM #define IF_HAVE_PG_XPM_INTEGRITY(flag,string) ,{1UL << flag, string} #else #define IF_HAVE_PG_XPM_INTEGRITY(flag,string) diff --git a/mm/ksm.c b/mm/ksm.c index 9cbb71aee10b..375c4080d4ed 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -1213,7 +1213,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, if (!PageAnon(page)) goto out; - /* Check XPM flags */ + /* Check XPM flags */ if(!xpm_integrity_equal(page, kpage)) goto out; diff --git a/mm/mmap.c b/mm/mmap.c index 3abcd0dd8982..8e47a7d04497 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -1461,10 +1461,8 @@ unsigned long do_mmap(struct file *file, unsigned long addr, * that it represents a valid section of the address space. */ addr = get_unmapped_area(file, addr, len, pgoff, flags); - if (IS_ERR_VALUE(addr)) { - xpm_log_error("get_unmapped_area failed: 0x%lx", addr); + if (IS_ERR_VALUE(addr)) return addr; - } if (flags & MAP_FIXED_NOREPLACE) { struct vm_area_struct *vma = find_vma(mm, addr); @@ -1855,10 +1853,6 @@ unsigned long mmap_region(struct file *file, unsigned long addr, * as we may succeed this time. */ if (unlikely(vm_flags != vma->vm_flags && prev)) { - error = security_mmap_region(vma); - if (error) - goto unmap_and_free_vma; - merge = vma_merge(mm, prev, vma->vm_start, vma->vm_end, vma->vm_flags, NULL, vma->vm_file, vma->vm_pgoff, NULL, NULL_VM_UFFD_CTX, NULL); if (merge) { @@ -2010,7 +2004,7 @@ static unsigned long unmapped_area(struct vm_unmapped_area_info *info) return -ENOMEM; if ((gap_end >= low_limit && gap_end > gap_start && gap_end - gap_start >= length) && - (xpm_is_region_outer(gap_start, gap_end, info->flags))) + (xpm_region_outer_hook(gap_start, gap_end, info->flags))) goto found; /* Visit right subtree if it looks promising */ @@ -2079,10 +2073,8 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) return -ENOMEM; high_limit = gap_end - length; - if (info->low_limit > high_limit) { - xpm_log_error("low limit bigger than high_limit: [0x%x, 0x%x]", info->low_limit, high_limit); + if (info->low_limit > high_limit) return -ENOMEM; - } low_limit = info->low_limit + length; /* Check highest gap, which does not precede any rbtree node */ @@ -2117,7 +2109,7 @@ static unsigned long unmapped_area_topdown(struct vm_unmapped_area_info *info) return -ENOMEM; if ((gap_start <= high_limit && gap_end > gap_start && gap_end - gap_start >= length) && - (xpm_is_region_outer(gap_start, gap_end, info->flags))) + (xpm_region_outer_hook(gap_start, gap_end, info->flags))) goto found; /* Visit left subtree if it looks promising */ @@ -2208,7 +2200,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, if (len > mmap_end - mmap_min_addr) return -ENOMEM; - xpm_addr = xpm_get_unmapped_area(addr, len, flags, 0); + xpm_addr = xpm_get_unmapped_area_hook(addr, len, flags, 0); if (xpm_addr) return xpm_addr; @@ -2221,7 +2213,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr, if (mmap_end - len >= addr && addr >= mmap_min_addr && (!vma || addr + len <= vm_start_gap(vma)) && (!prev || addr >= vm_end_gap(prev)) && - (xpm_is_region_outer(addr, addr + len, 0))) + (xpm_region_outer_hook(addr, addr + len, 0))) return addr; } @@ -2255,7 +2247,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, if (len > mmap_end - mmap_min_addr) return -ENOMEM; - xpm_addr = xpm_get_unmapped_area(addr, len, flags, + xpm_addr = xpm_get_unmapped_area_hook(addr, len, flags, VM_UNMAPPED_AREA_TOPDOWN); if (xpm_addr) return xpm_addr; @@ -2270,7 +2262,7 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr, if (mmap_end - len >= addr && addr >= mmap_min_addr && (!vma || addr + len <= vm_start_gap(vma)) && (!prev || addr >= vm_end_gap(prev)) && - (xpm_is_region_outer(addr, addr + len, 0))) + (xpm_region_outer_hook(addr, addr + len, 0))) return addr; } @@ -2330,10 +2322,8 @@ get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, } addr = get_area(file, addr, len, pgoff, flags); - if (IS_ERR_VALUE(addr)) { - xpm_log_error("get_area failed: 0x%lx", addr); + if (IS_ERR_VALUE(addr)) return addr; - } if (addr > TASK_SIZE - len) return -ENOMEM; diff --git a/security/Makefile b/security/Makefile index edbffda9c54d..3f01136d7b1f 100644 --- a/security/Makefile +++ b/security/Makefile @@ -13,7 +13,7 @@ subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown subdir-$(CONFIG_BPF_LSM) += bpf -subdir-$(CONFIG_XPM) += xpm +subdir-$(CONFIG_SECURITY_XPM) += xpm # always enable default capabilities obj-y += commoncap.o @@ -33,7 +33,7 @@ obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ obj-$(CONFIG_CGROUPS) += device_cgroup.o obj-$(CONFIG_BPF_LSM) += bpf/ -obj-$(CONFIG_XPM) += xpm/ +obj-$(CONFIG_SECURITY_XPM) += xpm/ # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity diff --git a/security/security.c b/security/security.c index 52a92becfeca..6c5f9a7c6b59 100644 --- a/security/security.c +++ b/security/security.c @@ -2576,7 +2576,7 @@ int security_perf_event_write(struct perf_event *event) } #endif /* CONFIG_PERF_EVENTS */ -#ifdef CONFIG_XPM +#ifdef CONFIG_SECURITY_XPM int security_mmap_region(struct vm_area_struct *vma) { return call_int_hook(mmap_region, 0, vma); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 36500a2d400d..662f7b4a9516 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -92,7 +92,6 @@ #include #include #include -#include #include "avc.h" #include "objsec.h" @@ -6957,74 +6956,6 @@ static int selinux_perf_event_write(struct perf_event *event) } #endif -#ifdef CONFIG_XPM -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_prot_control(struct vm_area_struct *vma, unsigned long prot) -{ - int rc; - - rc = xpm_check_prot(vma, prot); - if (rc != XPM_CHECK_FAILED) - return xpm_value(rc); - - rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__ANON_EXECMEM); - return xpm_value(rc); -} - -static int xpm_signature_control(struct vm_area_struct *vma, unsigned long prot) -{ - int rc; - - rc = xpm_check_signature(vma, prot); - if (rc != XPM_CHECK_FAILED) - return xpm_value(rc); - - rc = xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_NO_SIGN); - - return xpm_value(rc); -} - -static int xpm_common_control(struct vm_area_struct *vma, unsigned long prot) -{ - int rc; - - rc = xpm_prot_control(vma, prot); - if (rc) - return rc; - - return xpm_signature_control(vma, prot); -} - -static int selinux_xpm_mmap_control(struct vm_area_struct *vma) -{ - return xpm_common_control(vma, vma->vm_flags); -} - -static int selinux_xpm_mprotect_control(struct vm_area_struct *vma, - unsigned long reqprot, unsigned long prot) -{ - if (checkreqprot_get(&selinux_state)) - prot = reqprot; - - return xpm_common_control(vma, prot); -} -#endif - /* * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order: * 1. any hooks that don't belong to (2.) or (3.) below, @@ -7310,10 +7241,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { #ifdef CONFIG_PERF_EVENTS LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), #endif -#ifdef CONFIG_XPM - LSM_HOOK_INIT(mmap_region, selinux_xpm_mmap_control), - LSM_HOOK_INIT(file_mprotect, selinux_xpm_mprotect_control), -#endif }; static __init int selinux_init(void) diff --git a/security/xpm/Kconfig b/security/xpm/Kconfig index a516aabb77ab..7a8dffd6ce39 100755 --- a/security/xpm/Kconfig +++ b/security/xpm/Kconfig @@ -4,10 +4,10 @@ # Config for the excutable permission manager # -menu "executable permission manager" +menu "Executable permission manager" -config XPM - bool "enables excutable permission manager feature" +config SECURITY_XPM + bool "Enables excutable permission manager feature" default n help The Executable Permission Manager(XPM) control process execution @@ -15,9 +15,9 @@ config XPM mmap and etc. It can control not to execute an illegal signature process. -config XPM_DEBUG - bool "excutable permission manager debug mode" - depends on XPM +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 diff --git a/security/xpm/Makefile b/security/xpm/Makefile index d5fffe0c26ab..20747e9dad60 100755 --- a/security/xpm/Makefile +++ b/security/xpm/Makefile @@ -2,20 +2,31 @@ # # Copyright (c) 2023 Huawei Device Co., Ltd. # -# Makefile for the ecutable permission manager(XPM) module +# Makefile for the ecutable permission manager module # -obj-$(CONFIG_XPM) += \ - core/xpm_main.o \ +obj-$(CONFIG_SECURITY_XPM) += \ + core/xpm_module.o \ core/xpm_misc.o \ - core/xpm_api.o \ - core/xpm_debugfs.o \ - core/xpm_integrity.o \ + core/xpm_hck.o \ core/xpm_report.o \ validator/elf_code_segment_info.o \ validator/exec_signature_info.o -ccflags-$(CONFIG_XPM) += \ - -I$(srctree)/security/xpm/incude \ - -I$(srctree)/security/xpm/validator \ - -I$(srctree)/fs/proc +obj-$(CONFIG_SECURITY_XPM_DEBUG) += \ + core/xpm_debugfs.o + +ccflags-$(CONFIG_SECURITY_XPM) += \ + -I$(srctree)/fs/proc \ + -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/security/xpm/core/xpm_debugfs.c b/security/xpm/core/xpm_debugfs.c index 44b135cba85c..7902b5074cde 100755 --- a/security/xpm/core/xpm_debugfs.c +++ b/security/xpm/core/xpm_debugfs.c @@ -3,32 +3,27 @@ * Copyright (c) 2023 Huawei Device Co., Ltd. */ -#include "xpm_debugfs.h" #include +#include "xpm_log.h" +#include "xpm_debugfs.h" -uint8_t g_xpm_mode = XPM_PERMISSIVE_MODE; -static struct dentry *g_xpm_dir = NULL; - -int xpm_value(int value) -{ - return (g_xpm_mode == XPM_ENFORCE_MODE ? value : XPM_SUCCESS); -} +extern uint8_t xpm_mode; +static struct dentry *xpm_dir; int xpm_debugfs_init(void) { - g_xpm_dir = debugfs_create_dir("xpm", NULL); - if (!g_xpm_dir) { + xpm_dir = debugfs_create_dir("xpm", NULL); + if (!xpm_dir) { xpm_log_error("create xpm debugfs dir failed"); - return XPM_FAULT; + return -EINVAL; } - debugfs_create_u8("xpm_mode", S_IRUSR | S_IWUSR, g_xpm_dir, - &g_xpm_mode); + debugfs_create_u8("xpm_mode", 0600, xpm_dir, &xpm_mode); - return XPM_SUCCESS; + return 0; } void xpm_debugfs_exit(void) { - debugfs_remove_recursive(g_xpm_dir); -} \ No newline at end of file + debugfs_remove_recursive(xpm_dir); +} diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c new file mode 100644 index 000000000000..92d57faf6459 --- /dev/null +++ b/security/xpm/core/xpm_hck.c @@ -0,0 +1,337 @@ +// 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__EXECMEM_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__EXECMEM_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) { + report_file_event(TYPE_FORMAT_UNDEF, vma->vm_file); + xpm_log_error("xpm get executable file signature info failed"); + 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 = -EPERM; + 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 ret; + } + + /* anonymous executable permission need controled by selinux */ + if (is_anon && (prot & PROT_EXEC)) { + ret = xpm_avc_has_perm(SECCLASS_XPM, XPM__ANON_EXECMEM); + if (ret) { + xpm_log_error("anonymous mmap not allow exec permission"); + report_mmap_event(TYPE_ANON_EXEC, vma, TYPE_ANON, prot); + } + return ret; + } + + 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); +} + +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"); +} + +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; + + xpm_log_debug("flag(0x%lx), low_limit(0x%lx), high_limit(0x%lx)", + info.flags, info.low_limit, info.high_limit); + + *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; +} + +vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page) +{ + if (!page) + return 0; + + /* 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); + return xpm_value(VM_FAULT_SIGSEGV); + } + + /* integrity violation: execute a writetained page */ + if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { + report_integrity_event(TYPE_INTEGRITY_WT, vma, page); + return xpm_value(VM_FAULT_SIGSEGV); + } + + return 0; +} + +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); +} + +vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page) +{ + vm_fault_t ret; + + if (!page) + return 0; + + ret = xpm_integrity_check(vma, vflags, addr, page); + if (!ret) + xpm_integrity_update(vma, vflags, page); + + return ret; +} + +/* + * check the integrity of these two pages, return true if equal, + * otherwise false + */ +bool xpm_integrity_equal(struct page *page, struct page *kpage) +{ + if (!page || !kpage) + return true; + + return !((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) || + (PageXPMReadonly(page) != PageXPMReadonly(kpage))); +} + +void xpm_register_hck_hooks(void) +{ + REGISTER_HCK_LITE_HOOK(xpm_region_outer_lhck, xpm_region_outer); + REGISTER_HCK_LITE_HOOK(xpm_get_unmapped_area_lhck, + xpm_get_unmapped_area); +} diff --git a/security/xpm/core/xpm_integrity.c b/security/xpm/core/xpm_integrity.c deleted file mode 100644 index 5ee502619969..000000000000 --- a/security/xpm/core/xpm_integrity.c +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (c) 2022 Huawei Device Co., Ltd. - */ - -#include -#include -#include -#include -#include "xpm_report.h" - -#ifdef CONFIG_XPM - -/* - * 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) -{ - struct xpm_region *xpm_region; - struct mm_struct *mm; - - mm = current->mm; - if (!mm) { - xpm_log_error("mm_struct is null"); - return false; - } - - xpm_region = &mm->xpm_region; - - /* 1. XPM Secure 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; -} - -vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, - unsigned long addr, struct page *page) -{ - if (!page) - return 0; - - /* Integrity violation: write a readonly page */ - if ((vflags & FAULT_FLAG_WRITE) && - (vma->vm_flags & VM_WRITE) && - PageXPMReadonly(page)) { - report_integrity_violated(vma, page); - return xpm_value(VM_FAULT_SIGSEGV); - } - - /* Integrity violation: execute a writetained page */ - if (PageXPMWritetainted(page) && is_xpm_readonly_region(vma)) { - report_integrity_violated(vma, page); - return xpm_value(VM_FAULT_SIGSEGV); - } - - return 0; -} - -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); - - return; -} - -vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, - unsigned long addr, struct page *page) -{ - vm_fault_t ret; - if (!page) - return 0; - - ret = xpm_integrity_check(vma, vflags, addr, page); - if(!ret) - xpm_integrity_update(vma, vflags, page); - - return ret; -} - -/* - * check the integrity of these two pages, return true if equal, - * otherwise false - */ -bool xpm_integrity_equal(struct page *page, struct page *kpage) -{ - if (!kpage) - return true; - - return !((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) || - (PageXPMReadonly(page) != PageXPMReadonly(kpage))); -} - -#endif diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index aed77cbabbce..b8dda41f36d2 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -12,14 +12,14 @@ #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 = XPM_SUCCESS; + int ret = 0; unsigned long addr; struct mm_struct *mm = current->mm; @@ -113,4 +113,4 @@ int xpm_register_misc_device(void) void xpm_deregister_misc_device(void) { misc_deregister(&xpm_misc); -} \ No newline at end of file +} diff --git a/security/xpm/core/xpm_main.c b/security/xpm/core/xpm_module.c similarity index 79% rename from security/xpm/core/xpm_main.c rename to security/xpm/core/xpm_module.c index 4894d9214452..4b875b7395b5 100755 --- a/security/xpm/core/xpm_main.c +++ b/security/xpm/core/xpm_module.c @@ -7,30 +7,36 @@ #include #include #include -#include -#include "xpm_debugfs.h" +#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; + xpm_register_xpm_hooks(); + xpm_register_hck_hooks(); + 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_deregister_misc_device(); xpm_log_error("xpm init debugfs failed, ret = %d", ret); + xpm_deregister_misc_device(); + report_init_event(TYPE_DEBUGFS_UNINIT); return ret; } xpm_log_info("xpm module init success"); - return XPM_SUCCESS; + return 0; } static void __exit xpm_module_exit(void) @@ -42,4 +48,4 @@ static void __exit xpm_module_exit(void) module_init(xpm_module_init); module_exit(xpm_module_exit); -MODULE_LICENSE("GPL"); \ No newline at end of file +MODULE_LICENSE("GPL"); diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 341f70fa8f3d..17f47df89298 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -5,205 +5,265 @@ #include #include +#include +#include #include #include +#ifdef CONFIG_HW_KERNEL_SG +#include +#endif +#include "xpm_log.h" #include "xpm_report.h" -void report_integrity_violated(struct vm_area_struct *vma, struct page *page) +#ifndef CONFIG_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) { - INIT_RATELIMIT(); - if(GET_RATELIMIT()) - report_code_tampered(vma, page); + xpm_log_info("%d: %s", event->event_id, event->content); + return 0; } +#endif -static char *get_file_name(struct file *file, char *path_buf, int len) +static char *xpm_get_filename(struct xpm_event_param *param, char *buf, int len) { - char *filename; - struct dentry *dentry; - - if (!file) + 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; - dentry = file->f_path.dentry; - filename = dentry_path(dentry, path_buf, len); - if (IS_ERR(filename)) + 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 inline void report_xpm_event(event_info *event) +static int set_init_content(struct xpm_event_param *param, + uint8_t *content, uint32_t content_len) { - INIT_RATELIMIT(); - if(!GET_RATELIMIT()) - return; - event->event_id = XPM_SG_EVENT_ID; - event->version = XPM_SG_VERSION; - event->content_len = strlen(event->content) + 1; - xpm_report_security_info(event); -} + int len; -void report_xpm_init_failed(int err_code) -{ - event_info *event; + 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; + } - INIT_RATELIMIT(); - if(!GET_RATELIMIT()) - return; + return 0; +} - event = (event_info *)kzalloc(sizeof(event_info) + - MAX_CONTENT_LEN, GFP_KERNEL); +#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 (!event) { - xpm_log_error("kmalloc event_info failed"); - return; + if (!param->vma) { + xpm_log_error("input vma is NULL"); + return -EINVAL; } - snprintf(event->content, MAX_CONTENT_LEN, XPM_INIT_FAILED_JSON, - XPM_INIT_FAILED, ktime_get_real(), err_code); - - report_xpm_event(event); + 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; + } - kfree(event); - return; + return 0; } -void report_file_format_damaged(struct file *file, bool signature, char *log) +static int set_file_content(struct xpm_event_param *param, + uint8_t *content, uint32_t content_len) { - char *filename, *buf; - event_info *event; + int len; - if (!file) { - xpm_log_error("damaged fileptr is null"); - return; + 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; } - INIT_RATELIMIT(); - if(!GET_RATELIMIT()) - return; + return 0; +} + +static int set_integrity_content(struct xpm_event_param *param, + uint8_t *content, uint32_t content_len) +{ + int len; + char *page_type; - event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, - GFP_KERNEL); - if (!event) { - xpm_log_error("kmalloc event_info failed"); - return; + if (!param->vma || !param->page) { + xpm_log_error("input vma or page is NULL"); + return -EINVAL; } - buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); - if (!buf) { - xpm_log_error("kmalloc failed"); - goto free_event; + 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; } - filename = get_file_name(file, buf, MAX_CONTENT_LEN); - if(!filename) - goto free_buf; + return 0; +} - snprintf(event->content, MAX_CONTENT_LEN, - XPM_FILE_FORMAT_DAMAGED_JSON, - XPM_FILE_FORMAT_DAMAGED, ktime_get_real(), - current->pid, filename, signature, log ? log : ""); +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(xpm_event_type type, + struct xpm_event_param *param, event_info *event) +{ + int ret; - report_xpm_event(event); + 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; + } -free_buf: - kfree(buf); -free_event: - kfree(event); - return; + return 0; } -void report_code_map_failed(int err_code, struct file *file, bool signature, - int codetype, int prot, pgoff_t pgoff, size_t size) +static int xpm_report_event(xpm_event_type type, struct xpm_event_param *param) { - char *filename, *buf; - event_info *event; - - if (!file) - return; + int ret; + event_info *sg_event = NULL; + char *buf = NULL; - INIT_RATELIMIT(); - if(!GET_RATELIMIT()) - return; + if (!(xpm_event[type].set_content)) { + xpm_log_error("type [%d] set content func invalid", type); + return -EINVAL; + } - event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, - GFP_KERNEL); - if (!event) { - xpm_log_error("kmalloc event_info failed"); - return; + 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 = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); + buf = __getname(); if (!buf) { - xpm_log_error("kmalloc failed"); - goto free_event; + xpm_log_error("alloc file name buf failed"); + kfree(sg_event); + return -ENOMEM; } - filename = get_file_name(file, buf, MAX_CONTENT_LEN); - if(!filename) - goto free_buf; + 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; - snprintf(event->content, MAX_CONTENT_LEN, XPM_MAP_FAILED_JSON, - XPM_CODE_MAP_FAILED, ktime_get_real(), err_code, - current->pid, filename, signature, codetype, prot, pgoff, size); + ret = report_event_inner(type, param, sg_event); - report_xpm_event(event); - -free_buf: - kfree(buf); -free_event: - kfree(event); - return; + __putname(buf); + kfree(sg_event); + return ret; } -static int inline get_xpm_flags(struct page *page) +void report_init_event(xpm_event_type type) { - return (PageXPMReadonly(page) << 1 | PageXPMWritetainted(page)); + struct xpm_event_param param = {0}; + + xpm_report_ratelimited(xpm_report_event, type, ¶m); } -void report_code_tampered(struct vm_area_struct *vma, struct page *page) +void report_file_event(xpm_event_type type, struct file *file) { - char *filename, *buf, *page_type; - event_info *event; - - INIT_RATELIMIT(); - if(!GET_RATELIMIT()) - return; - - event = (event_info *)kzalloc(sizeof(event_info) + MAX_CONTENT_LEN, - GFP_KERNEL); - if (event == NULL) { - xpm_log_error("kmalloc event_info failed"); - return; - } - - buf = (char *)kmalloc(MAX_CONTENT_LEN, GFP_KERNEL); - if (!buf) { - xpm_log_error("kmalloc buf failed"); - goto free_event; - } - - filename = vma_is_anonymous(vma) ? - "Anon" : get_file_name(vma->vm_file, buf, MAX_CONTENT_LEN); - - page_type = PageKsm(page) ? - "[ksm]" : PageAnon(page) ? "[anon]" : "[file]"; + struct xpm_event_param param = {0}; - snprintf(event->content, MAX_CONTENT_LEN, XPM_INTEGRITY_TAMPERED_JSON, - XPM_INTEGRITY_TAMPERED, ktime_get_real(), current->pid, get_xpm_flags(page), - page_type, filename ? filename:"", page->index, vma->vm_page_prot); + param.file = file; + xpm_report_ratelimited(xpm_report_event, type, ¶m); +} - report_xpm_event(event); +void report_mmap_event(xpm_event_type type, struct vm_area_struct *vma, + int code, int prot) +{ + struct xpm_event_param param = {0}; - kfree(buf); -free_event: - kfree(event); - return; + param.vma = vma; + param.code = code; + param.prot = prot; + xpm_report_ratelimited(xpm_report_event, type, ¶m); } -#ifndef CONFIG_HW_KERNEL_SG -unsigned int xpm_report_security_info(const event_info *event) +void report_integrity_event(xpm_event_type type, struct vm_area_struct *vma, + struct page *page) { - xpm_log_error("%s", event->content); - return 0; + struct xpm_event_param param = {0}; + + param.vma = vma; + param.page = page; + xpm_report_ratelimited(xpm_report_event, type, ¶m); } -#endif diff --git a/security/xpm/core/xpm_report.h b/security/xpm/core/xpm_report.h deleted file mode 100644 index 32c38f284568..000000000000 --- a/security/xpm/core/xpm_report.h +++ /dev/null @@ -1,169 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - */ - -#ifndef _XPM_LOG_H -#define _XPM_LOG_H - -#include -#include -#include - -enum { - XPM_INIT_FAILED = 1, - XPM_FILE_FORMAT_DAMAGED, - XPM_CODE_MAP_FAILED, - XPM_INTEGRITY_TAMPERED, - XPM_STASTIC_EVENT -}; - -#ifdef CONFIG_PRINTK -#define INIT_RATELIMIT() \ - static DEFINE_RATELIMIT_STATE(_rs, \ - DEFAULT_RATELIMIT_INTERVAL, \ - DEFAULT_RATELIMIT_BURST); -#define GET_RATELIMIT() \ - __ratelimit(&_rs) -#else -#define INIT_RATELIMIT() -#define GET_RATELIMIT() 1 -#endif - -#ifdef CONFIG_HW_KERNEL_SG -#include - -#define XPM_SG_EVENT_ID 111 -#define XPM_SG_VERSION 222 - -static inline unsigned int xpm_report_security_info(const event_info *info) -{ - return report_security_info(const event_info *info); -} - -#else -typedef struct { - unsigned long event_id; - unsigned int version; - unsigned int content_len; - char content[0]; -} event_info; - -#define XPM_SG_EVENT_ID 0 -#define XPM_SG_VERSION 0 - -/* - * xpm_report_security_info - report xpm evernt to hievent - */ -unsigned int xpm_report_security_info(const event_info *info); -#endif /*_HW_KERNEL_SG_COLLECˇ_H_*/ - -#define XPM_EVENT_ID 0 -#define XPM_EVENT_VERSION 0 -#define MAX_CONTENT_LEN 900 - -#define JSTR(val) "\""#val"\"" -#define JSPAIR(val, format) JSTR(val) ":" #format - -#define EVENT_COM_CONT() \ - JSPAIR(event, %d) ", "\ - JSPAIR(time, %lld) ", " - -#define PROCESS_INFO \ - JSTR(process) ":{" \ - JSPAIR(pid,%d) \ - "}" - -#define FILE_INFO \ - JSTR(file) ":{"\ - JSTR(filename) ":" JSTR(%s) ", "\ - JSPAIR(signature, %d) \ - "}" - -#define MAP_INFO \ - JSTR(vma) ":{" \ - JSPAIR(code_type, %d) ", "\ - JSPAIR(flags, %lx) ", "\ - JSPAIR(pgoff, 0x%lx) ", "\ - JSPAIR(size, 0x%lx) \ - "}" - -#define PAGE_INFO \ - JSTR(page) ":{" \ - JSPAIR(xpm_flag, %d) ", "\ - JSTR(page_type) ":" JSTR(%s) ", "\ - JSTR(filename) ":" JSTR(%s) ", "\ - JSPAIR(pgoff, %d) \ - "}" - -#define EXTRA_INFO \ - JSTR(extra) ":" JSTR(%s) - -/* - * XPM_INIT_FAILED JSON format - */ -#define XPM_INIT_FAILED_JSON \ - "{" EVENT_COM_CONT() \ - JSTR(detail) ":{" \ - JSPAIR(xpm_flag, %d) \ - "}}" - -/* - * ELF_FORMAT_DAMAGED JSON format - */ -#define XPM_FILE_FORMAT_DAMAGED_JSON \ - "{" EVENT_COM_CONT() \ - JSTR(detail) ":{"\ - PROCESS_INFO ","\ - FILE_INFO "," \ - EXTRA_INFO \ - "}}" - -/* - * XPM_MAP_FAILED JSON format - */ -#define XPM_MAP_FAILED_JSON \ - "{" EVENT_COM_CONT() \ - JSTR(detail) ":{" \ - JSPAIR(fault, %d) "," \ - PROCESS_INFO "," \ - FILE_INFO "," \ - MAP_INFO \ - "}}" - -/* - * XPM_INTEGRITY_VIOLATION JSON format - */ -#define XPM_INTEGRITY_TAMPERED_JSON \ - "{" EVENT_COM_CONT() \ - JSTR(detail) ":{" \ - PROCESS_INFO","\ - PAGE_INFO ","\ - JSPAIR(vm_prot, %d) \ - "}}" - -#ifdef CONFIG_XPM -void report_integrity_violated(struct vm_area_struct *vma, struct page *page); -void report_xpm_init_failed(int errs_code); -void report_file_format_damaged(struct file *file, bool signature, char *log); -void report_code_map_failed(int err_code, struct file *file, bool signature, - int codetype, int prot, pgoff_t pgoff, size_t size); -void report_code_tampered(struct vm_area_struct *vma, struct page *page); - -#else -inline void report_integrity_violated(struct vm_area_struct *vma, - struct page *page) {} - -void report_xpm_init_failed(int errs_code) {} - -void report_file_format_damaged(struct file *file, bool signature, - char *log) {} - -void report_code_map_failed(int err_code, struct file *file, bool signature, - int codetype, int prot, pgoff_t pgoff, size_t size) {} - -void report_code_tampered(struct vm_area_struct *vma, struct page *page) {} - -#endif - -#endif /* _XPM_LOG_H */ diff --git a/security/xpm/include/exec_signature_info.h b/security/xpm/include/exec_signature_info.h new file mode 100644 index 000000000000..b37aa46cfe03 --- /dev/null +++ b/security/xpm/include/exec_signature_info.h @@ -0,0 +1,59 @@ +/* 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 put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); +int get_elf_code_segment_info(struct file *file, bool is_exec, int type, 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); +#endif diff --git a/security/xpm/core/xpm_debugfs.h b/security/xpm/include/xpm_debugfs.h similarity index 46% rename from security/xpm/core/xpm_debugfs.h rename to security/xpm/include/xpm_debugfs.h index 2c87898cef1f..7466f66ce23c 100755 --- a/security/xpm/core/xpm_debugfs.h +++ b/security/xpm/include/xpm_debugfs.h @@ -3,20 +3,16 @@ * Copyright (c) 2023 Huawei Device Co., Ltd. */ -#ifndef XPM_DEBUGFS_H +#ifndef _XPM_DEBUGFS_H -#include +#ifdef CONFIG_SECURITY_XPM_DEBUG +int xpm_debugfs_init(void); +void xpm_debugfs_exit(void); -#define XPM_PERMISSIVE_MODE 0 -#define XPM_ENFORCE_MODE 1 - -#ifdef CONFIG_XPM_DEBUG -extern int xpm_debugfs_init(void); -extern void xpm_debugfs_exit(void); #else static inline int xpm_debugfs_init(void) { - return XPM_SUCCESS; + return 0; } static inline void xpm_debugfs_exit(void) @@ -24,4 +20,4 @@ static inline void xpm_debugfs_exit(void) } #endif -#endif /* XPM_DEBUGFS_H */ \ No newline at end of file +#endif /* _XPM_DEBUGFS_H */ diff --git a/security/xpm/include/xpm_hck.h b/security/xpm/include/xpm_hck.h new file mode 100644 index 000000000000..46bf62d4069e --- /dev/null +++ b/security/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/security/xpm/include/xpm_log.h b/security/xpm/include/xpm_log.h new file mode 100644 index 000000000000..5638e750ff2e --- /dev/null +++ b/security/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/security/xpm/core/xpm_misc.h b/security/xpm/include/xpm_misc.h similarity index 92% rename from security/xpm/core/xpm_misc.h rename to security/xpm/include/xpm_misc.h index 050269446aa6..fbdf45e033bb 100755 --- a/security/xpm/core/xpm_misc.h +++ b/security/xpm/include/xpm_misc.h @@ -14,4 +14,4 @@ struct xpm_region_info { int xpm_register_misc_device(void); void xpm_deregister_misc_device(void); -#endif /* _XPM_MISC_H */ \ No newline at end of file +#endif /* _XPM_MISC_H */ diff --git a/security/xpm/include/xpm_report.h b/security/xpm/include/xpm_report.h new file mode 100644 index 000000000000..1c1d99603356 --- /dev/null +++ b/security/xpm/include/xpm_report.h @@ -0,0 +1,89 @@ +/* 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 + + +#define CODE_TYPE_ABC "ABC" +#define CODE_TYPE_ELF "ELF" + +typedef enum { + EVENT_INIT = 1011009110, + EVENT_FILE = 1011009111, + EVENT_MMAP = 1011009112, + EVENT_INTEGRITY = 1011009113, +} xpm_event_id; + +typedef enum { + 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, +} xpm_event_type; + +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; + 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(xpm_event_type type); +void report_file_event(xpm_event_type type, struct file *file); +void report_mmap_event(xpm_event_type type, struct vm_area_struct *vma, + int code, int prot); +void report_integrity_event(xpm_event_type type, struct vm_area_struct *vma, + struct page *page); + +#endif /* _XPM_REPORT_H */ diff --git a/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index ef7bf9c63800..d295fc6e33b2 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -22,17 +22,135 @@ struct elf_info { uintptr_t e_phoff; }; +#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 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 read_elf_info(struct file *file, void *buffer, size_t read_size, loff_t pos) { - size_t len; + size_t len = kernel_read(file, buffer, read_size, &pos); - len = kernel_read(file, buffer, read_size, &pos); - if (unlikely(len != read_size)) + if (unlikely(len != read_size)) { return -EIO; return 0; } +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; + i++; + } + return i; +} + +static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) +{ + struct exec_file_signature_info *code_segment_info; + int i = 0; + int count = VERITY_NODE_CACHE_RECYCLE_NUM; + uintptr_t *code_segments = kzalloc(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); +} + +static void rm_code_segment_info(void) +{ + if ((dm_verity_node_count + fs_verity_node_count) < VERITY_NODE_CACHE_LIMITS) { + return; + } + + 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 uint64_t elf64_to_cpu(const struct elfhdr *ehdr, uint64_t value) { if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) @@ -175,8 +293,9 @@ static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_i 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) + int 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) @@ -269,10 +388,9 @@ int parse_elf_code_segment_info(struct file *file, { const char *phdr_info; struct elf_info elf_info = {0}; - int ret; + int ret = elf_check_and_get_code_segment_offset(file, &elf_info); - ret = elf_check_and_get_code_segment_offset(file, &elf_info); - if (ret < 0) + if (ret < 0) { return ret; phdr_info = kzalloc(elf_info.e_phsize, GFP_KERNEL); @@ -289,3 +407,229 @@ int parse_elf_code_segment_info(struct file *file, kfree(phdr_info); return ret; } + +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 = (struct exec_file_signature_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; + } + } + + 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 put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) +{ + if ((code_segment_info == NULL) || + !exec_file_signature_is_verity(code_segment_info)) { + return -EINVAL; + } + + if (atomic_sub_return(1, &code_segment_info->reference) <= 0 && + exec_file_signature_is_delete(code_segment_info)) { + kfree(code_segment_info); + } + return 0; +} + +int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) +{ + struct rb_root *root; + rwlock_t *verity_lock; + int *node_count; + struct exec_file_signature_info *segment_info = NULL; + + if (code_segment_info == NULL) { + return -EINVAL; + } + + if (exec_file_signature_is_dm_verity(code_segment_info)) { + root = &dm_verity_tree; + verity_lock = &dm_verity_tree_lock; + node_count = &dm_verity_node_count; + } else if (exec_file_signature_is_fs_verity(code_segment_info)) { + verity_lock = &fs_verity_tree_lock; + root = &fs_verity_tree; + node_count = &fs_verity_node_count; + } else { + return -EINVAL; + } + + write_lock(verity_lock); + segment_info = rb_search_node(root, code_segment_info->inode); + if (segment_info == NULL) { + write_unlock(verity_lock); + return -EINVAL; + } + rb_erase_node(root, node_count, code_segment_info); + write_unlock(verity_lock); + kfree(code_segment_info); + return 0; +} + +static int destroy_elf_code_segment_tree(struct rb_root *root, int *node_count) +{ + struct rb_node *node; + struct exec_file_signature_info *file_node; + + do { + node = rb_first(root); + if (node == NULL) { + return 0; + } + file_node = rb_entry(node, struct exec_file_signature_info, rb_node); + if (atomic_read(&file_node->reference) > 0) { + return -EPERM; + } + rb_erase_node(root, node_count, file_node); + } while (1); + return 0; +} + +int test_destroy_elf_code_segment_info_cache(void) +{ + int ret; + int count = 0; + + write_lock(&dm_verity_tree_lock); + count += dm_verity_node_count; + ret = destroy_elf_code_segment_tree(&dm_verity_tree, &dm_verity_node_count); + write_unlock(&dm_verity_tree_lock); + if (ret < 0) { + return ret; + } + + write_lock(&fs_verity_tree_lock); + count += fs_verity_node_count; + ret = destroy_elf_code_segment_tree(&fs_verity_tree, &fs_verity_node_count); + write_unlock(&fs_verity_tree_lock); + if (ret < 0) { + return ret; + } + return count; +} + +void test_rm_elf_code_segment_info_cache(void) +{ + 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 size_t 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; +} + +void test_get_elf_code_segment_info_cache_size(void) +{ + size_t cache_size = 0; + int count = 0; + + read_lock(&dm_verity_tree_lock); + cache_size += 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 += 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); +} + +void test_print_elf_code_segment_info(const char *file_path, + const struct exec_file_signature_info *file_info) +{ + int i; + + for (i = 0; i < file_info->code_segment_count; i++) { + pr_info("%s -> offset: 0x%llx size: 0x%lx\n", + file_path, file_info->code_segments->file_offset, file_info->code_segments->size); + } +} diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index e616e62a029b..6afef7975d41 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -10,23 +10,70 @@ #include #include "exec_signature_info.h" -#define VERITY_NODE_CACHE_LIMITS 10000 -#define VERITY_NODE_CACHE_RECYCLE_NUM 200 +#ifdef CONFIG_DM_VERITY +#define HVB_CMDLINE_VB_STATE "ohos.boot.hvb.enable" +static bool dm_verity_enable; -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 hvb_boot_param_cb(char *param, char *val, + const char *unused, void *arg) +{ + if (param == NULL || val == NULL) { + return 0; + } + if (strcmp(param, HVB_CMDLINE_VB_STATE) != 0) { + return 0; + } + if (strcmp(val, "true") == 0 || strcmp(val, "TRUE") == 0) { + dm_verity_enable = true; + } + return 0; +} + +static bool dm_verity_is_enable(void) +{ + static bool dm_verity_check; + char *cmdline; + + if (!dm_verity_check && !dm_verity_enable) { + cmdline = kstrdup(saved_command_line, GFP_KERNEL); + if (cmdline == NULL) { + return false; + } + parse_args("hvb.enable params", cmdline, NULL, + 0, 0, 0, NULL, &hvb_boot_param_cb); + kfree(cmdline); + } + dm_verity_check = true; + return dm_verity_enable; +} + +static bool is_dm_verity(struct file *file) +{ + struct mount *mnt; + struct mapped_device *device; + + if (!dm_verity_is_enable()) { + return false; + } + if (file->f_path.mnt == NULL) { + return false; + } + mnt = container_of(test_file->f_path.mnt, struct mount, mnt); + device = dm_get_md(dm_get_dev_t(mnt->mnt_devname)); + if (device == NULL) { + return false; + } + dm_put(device); + return true; +} +#endif #ifdef CONFIG_FS_VERITY static bool is_fs_verity(struct file *file) { - struct inode *file_node; + struct inode *file_node = file_inode(file); - file_inode = file_inode(file); - if (file_node == NULL) + if (file_node == NULL) { return false; if (file_node->i_verity_info == NULL) -- Gitee From 1a3b7cd16cbbbae3183500f67fd53b133af9ffee Mon Sep 17 00:00:00 2001 From: limerence Date: Fri, 28 Apr 2023 11:12:09 +0800 Subject: [PATCH 26/35] fix signature compile error Signed-off-by: limerence --- include/linux/xpm.h | 4 +- security/xpm/Makefile | 1 - security/xpm/core/xpm_api.c | 216 ----------- security/xpm/core/xpm_hck.c | 5 + security/xpm/include/exec_signature_info.h | 11 +- .../xpm/validator/elf_code_segment_info.c | 360 +----------------- security/xpm/validator/exec_signature_info.c | 69 +--- 7 files changed, 30 insertions(+), 636 deletions(-) delete mode 100755 security/xpm/core/xpm_api.c diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 4e9b93ba7dfd..63f5bd6e10a0 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -39,14 +39,12 @@ static inline unsigned long xpm_get_unmapped_area_hook(unsigned long addr, return ret; } +#ifdef CONFIG_SECURITY_XPM /** * When inodes are destroyed, the corresponding cache must be destroyed */ extern void xpm_delete_cache_node(struct inode *file_node); -#ifdef CONFIG_SECURITY_XPM -/* xpm internal return values */ - /* * Check the confliction of a page's xpm flags, make sure a process will not map * any RO page into a writable vma or a WT page into a execuable/XPM memory region. diff --git a/security/xpm/Makefile b/security/xpm/Makefile index 20747e9dad60..55e72cebad33 100755 --- a/security/xpm/Makefile +++ b/security/xpm/Makefile @@ -17,7 +17,6 @@ obj-$(CONFIG_SECURITY_XPM_DEBUG) += \ core/xpm_debugfs.o ccflags-$(CONFIG_SECURITY_XPM) += \ - -I$(srctree)/fs/proc \ -I$(srctree)/security/xpm/include \ -I$(srctree)/security/selinux/include \ -I$(srctree)/security/selinux diff --git a/security/xpm/core/xpm_api.c b/security/xpm/core/xpm_api.c deleted file mode 100755 index b6b565f53cba..000000000000 --- a/security/xpm/core/xpm_api.c +++ /dev/null @@ -1,216 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (c) 2023 Huawei Device Co., Ltd. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "xpm_report.h" -#include "exec_signature_info.h" - -#define XPM_REGION_STR_LEN 33 - -extern struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode); - -bool xpm_is_anonymous_vma(struct vm_area_struct *vma) -{ - return vma_is_anonymous(vma) || vma_is_shmem(vma); -} - -bool xpm_is_region_outer(unsigned long addr_start, unsigned long addr_end, - unsigned long flags) -{ - struct mm_struct *mm = current->mm; - - /* Already in xpm region, just return without judge */ - if (flags & VM_UNMAPPED_AREA_XPM) - return true; - - return (addr_start >= mm->xpm_region.addr_end) || - (addr_end <= mm->xpm_region.addr_start); -} - -unsigned long xpm_get_unmapped_area(unsigned long addr, unsigned long len, - unsigned long map_flags, unsigned long unmapped_flags) -{ - struct vm_unmapped_area_info info; - struct mm_struct *mm = current->mm; - - if ((map_flags & MAP_FIXED) && - (!xpm_is_region_outer(addr, addr + len , 0))) { - xpm_log_error("xpm region not allow mmap with MAP_FIXED"); - return -EFAULT; - } - - if (map_flags & MAP_XPM) { - if (addr) { - xpm_log_error("xpm region not allow specify addr"); - return -EPERM; - } - - 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; - - xpm_log_debug("set xpm region info success, flag(0x%lx), low_limit(0x%lx), high_limit(0x%lx)", - info.flags, info.low_limit, info.high_limit); - - return vm_unmapped_area(&info); - } - - return XPM_SUCCESS; -} - -static int xpm_check_segment(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 (!segments) { - xpm_log_error("code segments is NULL"); - return XPM_FAULT; - } - - 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 XPM_SUCCESS; - } - - xpm_log_error("executable vma is not code segments"); - return XPM_CHECK_FAILED; -} - -int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) -{ - int ret = 0; - struct exec_file_signature_info *info = NULL; - bool is_exec = !xpm_is_anonymous_vma(vma) && (prot & PROT_EXEC); - - if (!vma) { - xpm_log_error("input vma is NULL"); - return XPM_FAULT; - } - - /* vma is non-executable or mmap in xpm region just return success */ - if (!((vma->vm_flags & VM_XPM) || is_exec)) - return XPM_SUCCESS; - - /* 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("get executable file signature info failed"); - return XPM_FAULT; - } - - if (info == NULL) { - xpm_log_error("invalid executable file signature"); - ret = XPM_CHECK_FAILED; - goto exit; - } - - /* executable vma need check whether in code segment */ - if (is_exec) - ret = xpm_check_segment(vma, info); -exit: - put_exec_file_signature_info(info); - return ret; -} - -void xpm_delete_cache_node(struct inode *file_node) -{ - delete_exec_file_signature_info(file_node); -} - -int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) -{ - bool is_anon = xpm_is_anonymous_vma(vma); - - if (!vma) { - xpm_log_error("input vma is NULL"); - return XPM_FAULT; - } - - 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 XPM_FAULT; - } - - if (is_anon && (prot & PROT_EXEC)) { - xpm_log_info("anonymous mmap not suggest has exec permission"); - return XPM_CHECK_FAILED; - } - - if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) { - xpm_log_error("mmap not allow write & exec permission"); - return XPM_FAULT; - } - - return XPM_SUCCESS; -} - -static int xpm_region_open(struct inode *inode, struct file *file) -{ - struct mm_struct *mm = proc_mem_open(inode, PTRACE_MODE_READ); - - if (IS_ERR(mm)) - return PTR_ERR(mm); - - file->private_data = mm; - return XPM_SUCCESS; -} - -static ssize_t xpm_region_read(struct file *file, char __user *buf, - size_t count, loff_t *pos) -{ - struct mm_struct *mm = file->private_data; - char xpm_region_str[XPM_REGION_STR_LEN] = {0}; - size_t len; - - if (!mm) - return XPM_SUCCESS; - - len = snprintf(xpm_region_str, XPM_REGION_STR_LEN, "%lx-%lx", - mm->xpm_region.addr_start, - mm->xpm_region.addr_end); - - return simple_read_from_buffer(buf, count, pos, xpm_region_str, len); -} - -static int xpm_region_release(struct inode *inode, struct file *file) -{ - struct mm_struct *mm = file->private_data; - - if (mm) - mmdrop(mm); - - return XPM_SUCCESS; -} - -const struct file_operations proc_xpm_region_operations = { - .open = xpm_region_open, - .read = xpm_region_read, - .llseek = generic_file_llseek, - .release = xpm_region_release, -}; diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 92d57faf6459..5c8fd6b4a567 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -191,6 +191,11 @@ void xpm_register_xpm_hooks(void) security_add_hooks(xpm_hooks, ARRAY_SIZE(xpm_hooks), "xpm"); } +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) { diff --git a/security/xpm/include/exec_signature_info.h b/security/xpm/include/exec_signature_info.h index b37aa46cfe03..d6545073a109 100644 --- a/security/xpm/include/exec_signature_info.h +++ b/security/xpm/include/exec_signature_info.h @@ -1,8 +1,7 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +// 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 @@ -25,9 +24,9 @@ struct exec_segment_info { struct exec_file_signature_info { struct rb_node rb_node; atomic_t reference; - unsigned int type; + unsigned int type; uintptr_t inode; - unsigned int code_segment_count; + unsigned int code_segment_count; struct exec_segment_info *code_segments; }; @@ -52,8 +51,8 @@ static inline bool exec_file_signature_is_delete(const struct exec_file_signatur return !!(signature_info->type & FILE_SIGNATURE_DELETE); } -int put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info); -int get_elf_code_segment_info(struct file *file, bool is_exec, int type, struct exec_file_signature_info **code_segment_info); +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/security/xpm/validator/elf_code_segment_info.c b/security/xpm/validator/elf_code_segment_info.c index d295fc6e33b2..ef7bf9c63800 100644 --- a/security/xpm/validator/elf_code_segment_info.c +++ b/security/xpm/validator/elf_code_segment_info.c @@ -22,135 +22,17 @@ struct elf_info { uintptr_t e_phoff; }; -#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 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 read_elf_info(struct file *file, void *buffer, size_t read_size, loff_t pos) { - size_t len = kernel_read(file, buffer, read_size, &pos); + size_t len; - if (unlikely(len != read_size)) { + len = kernel_read(file, buffer, read_size, &pos); + if (unlikely(len != read_size)) return -EIO; return 0; } -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; - i++; - } - return i; -} - -static void clear_code_segment_info_cache(struct rb_root *root, int *node_count) -{ - struct exec_file_signature_info *code_segment_info; - int i = 0; - int count = VERITY_NODE_CACHE_RECYCLE_NUM; - uintptr_t *code_segments = kzalloc(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); -} - -static void rm_code_segment_info(void) -{ - if ((dm_verity_node_count + fs_verity_node_count) < VERITY_NODE_CACHE_LIMITS) { - return; - } - - 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 uint64_t elf64_to_cpu(const struct elfhdr *ehdr, uint64_t value) { if (ehdr->e_ident[EI_DATA] == ELFDATA2LSB) @@ -293,9 +175,8 @@ static int elf_check_and_get_code_segment_offset(struct file *file, struct elf_i struct elfhdr *elf_ehdr = &elf_info->elf_ehdr; int ret; - int ret = read_elf_info(file, (void *)elf_ehdr, sizeof(struct elfhdr), 0); - - if (ret < 0) { + 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) @@ -388,9 +269,10 @@ int parse_elf_code_segment_info(struct file *file, { const char *phdr_info; struct elf_info elf_info = {0}; - int ret = elf_check_and_get_code_segment_offset(file, &elf_info); + int ret; - if (ret < 0) { + 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); @@ -407,229 +289,3 @@ int parse_elf_code_segment_info(struct file *file, kfree(phdr_info); return ret; } - -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 = (struct exec_file_signature_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; - } - } - - 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 put_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) -{ - if ((code_segment_info == NULL) || - !exec_file_signature_is_verity(code_segment_info)) { - return -EINVAL; - } - - if (atomic_sub_return(1, &code_segment_info->reference) <= 0 && - exec_file_signature_is_delete(code_segment_info)) { - kfree(code_segment_info); - } - return 0; -} - -int test_delete_elf_code_segment_info(struct exec_file_signature_info *code_segment_info) -{ - struct rb_root *root; - rwlock_t *verity_lock; - int *node_count; - struct exec_file_signature_info *segment_info = NULL; - - if (code_segment_info == NULL) { - return -EINVAL; - } - - if (exec_file_signature_is_dm_verity(code_segment_info)) { - root = &dm_verity_tree; - verity_lock = &dm_verity_tree_lock; - node_count = &dm_verity_node_count; - } else if (exec_file_signature_is_fs_verity(code_segment_info)) { - verity_lock = &fs_verity_tree_lock; - root = &fs_verity_tree; - node_count = &fs_verity_node_count; - } else { - return -EINVAL; - } - - write_lock(verity_lock); - segment_info = rb_search_node(root, code_segment_info->inode); - if (segment_info == NULL) { - write_unlock(verity_lock); - return -EINVAL; - } - rb_erase_node(root, node_count, code_segment_info); - write_unlock(verity_lock); - kfree(code_segment_info); - return 0; -} - -static int destroy_elf_code_segment_tree(struct rb_root *root, int *node_count) -{ - struct rb_node *node; - struct exec_file_signature_info *file_node; - - do { - node = rb_first(root); - if (node == NULL) { - return 0; - } - file_node = rb_entry(node, struct exec_file_signature_info, rb_node); - if (atomic_read(&file_node->reference) > 0) { - return -EPERM; - } - rb_erase_node(root, node_count, file_node); - } while (1); - return 0; -} - -int test_destroy_elf_code_segment_info_cache(void) -{ - int ret; - int count = 0; - - write_lock(&dm_verity_tree_lock); - count += dm_verity_node_count; - ret = destroy_elf_code_segment_tree(&dm_verity_tree, &dm_verity_node_count); - write_unlock(&dm_verity_tree_lock); - if (ret < 0) { - return ret; - } - - write_lock(&fs_verity_tree_lock); - count += fs_verity_node_count; - ret = destroy_elf_code_segment_tree(&fs_verity_tree, &fs_verity_node_count); - write_unlock(&fs_verity_tree_lock); - if (ret < 0) { - return ret; - } - return count; -} - -void test_rm_elf_code_segment_info_cache(void) -{ - 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 size_t 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; -} - -void test_get_elf_code_segment_info_cache_size(void) -{ - size_t cache_size = 0; - int count = 0; - - read_lock(&dm_verity_tree_lock); - cache_size += 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 += 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); -} - -void test_print_elf_code_segment_info(const char *file_path, - const struct exec_file_signature_info *file_info) -{ - int i; - - for (i = 0; i < file_info->code_segment_count; i++) { - pr_info("%s -> offset: 0x%llx size: 0x%lx\n", - file_path, file_info->code_segments->file_offset, file_info->code_segments->size); - } -} diff --git a/security/xpm/validator/exec_signature_info.c b/security/xpm/validator/exec_signature_info.c index 6afef7975d41..e616e62a029b 100644 --- a/security/xpm/validator/exec_signature_info.c +++ b/security/xpm/validator/exec_signature_info.c @@ -10,70 +10,23 @@ #include #include "exec_signature_info.h" -#ifdef CONFIG_DM_VERITY -#define HVB_CMDLINE_VB_STATE "ohos.boot.hvb.enable" -static bool dm_verity_enable; +#define VERITY_NODE_CACHE_LIMITS 10000 +#define VERITY_NODE_CACHE_RECYCLE_NUM 200 -static int hvb_boot_param_cb(char *param, char *val, - const char *unused, void *arg) -{ - if (param == NULL || val == NULL) { - return 0; - } - if (strcmp(param, HVB_CMDLINE_VB_STATE) != 0) { - return 0; - } - if (strcmp(val, "true") == 0 || strcmp(val, "TRUE") == 0) { - dm_verity_enable = true; - } - return 0; -} - -static bool dm_verity_is_enable(void) -{ - static bool dm_verity_check; - char *cmdline; - - if (!dm_verity_check && !dm_verity_enable) { - cmdline = kstrdup(saved_command_line, GFP_KERNEL); - if (cmdline == NULL) { - return false; - } - parse_args("hvb.enable params", cmdline, NULL, - 0, 0, 0, NULL, &hvb_boot_param_cb); - kfree(cmdline); - } - dm_verity_check = true; - return dm_verity_enable; -} - -static bool is_dm_verity(struct file *file) -{ - struct mount *mnt; - struct mapped_device *device; - - if (!dm_verity_is_enable()) { - return false; - } - if (file->f_path.mnt == NULL) { - return false; - } - mnt = container_of(test_file->f_path.mnt, struct mount, mnt); - device = dm_get_md(dm_get_dev_t(mnt->mnt_devname)); - if (device == NULL) { - return false; - } - dm_put(device); - return true; -} -#endif +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; #ifdef CONFIG_FS_VERITY static bool is_fs_verity(struct file *file) { - struct inode *file_node = file_inode(file); + struct inode *file_node; - if (file_node == NULL) { + file_inode = file_inode(file); + if (file_node == NULL) return false; if (file_node->i_verity_info == NULL) -- Gitee From 9e0c775a9dd5bb79f84c206b3cf6a5303b801d92 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Fri, 28 Apr 2023 13:36:20 +0800 Subject: [PATCH 27/35] fix hck format Change-Id: I322bf82df3a93cdc9474d150dc33f31cf39fc636 --- fs/inode.c | 2 +- include/linux/hck/lite_hck_xpm.h | 26 +++++++++++ include/linux/xpm.h | 74 +++++++++++++++++++------------- mm/ksm.c | 2 +- mm/memory.c | 16 +++---- mm/mprotect.c | 2 +- security/xpm/core/xpm_hck.c | 54 +++++++++++++++-------- 7 files changed, 115 insertions(+), 61 deletions(-) diff --git a/fs/inode.c b/fs/inode.c index 179a13422009..e56bbfbb0c34 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -259,7 +259,7 @@ void __destroy_inode(struct inode *inode) security_inode_free(inode); fsnotify_inode_delete(inode); locks_free_lock_context(inode); - xpm_delete_cache_node(inode); + xpm_delete_cache_node_hook(inode); if (!inode->i_nlink) { WARN_ON(atomic_long_read(&inode->i_sb->s_remove_count) == 0); atomic_long_dec(&inode->i_sb->s_remove_count); diff --git a/include/linux/hck/lite_hck_xpm.h b/include/linux/hck/lite_hck_xpm.h index ca66e62152ae..a174d1f6a77e 100644 --- a/include/linux/hck/lite_hck_xpm.h +++ b/include/linux/hck/lite_hck_xpm.h @@ -6,6 +6,9 @@ #ifndef _LITE_HCK_XPM_H #define _LITE_HCK_XPM_H +#include "linux/mm_types.h" +#include +#include #include #ifndef CONFIG_HCK @@ -24,6 +27,29 @@ DECLARE_HCK_LITE_HOOK(xpm_get_unmapped_area_lhck, unsigned long unmapped_flags, unsigned long *ret), TP_ARGS(addr, len, map_flags, unmapped_flags, ret)); +DECLARE_HCK_LITE_HOOK(xpm_integrity_equal_lhck, + TP_PROTO(struct page *page, struct page *kpage, bool *ret), + TP_ARGS(page, kpage, ret)); + +DECLARE_HCK_LITE_HOOK(xpm_integrity_check_lhck, + TP_PROTO(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page, vm_fault_t *ret), + TP_ARGS(vma, vflags, addr, page, ret)); + +DECLARE_HCK_LITE_HOOK(xpm_integrity_validate_lhck, + TP_PROTO(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page, vm_fault_t *ret), + TP_ARGS(vma, vflags, addr, page, ret)); + +DECLARE_HCK_LITE_HOOK(xpm_integrity_update_lhck, + TP_PROTO(struct vm_area_struct *vma, unsigned int vflags, + struct page *page), + TP_ARGS(vma, vflags, page)); + +DECLARE_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, + TP_PROTO(struct inode *file_node), + TP_ARGS(file_node)); + #endif /* CONFIG_HCK */ diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 63f5bd6e10a0..a0cb43fd7c16 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -6,6 +6,7 @@ #ifndef _XPM_H #define _XPM_H +#include "linux/mm_types.h" #include #include #include @@ -39,51 +40,62 @@ static inline unsigned long xpm_get_unmapped_area_hook(unsigned long addr, return ret; } -#ifdef CONFIG_SECURITY_XPM -/** - * When inodes are destroyed, the corresponding cache must be destroyed - */ -extern void xpm_delete_cache_node(struct inode *file_node); - /* - * Check the confliction of a page's xpm flags, make sure a process will not map - * any RO page into a writable vma or a WT page into a execuable/XPM memory region. + * Check the confliction of a page's xpm flags, make sure a process will + * not map any RO page into a writable vma or a WT page into a execuable/XPM + * memory region. */ -vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, - unsigned long addr, struct page *page); -vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, - unsigned long addr, struct page *page); -void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, - struct page *page); -bool xpm_integrity_equal(struct page *page, struct page *kpage); - -#else -static inline void xpm_delete_cache_node(struct inode *file_node) + +static inline vm_fault_t xpm_integrity_check_hook(struct vm_area_struct *vma, + unsigned int vflags, unsigned long addr, struct page *page) { - return; + vm_fault_t ret = 0; + CALL_HCK_LITE_HOOK(xpm_integrity_check_lhck, vma, vflags, + addr, page, &ret); + + return ret; } -static inline bool xpm_integrity_equal(struct page *page, - struct page *kpage) +static inline +vm_fault_t xpm_integrity_validate_hook(struct vm_area_struct *vma, + unsigned int vflags, unsigned long addr, struct page *page) { - return true; + vm_fault_t ret = 0; + CALL_HCK_LITE_HOOK(xpm_integrity_validate_lhck, vma, vflags, + addr, page, &ret); + + return ret; } -static inline vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, - unsigned int vflags, unsigned long addr, struct page *page) +static inline +void xpm_integrity_update_hook(struct vm_area_struct *vma, + unsigned int vflags, struct page *page) { - return 0; + CALL_HCK_LITE_HOOK(xpm_integrity_update_lhck, vma, vflags, + page); + return; } -static inline vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, - unsigned int vflags, unsigned long addr, struct page *page) +static inline bool xpm_integrity_equal_hook(struct page *page, + struct page *kpage) { - return 0; + bool ret = true; + + CALL_HCK_LITE_HOOK(xpm_integrity_equal_lhck, page, kpage, + &ret); + + return ret; } -static inline void xpm_integrity_update(struct vm_area_struct *vma, - unsigned int vflags, struct page *page) { } +/** + * When inodes are destroyed, the corresponding cache must be destroyed + */ +static inline void xpm_delete_cache_node_hook(struct inode *file_node) +{ -#endif + CALL_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, file_node); + + return; +} #endif /* _XPM_H */ diff --git a/mm/ksm.c b/mm/ksm.c index 375c4080d4ed..1ccca1e6099b 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -1214,7 +1214,7 @@ static int try_to_merge_one_page(struct vm_area_struct *vma, goto out; /* Check XPM flags */ - if(!xpm_integrity_equal(page, kpage)) + if(!xpm_integrity_equal_hook(page, kpage)) goto out; /* diff --git a/mm/memory.c b/mm/memory.c index 3a6e0db5b320..d647c1320f4a 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -2944,7 +2944,7 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf) set_pte_at_notify(mm, vmf->address, vmf->pte, entry); update_mmu_cache(vma, vmf->address, vmf->pte); - xpm_integrity_update(vma, vmf->flags, new_page); + xpm_integrity_update_hook(vma, vmf->flags, new_page); if (old_page) { /* @@ -3041,7 +3041,7 @@ vm_fault_t finish_mkwrite_fault(struct vm_fault *vmf) return VM_FAULT_NOPAGE; } - if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + if (unlikely(xpm_integrity_validate_hook(vmf->vma, vmf->flags, vmf->address, vmf->page))) { pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_SIGSEGV; @@ -3099,7 +3099,7 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf) } } else { - if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + if (unlikely(xpm_integrity_validate_hook(vmf->vma, vmf->flags, vmf->address, vmf->page))){ pte_unmap_unlock(vmf->pte, vmf->ptl); put_page(vmf->page); @@ -3191,7 +3191,7 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf) */ unlock_page(page); - if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + if (unlikely(xpm_integrity_validate_hook(vmf->vma, vmf->flags, vmf->address, vmf->page))){ pte_unmap_unlock(vmf->pte, vmf->ptl); return VM_FAULT_SIGSEGV; @@ -3513,7 +3513,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) */ - if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, vmf->address, + if (unlikely(xpm_integrity_validate_hook(vmf->vma, vmf->flags, vmf->address, page))){ ret = VM_FAULT_SIGSEGV; goto out_nomap; @@ -3629,7 +3629,7 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) if (do_uxpte_page_fault(vmf, &entry)) goto oom; else{ - if(xpm_integrity_check(vma, vmf->flags, vmf->address, + if(xpm_integrity_check_hook(vma, vmf->flags, vmf->address, pte_page(entry))) return VM_FAULT_SIGSEGV; goto got_page; @@ -3711,7 +3711,7 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf) uxpte_set_present(vma, vmf->address); if(!pte_special(entry)){ - xpm_integrity_update(vma, vmf->flags, page); + xpm_integrity_update_hook(vma, vmf->flags, page); } set_pte_at(vma->vm_mm, vmf->address, vmf->pte, entry); @@ -3972,7 +3972,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct page *page) } /* check the confliction of xpm integrity flags*/ - if (unlikely(xpm_integrity_validate(vmf->vma, vmf->flags, + if (unlikely(xpm_integrity_validate_hook(vmf->vma, vmf->flags, vmf->address, page))) return VM_FAULT_SIGSEGV; diff --git a/mm/mprotect.c b/mm/mprotect.c index 847ee238ce00..7114de7e5a09 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -142,7 +142,7 @@ static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd, /* if exec added, check xpm integrity before set pte */ if((!pte_user_exec(oldpte) && pte_user_exec(ptent)) && - unlikely(xpm_integrity_validate(vma, 0, addr, + unlikely(xpm_integrity_validate_hook(vma, 0, addr, vm_normal_page(vma, addr, oldpte)))) continue; diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 5c8fd6b4a567..25414d6c9898 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -268,27 +268,29 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) return false; } -vm_fault_t xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, - unsigned long addr, struct page *page) +void xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page, vm_fault_t *ret) { + *ret = 0; if (!page) - return 0; + return; /* integrity violation: write a readonly page */ - if ((vflags & FAULT_FLAG_WRITE) && - (vma->vm_flags & VM_WRITE) && + if ((vflags & FAULT_FLAG_WRITE) && (vma->vm_flags & VM_WRITE) && PageXPMReadonly(page)) { report_integrity_event(TYPE_INTEGRITY_RO, vma, page); - return xpm_value(VM_FAULT_SIGSEGV); + *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); - return xpm_value(VM_FAULT_SIGSEGV); + *ret = xpm_value(VM_FAULT_SIGSEGV); + return; } - return 0; + return; } void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, @@ -306,32 +308,36 @@ void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, SetPageXPMReadonly(page); } -vm_fault_t xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, - unsigned long addr, struct page *page) +void xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, + unsigned long addr, struct page *page, vm_fault_t *ret) { - vm_fault_t ret; + *ret = 0; if (!page) - return 0; + return; - ret = xpm_integrity_check(vma, vflags, addr, page); - if (!ret) + xpm_integrity_check(vma, vflags, addr, page, ret); + if (!*ret) xpm_integrity_update(vma, vflags, page); - return ret; + return; } /* * check the integrity of these two pages, return true if equal, * otherwise false */ -bool xpm_integrity_equal(struct page *page, struct page *kpage) +void xpm_integrity_equal(struct page *page, struct page *kpage, bool *ret) { - if (!page || !kpage) - return true; + if (!page || !kpage){ + *ret = true; + return; + } - return !((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) || + *ret = !((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) || (PageXPMReadonly(page) != PageXPMReadonly(kpage))); + + return; } void xpm_register_hck_hooks(void) @@ -339,4 +345,14 @@ void xpm_register_hck_hooks(void) 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); + + REGISTER_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, + xpm_delete_cache_node); } -- Gitee From 2ee1485f189a568239ebfd4549dea68800623fa6 Mon Sep 17 00:00:00 2001 From: limerence Date: Thu, 4 May 2023 10:43:59 +0800 Subject: [PATCH 28/35] fix kernel coding style Signed-off-by: limerence --- include/linux/hck/lite_hck_xpm.h | 9 +++---- include/linux/xpm.h | 34 ++++++++++---------------- security/xpm/core/xpm_hck.c | 41 ++++++++++++-------------------- 3 files changed, 31 insertions(+), 53 deletions(-) diff --git a/include/linux/hck/lite_hck_xpm.h b/include/linux/hck/lite_hck_xpm.h index a174d1f6a77e..def3be8004d1 100644 --- a/include/linux/hck/lite_hck_xpm.h +++ b/include/linux/hck/lite_hck_xpm.h @@ -16,6 +16,9 @@ #define REGISTER_HCK_LITE_HOOK(name, probe) #define REGISTER_HCK_LITE_DATA_HOOK(name, probe, data) #else +DECLARE_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, + TP_PROTO(struct inode *file_node), + TP_ARGS(file_node)); DECLARE_HCK_LITE_HOOK(xpm_region_outer_lhck, TP_PROTO(unsigned long addr_start, unsigned long addr_end, @@ -45,12 +48,6 @@ DECLARE_HCK_LITE_HOOK(xpm_integrity_update_lhck, TP_PROTO(struct vm_area_struct *vma, unsigned int vflags, struct page *page), TP_ARGS(vma, vflags, page)); - -DECLARE_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, - TP_PROTO(struct inode *file_node), - TP_ARGS(file_node)); - - #endif /* CONFIG_HCK */ #endif /* _LITE_HCK_XPM_H */ diff --git a/include/linux/xpm.h b/include/linux/xpm.h index a0cb43fd7c16..2a799a51c2cf 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -13,6 +13,14 @@ #include #include +/** + * when inodes are destroyed, the corresponding cache must be destroyed + */ +static inline void xpm_delete_cache_node_hook(struct inode *file_node) +{ + CALL_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, file_node); +} + /** * check whether input address range is out of the xpm region */ @@ -41,18 +49,17 @@ static inline unsigned long xpm_get_unmapped_area_hook(unsigned long addr, } /* - * Check the confliction of a page's xpm flags, make sure a process will + * check the confliction of a page's xpm flags, make sure a process will * not map any RO page into a writable vma or a WT page into a execuable/XPM * memory region. */ - static inline vm_fault_t xpm_integrity_check_hook(struct vm_area_struct *vma, unsigned int vflags, unsigned long addr, struct page *page) { vm_fault_t ret = 0; + CALL_HCK_LITE_HOOK(xpm_integrity_check_lhck, vma, vflags, addr, page, &ret); - return ret; } @@ -61,9 +68,9 @@ vm_fault_t xpm_integrity_validate_hook(struct vm_area_struct *vma, unsigned int vflags, unsigned long addr, struct page *page) { vm_fault_t ret = 0; + CALL_HCK_LITE_HOOK(xpm_integrity_validate_lhck, vma, vflags, addr, page, &ret); - return ret; } @@ -71,9 +78,7 @@ static inline void xpm_integrity_update_hook(struct vm_area_struct *vma, unsigned int vflags, struct page *page) { - CALL_HCK_LITE_HOOK(xpm_integrity_update_lhck, vma, vflags, - page); - return; + CALL_HCK_LITE_HOOK(xpm_integrity_update_lhck, vma, vflags, page); } static inline bool xpm_integrity_equal_hook(struct page *page, @@ -81,21 +86,8 @@ static inline bool xpm_integrity_equal_hook(struct page *page, { bool ret = true; - CALL_HCK_LITE_HOOK(xpm_integrity_equal_lhck, page, kpage, - &ret); - + CALL_HCK_LITE_HOOK(xpm_integrity_equal_lhck, page, kpage, &ret); return ret; } -/** - * When inodes are destroyed, the corresponding cache must be destroyed - */ -static inline void xpm_delete_cache_node_hook(struct inode *file_node) -{ - - CALL_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, file_node); - - return; -} - #endif /* _XPM_H */ diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 25414d6c9898..5c4825da164c 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -152,7 +152,6 @@ static int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) return 0; } - static int xpm_common_check(struct vm_area_struct *vma, unsigned long prot) { int ret; @@ -181,16 +180,6 @@ static int xpm_mprotect_check(struct vm_area_struct *vma, return xpm_common_check(vma, prot); } -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_delete_cache_node(struct inode *file_node) { delete_exec_file_signature_info(file_node); @@ -271,7 +260,6 @@ static bool is_xpm_readonly_region(struct vm_area_struct *vma) void xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, unsigned long addr, struct page *page, vm_fault_t *ret) { - *ret = 0; if (!page) return; @@ -289,8 +277,6 @@ void xpm_integrity_check(struct vm_area_struct *vma, unsigned int vflags, *ret = xpm_value(VM_FAULT_SIGSEGV); return; } - - return; } void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, @@ -311,16 +297,12 @@ void xpm_integrity_update(struct vm_area_struct *vma, unsigned int vflags, void xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, unsigned long addr, struct page *page, vm_fault_t *ret) { - *ret = 0; - if (!page) return; xpm_integrity_check(vma, vflags, addr, page, ret); if (!*ret) xpm_integrity_update(vma, vflags, page); - - return; } /* @@ -329,30 +311,37 @@ void xpm_integrity_validate(struct vm_area_struct *vma, unsigned int vflags, */ void xpm_integrity_equal(struct page *page, struct page *kpage, bool *ret) { - if (!page || !kpage){ - *ret = true; + if (!page || !kpage) return; - } *ret = !((PageXPMWritetainted(page) != PageXPMWritetainted(kpage)) || (PageXPMReadonly(page) != PageXPMReadonly(kpage))); +} - return; +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*/ + /* 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); - - REGISTER_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, - xpm_delete_cache_node); } -- Gitee From c467475921063731f384a0cafa8e5d59475f4827 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Thu, 4 May 2023 12:11:19 +0800 Subject: [PATCH 29/35] update apply_xpm.sh Change-Id: I0c2a67e54b9205146e384e71311ee7b2716c42f3 --- security/xpm/OAT.xml | 74 ---------------------------------- security/xpm/README.OpenSource | 11 ----- security/xpm/apply_xpm.sh | 7 +--- 3 files changed, 2 insertions(+), 90 deletions(-) delete mode 100644 security/xpm/OAT.xml delete mode 100644 security/xpm/README.OpenSource diff --git a/security/xpm/OAT.xml b/security/xpm/OAT.xml deleted file mode 100644 index 5764102e1703..000000000000 --- a/security/xpm/OAT.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/security/xpm/README.OpenSource b/security/xpm/README.OpenSource deleted file mode 100644 index 35cf3e9c6b8d..000000000000 --- a/security/xpm/README.OpenSource +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "Name": "linux-5.10", - "License": "GPL-2.0+", - "License File": "COPYING", - "Version Number": "5.10.93", - "Owner": "shituanhui@huawei.com", - "Upstream URL": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/log/?h=linux-5.10.y", - "Description": "linux kernel 5.10" - } -] diff --git a/security/xpm/apply_xpm.sh b/security/xpm/apply_xpm.sh index 4eb0c74e526d..e8947a93534a 100755 --- a/security/xpm/apply_xpm.sh +++ b/security/xpm/apply_xpm.sh @@ -9,20 +9,17 @@ 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 +XPM_SOURCE_ROOT=$OHOS_SOURCE_ROOT/kernel/linux/common_modules/xpm function main() { pushd . - cd $KERNEL_BUILD_ROOT/include/linux/ - ln -s -f $(realpath --relative-to=$KERNEL_BUILD_ROOT/include/linux $XPM_SOURCE_ROOT/include)/xpm.h ./ - if [ ! -d " $KERNEL_BUILD_ROOT/security/xpm" ]; then mkdir $KERNEL_BUILD_ROOT/security/xpm fi - cd $KERNEL_BUILD_ROOT/security/xpm/ + cd $KERNEL_BUILD_ROOT/security/xpm ln -s -f $(realpath --relative-to=$KERNEL_BUILD_ROOT/security/xpm/ $XPM_SOURCE_ROOT)/* ./ popd -- Gitee From 238e76a148355dc4396d1e9cdddf07fee894db13 Mon Sep 17 00:00:00 2001 From: limerence Date: Thu, 4 May 2023 20:31:52 +0800 Subject: [PATCH 30/35] modify selinux tag Signed-off-by: limerence --- security/selinux/include/classmap.h | 2 +- security/xpm/README_zh.md | 2 +- security/xpm/core/xpm_hck.c | 13 ++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index c6cdaaaae867..1c6c59b92ab5 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -251,7 +251,7 @@ struct security_class_mapping secclass_map[] = { { "lockdown", { "integrity", "confidentiality", NULL } }, { "xpm", - { "execmem_no_sign", "anon_execmem", NULL } }, + { "exec_no_sign", "exec_anon_mem", NULL } }, { NULL } }; diff --git a/security/xpm/README_zh.md b/security/xpm/README_zh.md index c1b63d6bee95..8215bd610a39 100644 --- a/security/xpm/README_zh.md +++ b/security/xpm/README_zh.md @@ -60,7 +60,7 @@ XPM执行权限管控的主要代码目录结构如下: ## 管控规则说明 -针对当前不同应用的运行需求,通过selinux对相应的应用做标签化处理(execmem_no_sign & execmem_anon),实施不同的管控策略,具体如下: +针对当前不同应用的运行需求,通过selinux对相应的应用做标签化处理(exec_no_sign & execmem_anon),实施不同的管控策略,具体如下: 1. 普通应用类:强制检查二进制可执行文件和abc字节码的合法代码签名,限制申请匿名可执行内存 2. webview类:强制二进制可执行文件和abc字节码的合法代码签名,不限制匿名可执行内存的申请,允许拥有JIT能力 diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 5c4825da164c..06263402f668 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -50,7 +50,7 @@ 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__EXECMEM_NO_SIGN); + return xpm_avc_has_perm(SECCLASS_XPM, XPM__EXEC_NO_SIGN); return 0; } @@ -84,7 +84,7 @@ static int xpm_check_code_segment(bool is_exec, struct vm_area_struct *vma, return 0; } - return xpm_avc_has_perm(SECCLASS_XPM, XPM__EXECMEM_NO_SIGN); + return xpm_avc_has_perm(SECCLASS_XPM, XPM__EXEC_NO_SIGN); } static int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) @@ -125,23 +125,23 @@ static int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) static int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) { - int ret = -EPERM; + 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 ret; + return -EPERM; } /* anonymous executable permission need controled by selinux */ if (is_anon && (prot & PROT_EXEC)) { - ret = xpm_avc_has_perm(SECCLASS_XPM, XPM__ANON_EXECMEM); + 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 ret; + return -EPERM; } if (!is_anon && (prot & PROT_WRITE) && (prot & PROT_EXEC)) { @@ -328,7 +328,6 @@ 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, -- Gitee From 87fd5ed601b98ad87afd7d15f6de0d5e278183d4 Mon Sep 17 00:00:00 2001 From: limerence Date: Thu, 4 May 2023 22:32:17 +0800 Subject: [PATCH 31/35] modify check prot bug Signed-off-by: limerence --- security/xpm/core/xpm_hck.c | 4 ++-- security/xpm/core/xpm_misc.c | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 06263402f668..27e963991685 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -100,8 +100,8 @@ static int xpm_check_signature(struct vm_area_struct *vma, unsigned long prot) /* 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) { - report_file_event(TYPE_FORMAT_UNDEF, vma->vm_file); xpm_log_error("xpm get executable file signature info failed"); + report_file_event(TYPE_FORMAT_UNDEF, vma->vm_file); return ret; } @@ -140,8 +140,8 @@ static int xpm_check_prot(struct vm_area_struct *vma, unsigned long prot) if (ret) { xpm_log_error("anonymous mmap not allow exec permission"); report_mmap_event(TYPE_ANON_EXEC, vma, TYPE_ANON, prot); + return -EPERM; } - return -EPERM; } if (!is_anon && (prot & PROT_WRITE) && (prot & PROT_EXEC)) { diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index b8dda41f36d2..ba53fc8e3da1 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -41,9 +41,6 @@ static int xpm_set_region(unsigned long addr_base, unsigned long length) mm->xpm_region.addr_start = addr; mm->xpm_region.addr_end = addr + length; - xpm_log_debug("xpm set kernel region success: [0x%lx, 0x%lx]", - mm->xpm_region.addr_start, mm->xpm_region.addr_end); - exit: mmap_write_unlock(mm); return ret; -- Gitee From 26c65c694184775c48b319b0cebbe088745334a8 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Fri, 5 May 2023 09:36:00 +0800 Subject: [PATCH 32/35] fix a compatibility issuse Change-Id: Idf52729fa30fd2e5a3c4fe1abfee461cd73512e0 --- include/linux/xpm.h | 7 +++++++ mm/mprotect.c | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 2a799a51c2cf..122b56e410d8 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -90,4 +90,11 @@ static inline bool xpm_integrity_equal_hook(struct page *page, return ret; } +#ifdef CONFIG_ARM64 +#define pte_user_mkexec(oldpte, ptent) \ + ((!pte_user_exec(oldpte) && pte_user_exec(ptent))) +#else +#define pte_user_mkexec(oldpte, ptent) +#endif + #endif /* _XPM_H */ diff --git a/mm/mprotect.c b/mm/mprotect.c index 7114de7e5a09..d7131fcffc3b 100644 --- a/mm/mprotect.c +++ b/mm/mprotect.c @@ -141,9 +141,9 @@ static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd, } /* if exec added, check xpm integrity before set pte */ - if((!pte_user_exec(oldpte) && pte_user_exec(ptent)) && - unlikely(xpm_integrity_validate_hook(vma, 0, addr, - vm_normal_page(vma, addr, oldpte)))) + if(pte_user_mkexec(oldpte, ptent) && + unlikely(xpm_integrity_validate_hook(vma, 0, addr, + vm_normal_page(vma, addr, oldpte)))) continue; ptep_modify_prot_commit(vma, addr, pte, oldpte, ptent); -- Gitee From e4c28957c59b7dc59cfe6afd6a3ed5a433cab4dc Mon Sep 17 00:00:00 2001 From: zhangpan Date: Fri, 5 May 2023 09:39:46 +0800 Subject: [PATCH 33/35] fix update Change-Id: If7f25c3677677ac0f2edd8c95faa34d789097cd3 --- include/linux/xpm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/xpm.h b/include/linux/xpm.h index 122b56e410d8..b0afa497f1a7 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -94,7 +94,7 @@ static inline bool xpm_integrity_equal_hook(struct page *page, #define pte_user_mkexec(oldpte, ptent) \ ((!pte_user_exec(oldpte) && pte_user_exec(ptent))) #else -#define pte_user_mkexec(oldpte, ptent) +#define pte_user_mkexec(oldpte, ptent) 1 #endif #endif /* _XPM_H */ -- Gitee From 59d288c7262f2b396b872c975458252a84d2a101 Mon Sep 17 00:00:00 2001 From: limerence Date: Fri, 5 May 2023 13:03:41 +0800 Subject: [PATCH 34/35] coding style modify Signed-off-by: limerence --- fs/proc/base.c | 2 +- include/linux/hck/lite_hck_xpm.h | 3 +++ include/linux/xpm.h | 1 - include/trace/events/mmflags.h | 4 ++-- security/xpm/core/xpm_hck.c | 3 --- security/xpm/core/xpm_misc.c | 3 +++ security/xpm/core/xpm_module.c | 8 ++++---- security/xpm/core/xpm_report.c | 4 ++-- 8 files changed, 15 insertions(+), 13 deletions(-) diff --git a/fs/proc/base.c b/fs/proc/base.c index 93e62a07615f..96cfc8e0a8c5 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -3846,7 +3846,7 @@ static const struct pid_entry tid_base_stuff[] = { REG("sched_group_id", S_IRUGO|S_IWUGO, proc_pid_sched_group_id_operations), #endif #ifdef CONFIG_SECURITY_XPM - REG("xpm_region", S_IRUGO, proc_xpm_region_operations), + REG("xpm_region", S_IRUSR|S_IRGRP, proc_xpm_region_operations), #endif }; diff --git a/include/linux/hck/lite_hck_xpm.h b/include/linux/hck/lite_hck_xpm.h index def3be8004d1..da6934882b03 100644 --- a/include/linux/hck/lite_hck_xpm.h +++ b/include/linux/hck/lite_hck_xpm.h @@ -12,8 +12,11 @@ #include #ifndef CONFIG_HCK +#undef CALL_HCK_LITE_HOOK #define CALL_HCK_LITE_HOOK(name, args...) +#undef REGISTER_HCK_LITE_HOOK #define REGISTER_HCK_LITE_HOOK(name, probe) +#undef REGISTER_HCK_LITE_DATA_HOOK #define REGISTER_HCK_LITE_DATA_HOOK(name, probe, data) #else DECLARE_HCK_LITE_HOOK(xpm_delete_cache_node_lhck, diff --git a/include/linux/xpm.h b/include/linux/xpm.h index b0afa497f1a7..fd7f65bca590 100644 --- a/include/linux/xpm.h +++ b/include/linux/xpm.h @@ -6,7 +6,6 @@ #ifndef _XPM_H #define _XPM_H -#include "linux/mm_types.h" #include #include #include diff --git a/include/trace/events/mmflags.h b/include/trace/events/mmflags.h index d3a4a32c0807..c452bf5bd5b4 100644 --- a/include/trace/events/mmflags.h +++ b/include/trace/events/mmflags.h @@ -120,8 +120,8 @@ {1UL << PG_swapbacked, "swapbacked" }, \ {1UL << PG_unevictable, "unevictable" } \ IF_HAVE_PG_PURGEABLE(PG_purgeable, "purgeable" ) \ -IF_HAVE_PG_XPM_INTEGRITY(PG_xpm_readonly, "readonly") \ -IF_HAVE_PG_XPM_INTEGRITY(PG_xpm_writetainted, "writetained") \ +IF_HAVE_PG_XPM_INTEGRITY(PG_xpm_readonly, "readonly") \ +IF_HAVE_PG_XPM_INTEGRITY(PG_xpm_writetainted, "writetained") \ IF_HAVE_PG_MLOCK(PG_mlocked, "mlocked" ) \ IF_HAVE_PG_UNCACHED(PG_uncached, "uncached" ) \ IF_HAVE_PG_HWPOISON(PG_hwpoison, "hwpoison" ) \ diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 27e963991685..b64e18c19873 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -232,9 +232,6 @@ void xpm_get_unmapped_area(unsigned long addr, unsigned long len, info.align_mask = 0; info.align_offset = 0; - xpm_log_debug("flag(0x%lx), low_limit(0x%lx), high_limit(0x%lx)", - info.flags, info.low_limit, info.high_limit); - *ret = vm_unmapped_area(&info); } } diff --git a/security/xpm/core/xpm_misc.c b/security/xpm/core/xpm_misc.c index ba53fc8e3da1..73bf05eb6e2a 100755 --- a/security/xpm/core/xpm_misc.c +++ b/security/xpm/core/xpm_misc.c @@ -23,6 +23,9 @@ static int xpm_set_region(unsigned long addr_base, unsigned long length) unsigned long addr; struct mm_struct *mm = current->mm; + if (!mm) + return -EINVAL; + if (mmap_write_lock_killable(mm)) return -EINTR; diff --git a/security/xpm/core/xpm_module.c b/security/xpm/core/xpm_module.c index 4b875b7395b5..498932c2dc29 100755 --- a/security/xpm/core/xpm_module.c +++ b/security/xpm/core/xpm_module.c @@ -17,9 +17,6 @@ static int __init xpm_module_init(void) { int ret; - xpm_register_xpm_hooks(); - xpm_register_hck_hooks(); - ret = xpm_register_misc_device(); if (ret) { xpm_log_error("xpm register misc device failed, ret = %d", ret); @@ -34,8 +31,11 @@ static int __init xpm_module_init(void) report_init_event(TYPE_DEBUGFS_UNINIT); return ret; } - xpm_log_info("xpm module init success"); + xpm_register_xpm_hooks(); + xpm_register_hck_hooks(); + + xpm_log_info("xpm module init success"); return 0; } diff --git a/security/xpm/core/xpm_report.c b/security/xpm/core/xpm_report.c index 17f47df89298..c1a1432ad55f 100644 --- a/security/xpm/core/xpm_report.c +++ b/security/xpm/core/xpm_report.c @@ -199,8 +199,8 @@ static int report_event_inner(xpm_event_type type, static int xpm_report_event(xpm_event_type type, struct xpm_event_param *param) { int ret; - event_info *sg_event = NULL; - char *buf = NULL; + event_info *sg_event; + char *buf; if (!(xpm_event[type].set_content)) { xpm_log_error("type [%d] set content func invalid", type); -- Gitee From 543f6a72355d2bd34ae6048393f4e3ec60668d64 Mon Sep 17 00:00:00 2001 From: zhangpan Date: Fri, 5 May 2023 16:21:09 +0800 Subject: [PATCH 35/35] optimize xpm_integrity_equal Change-Id: I6d0687e2f6cb93156e6db27b350815bbf7df8d00 --- security/xpm/core/xpm_hck.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/xpm/core/xpm_hck.c b/security/xpm/core/xpm_hck.c index 27e963991685..1a5295816db3 100644 --- a/security/xpm/core/xpm_hck.c +++ b/security/xpm/core/xpm_hck.c @@ -314,8 +314,8 @@ 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))); + *ret = ((PageXPMWritetainted(page) == PageXPMWritetainted(kpage)) && + (PageXPMReadonly(page) == PageXPMReadonly(kpage))); } static struct security_hook_list xpm_hooks[] __lsm_ro_after_init = { -- Gitee