From 8006398aaab567664062ed6edab5388b976b8c3f Mon Sep 17 00:00:00 2001 From: Jvle Date: Wed, 28 May 2025 11:12:32 +0800 Subject: [PATCH 01/13] upatch-diff: sync from 25.03 to 24.03-LTS Signed-off-by: Jvle --- upatch-diff/create-diff-object.c | 28 +++++++++++++++++++++- upatch-diff/elf-common.c | 41 +++++++++++++++++++++++++++++--- upatch-diff/elf-common.h | 2 ++ upatch-diff/elf-correlate.c | 6 +++++ upatch-diff/elf-insn.c | 8 +++++++ upatch-diff/elf-insn.h | 5 ++++ upatch-diff/upatch-elf.c | 10 ++++++++ upatch-diff/upatch-elf.h | 1 + 8 files changed, 97 insertions(+), 4 deletions(-) diff --git a/upatch-diff/create-diff-object.c b/upatch-diff/create-diff-object.c index 9e4504e9..886d8b21 100644 --- a/upatch-diff/create-diff-object.c +++ b/upatch-diff/create-diff-object.c @@ -434,7 +434,8 @@ static void replace_section_syms(struct upatch_elf *uelf) !is_text_section(sym->sec) && (rela->type == R_X86_64_32S || rela->type == R_X86_64_32 || - rela->type == R_AARCH64_ABS64) && + rela->type == R_AARCH64_ABS64 || + rela->type == R_RISCV_64) && rela->addend == (long)sym->sec->sh.sh_size && end == (long)sym->sec->sh.sh_size) { ERROR("Relocation refer end of data sections."); @@ -590,6 +591,12 @@ static void include_symbol(struct symbol *sym) if ((sym->status != SAME) || (sym->type == STT_SECTION)) { include_section(sym->sec); } +#ifdef __riscv + /* .L symbols not exist in EXE. If they are included, so are their sections. */ + else if (sym->sec && !sym->sec->include && !strncmp(sym->name, ".L", 2)) { + include_section(sym->sec); + } +#endif } static void include_section(struct section *sec) @@ -749,6 +756,23 @@ static void verify_patchability(struct upatch_elf *uelf) } } +/* + * These types are for linker optimization and memory layout. + * They have no associated symbols and their names are empty + * string which would mismatch running-elf symbols in later + * lookup_relf(). Drop these useless items now. + */ +static void rv_drop_useless_rela(struct section *relasec) +{ + struct rela *rela, *saferela; + list_for_each_entry_safe(rela, saferela, &relasec->relas, list) + if (rela->type == R_RISCV_RELAX || rela->type == R_RISCV_ALIGN) { + list_del(&rela->list); + memset(rela, 0, sizeof(*rela)); + free(rela); + } +} + static void migrate_included_elements(struct upatch_elf *uelf_patched, struct upatch_elf *uelf_out) { @@ -776,6 +800,8 @@ static void migrate_included_elements(struct upatch_elf *uelf_patched, if (sec->sym && !sec->sym->include) { sec->sym = NULL; // break link to non-included section symbol } + } else if (uelf_patched->arch == RISCV64) { + rv_drop_useless_rela(sec); } } diff --git a/upatch-diff/elf-common.c b/upatch-diff/elf-common.c index c5d8f619..22d12ca9 100644 --- a/upatch-diff/elf-common.c +++ b/upatch-diff/elf-common.c @@ -26,6 +26,35 @@ #include "elf-common.h" +#ifdef __riscv +/* + * .L local symbols are named as ".L" + "class prefix" + "number". + * The numbers are volatile due to code change. + * Compare class prefix(composed of letters) only. + */ +static int mangled_strcmp_dot_L(char *str1, char *str2) +{ + if (!*str2 || strncmp(str2, ".L", 2)) { + return 1; + } + + /* RISCV_FAKE_LABEL_NAME matched exactly */ + if (!strcmp(str1, ".L0 ") || !strcmp(str2, ".L0 ")) { + return strcmp(str1, str2); + } + + char *p = str1 + 2; + char *q = str2 + 2; + while (*p < '0' || *p > '9') p++; + while (*q < '0' || *q > '9') q++; + if ((p - str1 != q - str2) || strncmp(str1, str2, (size_t)(p - str1))) { + return 1; + } + + return 0; +} +#endif + static bool is_dynamic_debug_symbol(struct symbol *sym) { static const char *SEC_NAMES[] = { @@ -122,6 +151,12 @@ int mangled_strcmp(char *str1, char *str2) return strcmp(str1, str2); } +#ifdef __riscv + if (!strncmp(str1, ".L", 2)) { + return mangled_strcmp_dot_L(str1, str2); + } +#endif + while (*str1 == *str2) { if (!*str2) { return 0; @@ -180,7 +215,6 @@ int offset_of_string(struct list_head *list, char *name) ALLOC_LINK(string, list); string->name = name; - return index; } @@ -192,16 +226,17 @@ bool is_gcc6_localentry_bundled_sym(struct upatch_elf *uelf) return false; case X86_64: return false; + case RISCV64: + return false; default: ERROR("unsupported arch"); } - return false; } bool is_mapping_symbol(struct upatch_elf *uelf, struct symbol *sym) { - if (uelf->arch != AARCH64) { + if ((uelf->arch != AARCH64) && (uelf->arch != RISCV64)) { return false; } if (sym->name && sym->name[0] == '$' && diff --git a/upatch-diff/elf-common.h b/upatch-diff/elf-common.h index 3cebd32c..f252cb95 100644 --- a/upatch-diff/elf-common.h +++ b/upatch-diff/elf-common.h @@ -278,6 +278,8 @@ static inline unsigned int absolute_rela_type(struct upatch_elf *uelf) return R_AARCH64_ABS64; case X86_64: return R_X86_64_64; + case RISCV64: + return R_RISCV_64; default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-correlate.c b/upatch-diff/elf-correlate.c index dcf3cfa5..8e2f7659 100644 --- a/upatch-diff/elf-correlate.c +++ b/upatch-diff/elf-correlate.c @@ -83,6 +83,12 @@ void upatch_correlate_symbols(struct upatch_elf *uelf_source, continue; } + /* on RISC-V: .L symbols should not change section */ + if (uelf_source->arch == RISCV64 && !strncmp(sym_orig->name, ".L", 2) && + sym_orig->sec && sym_orig->sec->twin != sym_patched->sec) { + continue; + } + if (is_mapping_symbol(uelf_source, sym_orig)) { continue; } diff --git a/upatch-diff/elf-insn.c b/upatch-diff/elf-insn.c index 6fabd887..466a00c9 100644 --- a/upatch-diff/elf-insn.c +++ b/upatch-diff/elf-insn.c @@ -67,6 +67,8 @@ long rela_target_offset(struct upatch_elf *uelf, struct section *relasec, struct long add_off; switch (uelf->arch) { + case RISCV64: + /* fall through */ case AARCH64: add_off = 0; break; @@ -106,6 +108,12 @@ unsigned int insn_length(struct upatch_elf *uelf, void *addr) insn_init(&decoded_insn, addr, 1); insn_get_length(&decoded_insn); return decoded_insn.length; + case RISCV64: + /* LSB 2 bits distinguish insn size. Now only RV32, RVC supported. */ + if ((*(char *)addr & 0x3) == 0x3) { + return RISCV64_INSN_LEN_4; + } + return RISCV64_INSN_LEN_2; default: ERROR("unsupported arch"); } diff --git a/upatch-diff/elf-insn.h b/upatch-diff/elf-insn.h index ac48ad03..fdbc2e34 100644 --- a/upatch-diff/elf-insn.h +++ b/upatch-diff/elf-insn.h @@ -32,8 +32,13 @@ struct section; struct rela; struct insn; +// arm #define ARM64_INSTR_LEN 4 +// riscv +#define RISCV64_INSN_LEN_4 4 +#define RISCV64_INSN_LEN_2 2 + void rela_insn(const struct section *sec, const struct rela *rela, struct insn *insn); /* diff --git a/upatch-diff/upatch-elf.c b/upatch-diff/upatch-elf.c index c21b6ecc..331504bb 100644 --- a/upatch-diff/upatch-elf.c +++ b/upatch-diff/upatch-elf.c @@ -321,6 +321,16 @@ void uelf_open(struct upatch_elf *uelf, const char *name) case EM_X86_64: uelf->arch = X86_64; break; + case EM_RISCV: + /* + | Val | Macros | Description | + | 1 | ELFCLASS32 | riscv32 | + | 2 | ELFCLASS64 | riscv64 | + */ + if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { + uelf->arch = RISCV64; + } + break; default: ERROR("Unsupported architecture"); } diff --git a/upatch-diff/upatch-elf.h b/upatch-diff/upatch-elf.h index 858cfa92..b34c1af6 100644 --- a/upatch-diff/upatch-elf.h +++ b/upatch-diff/upatch-elf.h @@ -41,6 +41,7 @@ struct symbol; enum architecture { X86_64 = 0x1 << 0, AARCH64 = 0x1 << 1, + RISCV64 = 0x1 << 2, }; enum data_source { -- Gitee From 240c0077a2b4b8a0898d29b2e68e595dbdc1dffc Mon Sep 17 00:00:00 2001 From: Jvle Date: Wed, 28 May 2025 11:13:19 +0800 Subject: [PATCH 02/13] upatch-manage: sync from 25.03 to 24.03-LTS Signed-off-by: Jvle --- upatch-manage/arch/riscv64/insn.h | 123 ++++++++++++ upatch-manage/arch/riscv64/process.h | 28 +++ upatch-manage/arch/riscv64/ptrace.c | 219 +++++++++++++++++++++ upatch-manage/arch/riscv64/relocation.c | 246 ++++++++++++++++++++++++ upatch-manage/arch/riscv64/resolve.c | 154 +++++++++++++++ upatch-manage/upatch-patch.c | 22 +++ upatch-manage/upatch-ptrace.c | 4 + upatch-manage/upatch-ptrace.h | 8 + 8 files changed, 804 insertions(+) create mode 100644 upatch-manage/arch/riscv64/insn.h create mode 100644 upatch-manage/arch/riscv64/process.h create mode 100644 upatch-manage/arch/riscv64/ptrace.c create mode 100644 upatch-manage/arch/riscv64/relocation.c create mode 100644 upatch-manage/arch/riscv64/resolve.c diff --git a/upatch-manage/arch/riscv64/insn.h b/upatch-manage/arch/riscv64/insn.h new file mode 100644 index 00000000..8ab4daf7 --- /dev/null +++ b/upatch-manage/arch/riscv64/insn.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 laokz + */ + +#ifndef _ARCH_RISCV64_INSN_H +#define _ARCH_RISCV64_INSN_H + +static inline unsigned set_utype_imm(unsigned ins, unsigned long imm) +{ + /* + imm[11] to counteract lo12 sign extension in next instruction */ + unsigned long temp_imm = imm; + temp_imm += (temp_imm & 0x800) << 1; + return (temp_imm & 0xfffff000) | (ins & 0xfff); +} + +static inline unsigned set_itype_imm(unsigned ins, unsigned long imm) +{ + return ((imm & 0xfff) << 20) | (ins & 0xfffff); +} + +static inline unsigned set_stype_imm(unsigned ins, unsigned long imm) +{ + /* rs2 rs1 func opcode + ins: imm[11-8,7-5] 1,1111, 1111,1 111, imm[4-1,0] 111,1111 + ins mask 0 1 f f f 0 7 f + + imm bit no. 11-----5 4---0 + 1111,111 1,1111 + imm mask fe0 1f + + ==>imm bit no. 31----25 11--7 + */ + return (ins & 0x1fff07f) | + ((imm & 0xfe0) << (31 - 11)) | ((imm & 0x1f) << (11 - 4)); +} + +static inline unsigned set_jtype_imm(unsigned ins, unsigned long imm) +{ + /* + imm bit no. 20 19------12 11 10---------1 + 1, 1111,1111, 1 111,1111,111 0 + mask 100000 ff000 800 7fe + + ==>imm bit no. 31 19------12 20 30---------21 + */ + return (ins & 0xfff) | + ((imm & 0x100000) << (31 - 20)) | (imm & 0xff000) | + ((imm & 0x800) << (20 - 11)) | ((imm & 0x7fe) << (30 - 10)); +} + +static inline unsigned set_btype_imm(unsigned ins, unsigned long imm) +{ + /* rs2 rs1 func opcode + ins: imm[12 10-8,7-5] 1,1111, 1111,1 111, imm[4-1,11] 111,1111 + ins mask 0 1 f f f 0 7 f + + imm bit no. 12 11 10----5 4--1 + 1, 1 111,111 1,111 0 + imm mask 1000 800 7e0 1e + + ==>imm bit no. 31 7 30---25 11-8 + */ + return (ins & 0x01fff07f) | + ((imm & 0x1000) << (31 - 12)) | ((imm & 0x800) >> (11 - 7)) | + ((imm & 0x7e0) << (30 - 10)) | ((imm & 0x1e) << (11 - 4)); +} + +static inline unsigned short set_cjtype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm opcode + ins: 111 offset[11,4 9 8 10, 6 7 3 2, 1 5] 11 + ins mask e 0 0 3 + + imm bit no. 11 10 9-8 7 6 5 4 3-1 + 1 1 11, 1 1 1 1, 111 0 + imm mask 800 400 300 80 40 20 10 e + + ==>imm bit no. 12 8 10-9 6 7 2 11 5-3 + */ + return (ins & 0xe003) | + ((imm & 0x800) << (12 - 11)) | ((imm & 0x400) >> (10 - 8)) | + ((imm & 0x300) << (10 - 9)) | ((imm & 0x80) >> (7 - 6)) | + ((imm & 0x40) << (7 - 6)) | ((imm & 0x20) >> (5 - 2)) | + ((imm & 0x10) << (11 - 4)) | ((imm & 0xe) << (5 - 3)); +} + +/* only support C.LUI */ +static inline unsigned short set_citype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm[17] rd imm[16:12] opcode + ins: 111 imm[17], 1111,1 imm[16-14,13-12] 11 + ins mask e f 8 3 + + imm bit no. 17 16--12 + 1 1,1111 + imm mask 20000 1f000 + + ==>imm bit no. 12 6---2 + */ + return (ins & 0xef83) | + ((imm & 0x20000) >> (17 - 12)) | ((imm & 0x1f000) >> (16 - 6)); +} + +/* only support C.BEQZ C.BNEZ */ +static inline unsigned short set_cbtype_imm(unsigned short ins, unsigned long imm) +{ + /* funct3 imm[8 4 3] rs imm[7 6 2 1 5] opcode + ins: 111 0,0 0 11,1 0 0 0,0 0 11 + ins mask e 3 8 3 + + imm bit no. 8 , 7 6 5 4,3 2 1 0 + imm mask 100 c0 20 18 6 + + ==>imm bit no. 12 6-5 2 11-10 4-3 + */ + return (ins & 0xe383) | + ((imm & 0x100) << (12 - 8)) | ((imm & 0xc0) >> (7 - 6)) | + ((imm & 0x20) >> (5 - 2)) | ((imm & 0x18) << (11 - 4)) | + ((imm & 0x6) << (4 - 2)); +} + +#endif diff --git a/upatch-manage/arch/riscv64/process.h b/upatch-manage/arch/riscv64/process.h new file mode 100644 index 00000000..91926fa2 --- /dev/null +++ b/upatch-manage/arch/riscv64/process.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __PROCESS__ +#define __PROCESS__ + +#ifndef MAX_DISTANCE +#define MAX_DISTANCE 0x80000000 +#endif + +#endif diff --git a/upatch-manage/arch/riscv64/ptrace.c b/upatch-manage/arch/riscv64/ptrace.c new file mode 100644 index 00000000..2ccdc9ef --- /dev/null +++ b/upatch-manage/arch/riscv64/ptrace.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +#include "upatch-ptrace.h" + +int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc) +{ + struct iovec regs_iov; + struct user_regs_struct regs; + + regs_iov.iov_base = ®s; + regs_iov.iov_len = sizeof(regs); + + if (ptrace(PTRACE_GETREGSET, pid, + (void *)NT_PRSTATUS, (void *)®s_iov) < 0) { + log_error("Cannot get regs from %d\n", pid); + return -1; + } + *sp = (unsigned long)regs.sp; + *pc = (unsigned long)regs.pc; + return 0; +} + +static long read_gregs(int pid, struct user_regs_struct *regs) +{ + struct iovec data = {regs, sizeof(*regs)}; + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &data) == -1) { + log_error("ptrace(PTRACE_GETREGSET)"); + return -1; + } + return 0; +} + +static long write_gregs(int pid, struct user_regs_struct *regs) +{ + struct iovec data = {regs, sizeof(*regs)}; + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &data) == -1) { + log_error("ptrace(PTRACE_SETREGSET)"); + return -1; + } + return 0; +} + +long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *pctx, int nr, + unsigned long arg1, unsigned long arg2, + unsigned long arg3, unsigned long arg4, + unsigned long arg5, unsigned long arg6, + unsigned long *res) +{ + struct user_regs_struct regs; + unsigned char syscall[] = { + 0x73, 0x00, 0x00, 0x00, // ecall + 0x73, 0x00, 0x10, 0x00, // ebreak + }; + long ret; + + log_debug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); + regs.a7 = (unsigned long)nr; + regs.a0 = arg1; + regs.a1 = arg2; + regs.a2 = arg3; + regs.a3 = arg4; + regs.a4 = arg5; + regs.a5 = arg6; + + ret = upatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); + if (ret == 0) + *res = regs.a0; + + return ret; +} + +long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, + const unsigned char *code, size_t codelen, + struct user_regs_struct *pregs, + int (*func)(struct upatch_ptrace_ctx *pctx, + const void *data), + const void *data) +{ + struct user_regs_struct orig_regs, regs; + if (!codelen) { + log_error("Invalid codelen\n"); + return -1; + } + unsigned char *orig_code = (unsigned char *)malloc(sizeof(*orig_code) * codelen); + if (orig_code == NULL) { + log_error("Malloc orig_code failed\n"); + return -1; + } + long ret; + struct upatch_process *proc = pctx->proc; + unsigned long libc_base = proc->libc_base; + + ret = read_gregs(pctx->pid, &orig_regs); + if (ret < 0) { + free(orig_code); + return -1; + } + ret = upatch_process_mem_read(proc, libc_base, + (unsigned long *)orig_code, codelen); + if (ret < 0) { + log_error("can't peek original code - %d\n", pctx->pid); + free(orig_code); + return -1; + } + ret = upatch_process_mem_write(proc, (const unsigned long *)code, libc_base, + codelen); + if (ret < 0) { + log_error("can't poke syscall code - %d\n", pctx->pid); + goto poke_back; + } + + regs = orig_regs; + regs.pc = libc_base; + + copy_regs(®s, pregs); + + ret = write_gregs(pctx->pid, ®s); + if (ret < 0) { + goto poke_back; + } + + ret = func(pctx, data); + if (ret < 0) { + log_error("failed call to func\n"); + goto poke_back; + } + + ret = read_gregs(pctx->pid, ®s); + if (ret < 0) { + goto poke_back; + } + + ret = write_gregs(pctx->pid, &orig_regs); + if (ret < 0) { + goto poke_back; + } + + *pregs = regs; + +poke_back: + upatch_process_mem_write(proc, (unsigned long *)orig_code, libc_base, + codelen); + free(orig_code); + return ret; +} + +void copy_regs(struct user_regs_struct *dst, struct user_regs_struct *src) +{ +#define COPY_REG(x) dst->x = src->x + COPY_REG(a0); + COPY_REG(a1); + COPY_REG(a2); + COPY_REG(a3); + COPY_REG(a4); + COPY_REG(a5); + COPY_REG(a6); + COPY_REG(a7); +#undef COPY_REG +} + +#define UPATCH_INSN_LEN 8 +#define UPATCH_ADDR_LEN 8 +#define ORIGIN_INSN_LEN (UPATCH_INSN_LEN + UPATCH_ADDR_LEN) + +size_t get_origin_insn_len() +{ + return ORIGIN_INSN_LEN; +} + +size_t get_upatch_insn_len() +{ + return UPATCH_INSN_LEN; +} + +size_t get_upatch_addr_len() +{ + return UPATCH_ADDR_LEN; +} + +/* + * On RISC-V, there must be 3 instructors(12 bytes) to jump to + * arbitrary address. The core upatch-manage limit jump instructor + * to one long(8 bytes), for us is +-2G range. + */ +unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr) +{ + unsigned long offset; + unsigned int insn0, insn4; + + offset = new_addr - old_addr; + offset += (offset & 0x800) << 1; + insn0 = 0xf97 | (offset & 0xfffff000); // auipc t6, off[20] + insn4 = 0xf8067 | ((offset & 0xfff) << 20); // jalr zero, off[12](t6) + return (unsigned long)(insn0 | ((unsigned long)insn4 << 32)); +} diff --git a/upatch-manage/arch/riscv64/relocation.c b/upatch-manage/arch/riscv64/relocation.c new file mode 100644 index 00000000..ba825992 --- /dev/null +++ b/upatch-manage/arch/riscv64/relocation.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "insn.h" +#include "upatch-relocation.h" +#include "upatch-resolve.h" + +/* + * In PCREL_LO12 relocation entity, its corresponding symbol's value + * points to the ..._HI20 instruction, where the LO12 part of the + * immediate is part of the ..._HI20 symbol value. + */ +static unsigned long find_pcrel_hi_value(GElf_Rela *r, int idx, GElf_Sym *st, unsigned long v) +{ + int i = idx; + r--; + for (; i > 0; i--, r--) { + if ((r->r_offset == v) && + ((GELF_R_TYPE(r->r_info) == R_RISCV_PCREL_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GOT_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GD_HI20) || + (GELF_R_TYPE(r->r_info) == R_RISCV_GOT_HI20))) + return st[GELF_R_SYM(r->r_info)].st_value; + } + + /* should never happen */ + log_error("Not found no. %d rela's corresponding HI20\n", idx); + return 0; +} + +/* + * The patch is a .o file, has only static relocations, all symbols + * have been resolved with our jump table act as got/plt. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wsign-conversion" +int apply_relocate_add(struct upatch_elf *uelf, unsigned int symindex, + unsigned int relsec) +{ + unsigned int i; + GElf_Sym *sym, *symtab; + char const *sym_name; + unsigned long uloc_sec; + void *loc; + void *uloc; + u64 val; + GElf_Shdr *shdrs = (void *)uelf->info.shdrs; + GElf_Rela *rel = (void *)shdrs[relsec].sh_addr; + + symtab = (GElf_Sym *)shdrs[symindex].sh_addr; + for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { + /* loc corresponds to P in the kernel space */ + loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + + /* uloc corresponds P in user space */ + uloc_sec = shdrs[shdrs[relsec].sh_info].sh_addralign; + uloc = (void *)uloc_sec + rel[i].r_offset; + + /* sym is the ELF symbol we're referring to */ + sym = symtab + GELF_R_SYM(rel[i].r_info); + if (GELF_ST_TYPE(sym->st_info) == STT_SECTION && + sym->st_shndx < uelf->info.hdr->e_shnum) + sym_name = uelf->info.shstrtab + + shdrs[sym->st_shndx].sh_name; + else + sym_name = uelf->strtab + sym->st_name; + + /* val corresponds to (S + A) */ + val = (s64)(sym->st_value + rel[i].r_addend); + log_debug( + "upatch: reloc symbol, name=%s, k_addr=0x%lx, u_addr=0x%lx, " + "r_offset=0x%lx, st_value=0x%lx, r_addend=0x%lx \n", + sym_name, shdrs[shdrs[relsec].sh_info].sh_addr, + uloc_sec, rel[i].r_offset, sym->st_value, rel[i].r_addend); + + /* Perform the static relocation. */ + switch (GELF_R_TYPE(rel[i].r_info)) { + case R_RISCV_NONE: + case R_RISCV_TPREL_ADD: + break; + + case R_RISCV_64: + *(unsigned long *)loc = val; + break; + + /* seems no need to recalculate as it should confined in the same func */ + case R_RISCV_BRANCH: + val -= (unsigned long)uloc; + if ((signed)val >= 4096 || (signed)val < -4096) + goto overflow; + *(unsigned *)loc = set_btype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_JAL: + val -= (unsigned long)uloc; + if ((signed)val >= (1 << 20) || (signed)val < -(1 << 20)) + goto overflow; + *(unsigned *)loc = set_jtype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_CALL: + case R_RISCV_CALL_PLT: + // in our jump table, must not overflow + val -= (unsigned long)uloc; + *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); + *(unsigned *)(loc + 4) = set_itype_imm(*(unsigned *)(loc + 4), val); + break; + + case R_RISCV_GOT_HI20: + case R_RISCV_TLS_GOT_HI20: + case R_RISCV_TLS_GD_HI20: + case R_RISCV_PCREL_HI20: + val -= (unsigned long)uloc; // fall through + case R_RISCV_HI20: + case R_RISCV_TPREL_HI20: + if ((long)val != (long)(int)val) + goto overflow; + *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_PCREL_LO12_I: + val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); + if (val == 0) + goto overflow; + val -= sym->st_value; // fall through + case R_RISCV_LO12_I: + case R_RISCV_TPREL_LO12_I: + *(unsigned *)loc = set_itype_imm(*(unsigned *)loc, val); + break; + + case R_RISCV_PCREL_LO12_S: + val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); + if (val == 0) + goto overflow; + val -= sym->st_value; // fall through + case R_RISCV_LO12_S: + case R_RISCV_TPREL_LO12_S: + *(unsigned *)loc = set_stype_imm(*(unsigned *)loc, val); + break; + + /* inner function label calculation, must not overflow */ + case R_RISCV_ADD8: + *(char *)loc += val; + break; + case R_RISCV_ADD16: + *(short *)loc += val; + break; + case R_RISCV_ADD32: + *(int *)loc += val; + break; + case R_RISCV_ADD64: + *(long *)loc += val; + break; + + case R_RISCV_SUB8: + *(char *)loc -= val; + break; + case R_RISCV_SUB16: + *(short *)loc -= val; + break; + case R_RISCV_SUB32: + *(int *)loc -= val; + break; + case R_RISCV_SUB64: + *(long *)loc -= val; + break; + + case R_RISCV_RVC_BRANCH: + val -= (unsigned long)uloc; + if ((signed)val >= 256 || (signed)val < -256) + goto overflow; + *(unsigned short *)loc = set_cbtype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_RVC_JUMP: + val -= (unsigned long)uloc; + if ((signed)val >= 2048 || (signed)val < -2048) + goto overflow; + *(unsigned short *)loc = set_cjtype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_RVC_LUI: + if ((signed)val >= (1 << 17) || (signed)val < -(1 << 17) || (val & 0x3f000) == 0) + goto overflow; + *(unsigned short *)loc = set_citype_imm(*(unsigned short *)loc, val); + break; + + case R_RISCV_SET8: + *(char *)loc = val; + break; + case R_RISCV_SET16: + *(short *)loc = val; + break; + case R_RISCV_32_PCREL: + case R_RISCV_PLT32: + val -= (unsigned long)uloc; // fall through + case R_RISCV_32: + case R_RISCV_SET32: + if ((long)val != (long)(int)val) + goto overflow; + *(int *)loc = val; + break; + + case R_RISCV_SUB6: + char w6 = (*(char *)loc - (char)val) & 0x3f; + *(char *)loc = (*(char *)loc & 0xc0) | w6; + break; + case R_RISCV_SET6: + *(char *)loc = (*(char *)loc & 0xc0) | (val & 0x3f); + break; + + default: + log_error("upatch: unsupported RELA relocation: %lu\n", + GELF_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + } + return 0; + +overflow: + log_error("upatch: overflow in relocation type %d val %lx\n", + (int)GELF_R_TYPE(rel[i].r_info), val); + return -ENOEXEC; +} +#pragma GCC diagnostic pop diff --git a/upatch-manage/arch/riscv64/resolve.c b/upatch-manage/arch/riscv64/resolve.c new file mode 100644 index 00000000..0b4aa8ba --- /dev/null +++ b/upatch-manage/arch/riscv64/resolve.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch-manage + * Copyright (C) 2024 ISCAS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include "log.h" +#include "upatch-ptrace.h" +#include "upatch-resolve.h" + +/* + * auipc t6,0x0 + * ld t6,16(t6) # addr + * jr t6 + * undefined + */ +#define RISCV64_JMP_TABLE_JUMP0 0x010fbf8300000f97 +#define RISCV64_JMP_TABLE_JUMP1 0x000f8067 + +struct upatch_jmp_table_entry { + unsigned long inst[2]; + unsigned long addr[2]; +}; + +unsigned int get_jmp_table_entry() +{ + return sizeof(struct upatch_jmp_table_entry); +} + +static unsigned long setup_jmp_table(struct upatch_elf *uelf, + unsigned long jmp_addr, + unsigned long origin_addr) +{ + struct upatch_jmp_table_entry *table = + uelf->core_layout.kbase + uelf->jmp_offs; + unsigned int index = uelf->jmp_cur_entry; + if (index >= uelf->jmp_max_entry) { + log_error("jmp table overflow\n"); + return 0; + } + + table[index].inst[0] = RISCV64_JMP_TABLE_JUMP0; + table[index].inst[1] = RISCV64_JMP_TABLE_JUMP1; + table[index].addr[0] = jmp_addr; + table[index].addr[1] = origin_addr; + uelf->jmp_cur_entry++; + return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + + index * sizeof(struct upatch_jmp_table_entry)); +} + +unsigned long setup_got_table(struct upatch_elf *uelf, + unsigned long jmp_addr, + unsigned long tls_addr) +{ + struct upatch_jmp_table_entry *table = + uelf->core_layout.kbase + uelf->jmp_offs; + unsigned int index = uelf->jmp_cur_entry; + + if (index >= uelf->jmp_max_entry) { + log_error("got table overflow\n"); + return 0; + } + + table[index].inst[0] = jmp_addr; + table[index].inst[1] = tls_addr; + table[index].addr[0] = 0xffffffff; + table[index].addr[1] = 0xffffffff; + uelf->jmp_cur_entry++; + return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + + index * sizeof(struct upatch_jmp_table_entry)); +} + +unsigned long insert_plt_table(struct upatch_elf *uelf, struct object_file *obj, + unsigned long r_type __attribute__((unused)), unsigned long addr) +{ + unsigned long jmp_addr = 0xffffffff; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, + sizeof(jmp_addr))) { + log_error("copy address failed\n"); + goto out; + } + + elf_addr = setup_jmp_table(uelf, jmp_addr, (unsigned long)addr); + + log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, + jmp_addr, tls_addr); + +out: + return elf_addr; +} + +unsigned long insert_got_table(struct upatch_elf *uelf, struct object_file *obj, + unsigned long r_type, unsigned long addr) +{ + unsigned long jmp_addr = 0xffffffff; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, + sizeof(jmp_addr))) { + log_error("copy address failed\n"); + goto out; + } + + /* + * Addr with this type means the symbol is a dynamic TLS variable. + * Addr points to a GOT entry(16 bytes) having type + * + * typedef struct { + * unsigned long int ti_module; + * unsigned long int ti_offset; + * } tls_index; + * + * We also need copy ti_offset to our jump table. + * + * The corresponding symbol will associate with TLS_GD_HI20 + * relocation type, using this tls_index as argument to call + * `void *__tls_get_addr (tls_index *ti)` to resolve the real address. + */ + if (r_type == R_RISCV_TLS_DTPMOD64 && + upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), + &tls_addr, sizeof(tls_addr))) { + log_error("copy address failed\n"); + goto out; + } + + elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); + + log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, + jmp_addr, tls_addr); + +out: + return elf_addr; +} diff --git a/upatch-manage/upatch-patch.c b/upatch-manage/upatch-patch.c index 4b1b1be9..fd2c5f22 100644 --- a/upatch-manage/upatch-patch.c +++ b/upatch-manage/upatch-patch.c @@ -475,6 +475,23 @@ static int complete_info(struct upatch_elf *uelf, struct object_file *obj, upatch_func->addr = upatch_funcs_addr[i].addr; upatch_func->addr.old_addr += uelf->relf->load_bias; + +#ifdef __riscv +#define RISCV_MAX_JUMP_RANGE (1L << 31) + /* + * On RISC-V, to jump to arbitrary address, there must be + * at least 12 bytes to hold 3 instructors. Struct upatch_info + * new_insn field is only 8 bytes. We can only jump into + * +-2G ranges. Here do the check. + */ + long riscv_offset = (long)(upatch_func->addr.new_addr - upatch_func->addr.old_addr); + if (riscv_offset >= RISCV_MAX_JUMP_RANGE || riscv_offset < -RISCV_MAX_JUMP_RANGE) { + log_error("new_addr=%lx old_addr=%lx exceed +-2G range\n", + upatch_func->addr.new_addr, upatch_func->addr.old_addr); + goto out; + } +#endif + ret = upatch_process_mem_read(obj->proc, upatch_func->addr.old_addr, &upatch_func->old_insn, get_origin_insn_len()); if (ret) { @@ -483,7 +500,12 @@ static int complete_info(struct upatch_elf *uelf, struct object_file *obj, goto out; } +#ifdef __riscv + upatch_func->new_insn = get_new_insn( + upatch_func->addr.old_addr, upatch_func->addr.new_addr); +#else upatch_func->new_insn = get_new_insn(); +#endif log_debug("\taddr: 0x%lx -> 0x%lx, insn: 0x%lx -> 0x%lx, name: '%s'\n", upatch_func->addr.old_addr, upatch_func->addr.new_addr, upatch_func->old_insn[0], upatch_func->new_insn, diff --git a/upatch-manage/upatch-ptrace.c b/upatch-manage/upatch-ptrace.c index ab21891d..4ebe0380 100644 --- a/upatch-manage/upatch-ptrace.c +++ b/upatch-manage/upatch-ptrace.c @@ -25,6 +25,10 @@ #include #include +#ifdef __riscv +/* user_regs_struct defined here */ +#include +#endif #include #include diff --git a/upatch-manage/upatch-ptrace.h b/upatch-manage/upatch-ptrace.h index 8f36565d..f2503cfd 100644 --- a/upatch-manage/upatch-ptrace.h +++ b/upatch-manage/upatch-ptrace.h @@ -22,6 +22,9 @@ #define __UPATCH_PTRACE__ #include +#ifdef __riscv +#include +#endif #include "upatch-process.h" #include "list.h" @@ -79,6 +82,11 @@ long upatch_execute_remote(struct upatch_ptrace_ctx *, size_t get_origin_insn_len(void); size_t get_upatch_insn_len(void); size_t get_upatch_addr_len(void); + +#ifdef __riscv +unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr); +#else unsigned long get_new_insn(void); +#endif #endif -- Gitee From ba6fee40405c9baabcd878a68501646e5ecff143 Mon Sep 17 00:00:00 2001 From: Jvle Date: Wed, 23 Apr 2025 17:08:09 +0800 Subject: [PATCH 03/13] CMake: fix -Werror=cast-align for riscv64 Signed-off-by: Jvle --- CMakeLists.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d8b3051..95a917f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,11 +62,23 @@ list(APPEND PROJECT_C_BUILD_FLAGS -DBUILD_VERSION="${PROJECT_BUILD_VERSION}" -D_FORTIFY_SOURCE=2 -Wtrampolines -Wformat=2 -Wstrict-prototypes -Wdate-time -Wstack-usage=8192 -Wfloat-equal -Wswitch-default - -Wshadow -Wconversion -Wcast-qual -Wcast-align -Wunused -Wundef + -Wshadow -Wconversion -Wcast-qual -Wunused -Wundef -funsigned-char -fstack-protector-all -fpic -fpie -ftrapv -fstack-check -freg-struct-return -fno-canonical-system-headers -fno-common -pipe -fdebug-prefix-map=old=new ) + +# The -Werror=cast-align compiler flag causes issues on riscv64 GCC, +# while the same operations do not error on aarch64. This appears to be +# a compiler-specific problem. Temporarily disable this option as a +# workaround since applying fixes would require intrusive code changes +# across multiple files. +if(NOT ARCH STREQUAL "riscv64") + list(APPEND PROJECT_C_BUILD_FLAGS + -Wcast-align + ) +endif() + list(APPEND PROJECT_RUST_FLAGS --cfg unsound_local_offset -D warnings -- Gitee From 56106014c3928e0cad1fa94ef2b2fd3abf5bec90 Mon Sep 17 00:00:00 2001 From: renoseven Date: Thu, 12 Jun 2025 18:45:00 +0800 Subject: [PATCH 04/13] upatch-build: fix compile failure for lower rust version Signed-off-by: renoseven --- upatch-build/src/dwarf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upatch-build/src/dwarf.rs b/upatch-build/src/dwarf.rs index 4934a7a4..fe0821c6 100644 --- a/upatch-build/src/dwarf.rs +++ b/upatch-build/src/dwarf.rs @@ -112,7 +112,7 @@ impl ProducerParser { let section_name = section_id.name(); let section = match file.section_by_name(section_name) { Some(section) => section, - None => return Ok(EndianRcSlice::new(Rc::default(), endian)), + None => return Ok(EndianRcSlice::new(Rc::new([]), endian)), }; let mut section_data = section -- Gitee From dc7c3a34beba5bcf68d8e2ab23c6b7be7c3bcf03 Mon Sep 17 00:00:00 2001 From: renoseven Date: Sat, 14 Jun 2025 18:40:44 +0800 Subject: [PATCH 05/13] upatch-build: rename '--keep-line-macros' to '--override-line-macros' Signed-off-by: renoseven --- upatch-build/src/args.rs | 4 ++-- upatch-build/src/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/upatch-build/src/args.rs b/upatch-build/src/args.rs index f8400953..e3fc45b3 100644 --- a/upatch-build/src/args.rs +++ b/upatch-build/src/args.rs @@ -95,9 +95,9 @@ pub struct Arguments { #[clap(short, long, default_value = DEFAULT_OUTPUT_DIR)] pub output_dir: PathBuf, - /// Keep line macro unchanged + /// Override line macros to a fixed value #[clap(long)] - pub keep_line_macros: bool, + pub override_line_macros: bool, /// Skip compiler version check (not recommended) #[clap(long)] diff --git a/upatch-build/src/main.rs b/upatch-build/src/main.rs index 29d68f69..2ae47fe7 100644 --- a/upatch-build/src/main.rs +++ b/upatch-build/src/main.rs @@ -352,7 +352,7 @@ impl UpatchBuild { .with_context(|| format!("Failed to clean {}", project))?; } - if !self.args.keep_line_macros { + if self.args.override_line_macros { info!("Overriding line macros"); project .override_line_macros() @@ -395,7 +395,7 @@ impl UpatchBuild { .apply_patches() .with_context(|| format!("Failed to patch {}", project))?; - if !self.args.keep_line_macros { + if self.args.override_line_macros { info!("Overriding line macros"); project .override_line_macros() -- Gitee From 3ddf6769655ea756611d39b1fb7b5ecebeae080d Mon Sep 17 00:00:00 2001 From: renoseven Date: Sat, 14 Jun 2025 18:40:11 +0800 Subject: [PATCH 06/13] syscare-build: rename '--keep-line-macros' to '--override-line-macros' Signed-off-by: renoseven --- syscare-build/src/args.rs | 4 ++-- syscare-build/src/build_params.rs | 2 +- syscare-build/src/main.rs | 2 +- syscare-build/src/patch/user_patch/upatch_builder.rs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/syscare-build/src/args.rs b/syscare-build/src/args.rs index 4a517159..cba93f56 100644 --- a/syscare-build/src/args.rs +++ b/syscare-build/src/args.rs @@ -97,9 +97,9 @@ pub struct Arguments { #[clap(short, long, default_value = &DEFAULT_BUILD_JOBS)] pub jobs: usize, - /// Keep line macro unchanged (userspace patch only) + /// Override line macros to a fixed value (userspace patch only) #[clap(long)] - pub keep_line_macros: bool, + pub override_line_macros: bool, /// Skip compiler version check (not recommended) #[clap(long)] diff --git a/syscare-build/src/build_params.rs b/syscare-build/src/build_params.rs index 29dd9608..40d17dc6 100644 --- a/syscare-build/src/build_params.rs +++ b/syscare-build/src/build_params.rs @@ -39,7 +39,7 @@ pub struct BuildParameters { pub patch_description: String, pub patch_files: Vec, pub jobs: usize, - pub keep_line_macros: bool, + pub override_line_macros: bool, pub skip_compiler_check: bool, pub skip_cleanup: bool, pub verbose: bool, diff --git a/syscare-build/src/main.rs b/syscare-build/src/main.rs index 51f90506..27754afe 100644 --- a/syscare-build/src/main.rs +++ b/syscare-build/src/main.rs @@ -305,7 +305,7 @@ impl SyscareBuild { patch_type, patch_files, jobs: self.args.jobs, - keep_line_macros: self.args.keep_line_macros, + override_line_macros: self.args.override_line_macros, skip_compiler_check: self.args.skip_compiler_check, skip_cleanup: self.args.skip_cleanup, verbose: self.args.verbose, diff --git a/syscare-build/src/patch/user_patch/upatch_builder.rs b/syscare-build/src/patch/user_patch/upatch_builder.rs index a69373c6..9e524e00 100644 --- a/syscare-build/src/patch/user_patch/upatch_builder.rs +++ b/syscare-build/src/patch/user_patch/upatch_builder.rs @@ -54,7 +54,7 @@ struct UBuildParameters { patch_target: PackageInfo, patch_description: String, patch_files: Vec, - keep_line_macros: bool, + override_line_macros: bool, skip_compiler_check: bool, verbose: bool, } @@ -182,7 +182,7 @@ impl UserPatchBuilder { patch_target: build_params.build_entry.target_pkg.to_owned(), patch_description: build_params.patch_description.to_owned(), patch_files: build_params.patch_files.to_owned(), - keep_line_macros: build_params.keep_line_macros, + override_line_macros: build_params.override_line_macros, skip_compiler_check: build_params.skip_compiler_check, verbose: build_params.verbose, }; @@ -225,8 +225,8 @@ impl UserPatchBuilder { .arg("--output-dir") .arg(&ubuild_params.patch_output_dir); - if ubuild_params.keep_line_macros { - cmd_args.arg("--keep-line-macros"); + if ubuild_params.override_line_macros { + cmd_args.arg("--ignore-line-macros"); } if ubuild_params.skip_compiler_check { cmd_args.arg("--skip-compiler-check"); -- Gitee From 7dda895e90791f1c6739cfa077c98e19edd12f9d Mon Sep 17 00:00:00 2001 From: renoseven Date: Thu, 29 May 2025 18:12:32 +0800 Subject: [PATCH 07/13] common: update component 1. rewrite common::os 2. rewrite common::process 3. rewrite common::util::digest 4. remove common::io 5. move fs::fs_impl::xattr to fs::xattr 6. impl fs::flock::flock_exists() 7. prevent file creation in fs::mmap() 8. support kernel module management Signed-off-by: renoseven --- syscare-common/Cargo.toml | 3 +- syscare-common/src/fs/flock.rs | 130 ++++-- syscare-common/src/fs/fs_impl.rs | 97 +---- syscare-common/src/fs/glob.rs | 4 +- syscare-common/src/fs/mmap.rs | 87 ++-- syscare-common/src/fs/mod.rs | 2 + syscare-common/src/fs/xattr.rs | 97 +++++ syscare-common/src/io/mod.rs | 19 - syscare-common/src/io/os_lines.rs | 78 ---- syscare-common/src/io/select.rs | 77 ---- syscare-common/src/lib.rs | 1 - syscare-common/src/os/cpu.rs | 46 +-- syscare-common/src/os/kernel.rs | 161 +++++++- syscare-common/src/os/mod.rs | 2 - syscare-common/src/os/platform.rs | 82 ++-- syscare-common/src/os/proc_maps.rs | 90 ---- syscare-common/src/os/proc_mounts.rs | 160 -------- syscare-common/src/os/process.rs | 68 +--- syscare-common/src/os/selinux.rs | 331 +++++++++++---- syscare-common/src/os/umask.rs | 80 ++-- syscare-common/src/os/user.rs | 122 +++--- syscare-common/src/process/args.rs | 55 --- syscare-common/src/process/child.rs | 68 ++-- syscare-common/src/process/command.rs | 133 ------ syscare-common/src/process/envs.rs | 68 ---- syscare-common/src/process/mod.rs | 563 +++++++++++++++++++------- syscare-common/src/process/output.rs | 179 ++++++++ syscare-common/src/process/stdio.rs | 179 -------- syscare-common/src/util/digest.rs | 216 +++++++++- 29 files changed, 1702 insertions(+), 1496 deletions(-) create mode 100644 syscare-common/src/fs/xattr.rs delete mode 100644 syscare-common/src/io/mod.rs delete mode 100644 syscare-common/src/io/os_lines.rs delete mode 100644 syscare-common/src/io/select.rs delete mode 100644 syscare-common/src/os/proc_maps.rs delete mode 100644 syscare-common/src/os/proc_mounts.rs delete mode 100644 syscare-common/src/process/args.rs delete mode 100644 syscare-common/src/process/command.rs delete mode 100644 syscare-common/src/process/envs.rs create mode 100644 syscare-common/src/process/output.rs delete mode 100644 syscare-common/src/process/stdio.rs diff --git a/syscare-common/Cargo.toml b/syscare-common/Cargo.toml index c559631d..f0b6446c 100644 --- a/syscare-common/Cargo.toml +++ b/syscare-common/Cargo.toml @@ -10,10 +10,11 @@ build = "build.rs" [dependencies] anyhow = { version = "1.0" } -lazy_static = { version = "1.4" } log = { version = "0.4" } memmap2 = { version = "0.9" } nix = { version = "0.26" } +num_cpus = { version = "1.14" } +object = { version = "0.29" } regex = { version = "1.7" } sha2 = { version = "0.10" } serde = { version = "1.0", features = ["derive"] } diff --git a/syscare-common/src/fs/flock.rs b/syscare-common/src/fs/flock.rs index a1ca6ca7..931a9c75 100644 --- a/syscare-common/src/fs/flock.rs +++ b/syscare-common/src/fs/flock.rs @@ -16,7 +16,7 @@ use std::{ fs::File, io::Result, ops::{Deref, DerefMut}, - os::unix::io::AsRawFd, + os::{fd::RawFd, unix::io::AsRawFd}, path::Path, }; @@ -36,23 +36,15 @@ pub struct FileLock { } impl FileLock { - fn new>(file_path: P, kind: FileLockType) -> Result { - let file_path = file_path.as_ref(); - let flock = Self { - file: if file_path.exists() { - File::open(file_path)? - } else { - File::create(file_path)? - }, - }; - flock.acquire(kind)?; + fn new(file_path: &Path, kind: FileLockType) -> Result { + let file = File::open(file_path)?; + Self::acquire_flock(file.as_raw_fd(), kind)?; - Ok(flock) + Ok(Self { file }) } #[inline] - fn acquire(&self, kind: FileLockType) -> Result<()> { - let fd = self.file.as_raw_fd(); + fn acquire_flock(fd: RawFd, kind: FileLockType) -> Result<()> { let arg = match kind { FileLockType::Shared => fcntl::FlockArg::LockShared, FileLockType::Exclusive => fcntl::FlockArg::LockExclusive, @@ -65,10 +57,8 @@ impl FileLock { } #[inline] - fn release(&self) { - let fd = self.file.as_raw_fd(); - let arg = fcntl::FlockArg::Unlock; - fcntl::flock(fd, arg).expect("Failed to release file lock"); + fn release_flock(fd: RawFd) { + fcntl::flock(fd, fcntl::FlockArg::Unlock).expect("Failed to release file lock"); } } @@ -88,48 +78,110 @@ impl DerefMut for FileLock { impl Drop for FileLock { fn drop(&mut self) { - self.release(); + Self::release_flock(self.file.as_raw_fd()); } } +pub fn flock_exists>(file_path: P, kind: FileLockType) -> Result { + FileLock::new(file_path.as_ref(), kind) +} + pub fn flock>(file_path: P, kind: FileLockType) -> Result { - FileLock::new(file_path, kind) + let file_path = file_path.as_ref(); + if !file_path.exists() { + File::create(file_path)?; + } + self::flock_exists(file_path, kind) } #[test] fn test() -> anyhow::Result<()> { - use anyhow::{ensure, Context}; - + use anyhow::{anyhow, ensure}; use std::fs; let file_path = std::env::temp_dir().join("flock_test"); - fs::remove_file(&file_path)?; - - println!("Testing shared flock on {}...", file_path.display()); - let shared_lock = self::flock(&file_path, FileLockType::SharedNonBlock) - .context("Failed to create shared flock")?; - let shared_lock1 = self::flock(&file_path, FileLockType::SharedNonBlock) - .context("Failed to create shared flock")?; + let non_exist_file = std::env::temp_dir().join("flock_test_non_exist"); + + fs::remove_file(&file_path).ok(); + fs::remove_file(&non_exist_file).ok(); + + fs::write(&file_path, "flock_test")?; + + println!("Testing fs::flock_exists()..."); + println!("- Shared flock '{}'...", file_path.display()); + let shared_lock = + self::flock_exists(&file_path, FileLockType::SharedNonBlock).map_err(|e| { + anyhow!( + "Failed to create shared flock '{}', {}", + file_path.display(), + e + ) + })?; + let shared_lock1 = + self::flock_exists(&file_path, FileLockType::SharedNonBlock).map_err(|e| { + anyhow!( + "Failed to create shared flock '{}', {}", + file_path.display(), + e + ) + })?; ensure!( - self::flock(&file_path, FileLockType::ExclusiveNonBlock).is_err(), - "Exclusive flock should be failed" + self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).is_err(), + "Exclusive flock '{}' should be failed", + file_path.display() ); drop(shared_lock); drop(shared_lock1); - println!("Testing exclusive flock on {}...", file_path.display()); - let exclusive_lock = self::flock(&file_path, FileLockType::ExclusiveNonBlock) - .context("Failed to create exclusive flock")?; + println!("- Exclusive flock '{}'...", file_path.display()); + let exclusive_lock = + self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).map_err(|e| { + anyhow!( + "Failed to create exclusive flock '{}', {}", + file_path.display(), + e + ) + })?; ensure!( - self::flock(&file_path, FileLockType::SharedNonBlock).is_err(), - "Shared flock should be failed" + self::flock_exists(&file_path, FileLockType::SharedNonBlock).is_err(), + "Shared flock '{}' should be failed", + file_path.display() ); ensure!( - self::flock(&file_path, FileLockType::ExclusiveNonBlock).is_err(), - "Exclusive flock should be failed" + self::flock_exists(&file_path, FileLockType::ExclusiveNonBlock).is_err(), + "Exclusive flock '{}' should be failed", + file_path.display() ); - drop(exclusive_lock); + println!("- Non-exist flock '{}'...", non_exist_file.display()); + ensure!( + self::flock_exists(&non_exist_file, FileLockType::SharedNonBlock).is_err(), + "Shared flock '{}' should be failed", + non_exist_file.display() + ); + ensure!( + self::flock_exists(&non_exist_file, FileLockType::ExclusiveNonBlock).is_err(), + "Exclusive flock '{}' should be failed", + non_exist_file.display() + ); + + println!("Testing fs::flock()..."); + println!("- Non-exist flock '{}'...", non_exist_file.display()); + let _ = self::flock(&non_exist_file, FileLockType::SharedNonBlock).map_err(|e| { + anyhow!( + "Failed to create shared flock '{}', {}", + file_path.display(), + e + ) + })?; + let _ = self::flock(&non_exist_file, FileLockType::ExclusiveNonBlock).map_err(|e| { + anyhow!( + "Failed to create exclusive flock '{}', {}", + file_path.display(), + e + ) + })?; + Ok(()) } diff --git a/syscare-common/src/fs/fs_impl.rs b/syscare-common/src/fs/fs_impl.rs index f5393f8d..a0488041 100644 --- a/syscare-common/src/fs/fs_impl.rs +++ b/syscare-common/src/fs/fs_impl.rs @@ -14,15 +14,14 @@ use std::{ env, - ffi::{CStr, OsStr, OsString}, + ffi::{OsStr, OsString}, fs::{File, FileType, Metadata, Permissions, ReadDir}, io, - os::{raw::c_void, unix::fs::PermissionsExt}, + os::unix::fs::PermissionsExt, path::{Component, Path, PathBuf}, - ptr::null_mut, }; -use crate::ffi::{CStrExt, OsStrExt}; +use crate::ffi::OsStrExt; trait RewriteError { fn rewrite_err(self, err_msg: String) -> Self; @@ -196,96 +195,6 @@ pub fn file_ext>(path: P) -> OsString { .unwrap_or_default() } -pub fn getxattr(path: P, name: S) -> std::io::Result -where - P: AsRef, - S: AsRef, -{ - let file_path = path.as_ref().to_cstring()?; - let xattr_name = name.as_ref().to_cstring()?; - - /* - * SAFETY: - * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. - * In our implementation, the buffer is checked properly, so that would be safe. - */ - let mut ret = - unsafe { nix::libc::getxattr(file_path.as_ptr(), xattr_name.as_ptr(), null_mut(), 0) }; - if ret == -1 { - return Err(std::io::Error::last_os_error()).rewrite_err(format!( - "Cannot get path {} xattr {}", - file_path.to_string_lossy(), - xattr_name.to_string_lossy() - )); - } - - let mut buf = vec![0; ret.unsigned_abs()]; - let value_ptr = buf.as_mut_ptr().cast::(); - - /* - * SAFETY: - * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. - * In our implementation, the buffer is checked properly, so that would be safe. - */ - ret = unsafe { - nix::libc::getxattr( - file_path.as_ptr(), - xattr_name.as_ptr(), - value_ptr, - buf.len(), - ) - }; - if ret == -1 { - return Err(std::io::Error::last_os_error()).rewrite_err(format!( - "Cannot get path {} xattr {}", - file_path.to_string_lossy(), - xattr_name.to_string_lossy(), - )); - } - - let value = CStr::from_bytes_with_nul(&buf[0..ret.unsigned_abs()]) - .unwrap_or_default() - .to_os_string(); - - Ok(value) -} - -pub fn setxattr(path: P, name: S, value: T) -> std::io::Result<()> -where - P: AsRef, - S: AsRef, - T: AsRef, -{ - let file_path = path.as_ref().to_cstring()?; - let xattr_name = name.as_ref().to_cstring()?; - let xattr_value = value.as_ref().to_cstring()?; - let size = xattr_value.to_bytes_with_nul().len(); - - /* - * SAFETY: - * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. - * In our implementation, the buffer is checked properly, so that would be safe. - */ - let ret = unsafe { - nix::libc::setxattr( - file_path.as_ptr(), - xattr_name.as_ptr(), - xattr_value.as_ptr().cast::(), - size, - 0, - ) - }; - if ret == -1 { - return Err(std::io::Error::last_os_error()).rewrite_err(format!( - "Cannot set {} xattr {}", - file_path.to_string_lossy(), - xattr_name.to_string_lossy() - )); - } - - Ok(()) -} - pub fn normalize>(path: P) -> io::Result { let mut new_path = PathBuf::new(); diff --git a/syscare-common/src/fs/glob.rs b/syscare-common/src/fs/glob.rs index 8165343b..9c115f42 100644 --- a/syscare-common/src/fs/glob.rs +++ b/syscare-common/src/fs/glob.rs @@ -69,7 +69,7 @@ impl Glob { } fn match_component(&mut self, path: PathBuf, index: usize) -> Result> { - let last_index = self.components.len() - 1; + let last_index = self.components.len().saturating_sub(1); for dir_entry in fs::read_dir(&path)? { let next_path = dir_entry?.path(); @@ -126,7 +126,7 @@ impl Iterator for Glob { fn next(&mut self) -> Option { while let Some((mut curr_path, mut index)) = self.stack.pop() { - let last_index = self.components.len() - 1; + let last_index = self.components.len().saturating_sub(1); // iterate all of components over matching path while index <= last_index { diff --git a/syscare-common/src/fs/mmap.rs b/syscare-common/src/fs/mmap.rs index b65f170b..07c9a17d 100644 --- a/syscare-common/src/fs/mmap.rs +++ b/syscare-common/src/fs/mmap.rs @@ -12,36 +12,18 @@ * See the Mulan PSL v2 for more details. */ -use std::{io::Result, ops::Deref, os::fd::AsRawFd, path::Path}; +use std::{ops::Deref, os::unix::io::AsRawFd, path::Path}; use memmap2::{Advice, Mmap, MmapOptions}; -use super::flock::*; +use super::flock; #[derive(Debug)] pub struct FileMmap { - _lock: FileLock, + _lock: flock::FileLock, mmap: Mmap, } -impl FileMmap { - fn new>(file_path: P) -> Result { - /* - * SAFETY: - * All file-backed memory map constructors are marked unsafe because of the - * potential for Undefined Behavior (UB) using the map if the underlying file - * is subsequently modified, in or out of process. - * Our implementation uses shared file lock to avoid cross-process file access. - * This mapped area would be safe. - */ - let lock = flock(file_path, FileLockType::Shared)?; - let mmap = unsafe { MmapOptions::new().map(lock.as_raw_fd())? }; - mmap.advise(Advice::Random)?; - - Ok(Self { _lock: lock, mmap }) - } -} - impl Deref for FileMmap { type Target = [u8]; @@ -51,27 +33,58 @@ impl Deref for FileMmap { } pub fn mmap>(file_path: P) -> std::io::Result { - FileMmap::new(file_path) + /* + * SAFETY: + * All file-backed memory map constructors are marked unsafe because of the + * potential for Undefined Behavior (UB) using the map if the underlying file + * is subsequently modified, in or out of process. + * Our implementation uses shared file lock to avoid cross-process file access. + * This mapped area would be safe. + */ + let lock = flock::flock_exists(file_path, flock::FileLockType::Shared)?; + let mmap = unsafe { MmapOptions::new().map(lock.as_raw_fd())? }; + mmap.advise(Advice::Random)?; + + Ok(FileMmap { _lock: lock, mmap }) } -#[test] -fn test() -> anyhow::Result<()> { - use anyhow::Context; +#[cfg(test)] +mod test { + use std::fs; + + use super::*; - const FILE_PATH: &str = "/etc/os-release"; - const SYS_FS_PATH: &str = "/sys/kernel/vmcoreinfo"; - const PROC_FS_PATH: &str = "/proc/version"; + #[test] + fn mmap_file() -> std::io::Result<()> { + let file_path = std::env::temp_dir().join("mmap_test"); + fs::remove_file(&file_path).ok(); + fs::write(&file_path, "mmap_test")?; - println!("Testing FileMmap..."); - let orig_file = - std::fs::read(FILE_PATH).with_context(|| format!("Failed to open file {}", FILE_PATH))?; - let map_file = - self::mmap(FILE_PATH).with_context(|| format!("Failed to mmap file {}", FILE_PATH))?; + let orig_file = fs::read(&file_path)?; + let map_file = self::mmap(&file_path)?; + assert_eq!(orig_file, map_file.as_ref()); - let _ = self::mmap(SYS_FS_PATH).expect_err("Sysfs cannot not be mapped"); - let _ = self::mmap(PROC_FS_PATH).expect_err("Procfs cannot not be mapped"); + fs::remove_file(&file_path)?; + Ok(()) + } - assert_eq!(orig_file, map_file.as_ref()); + #[test] + fn mmap_non_exists_file() { + const NON_EXISTS_FILE: &str = "/non_exists_file"; + fs::remove_file(NON_EXISTS_FILE).ok(); - Ok(()) + assert!(self::mmap(NON_EXISTS_FILE).is_err(),); + } + + #[test] + fn mmap_proc_file() { + const PROC_FS_PATH: &str = "/proc/version"; + assert!(self::mmap(PROC_FS_PATH).is_err(),); + } + + #[test] + fn mmap_sys_file() { + const SYS_FS_PATH: &str = "/sys/kernel/vmcoreinfo"; + assert!(self::mmap(SYS_FS_PATH).is_err(),); + } } diff --git a/syscare-common/src/fs/mod.rs b/syscare-common/src/fs/mod.rs index 35269150..b6d2d049 100644 --- a/syscare-common/src/fs/mod.rs +++ b/syscare-common/src/fs/mod.rs @@ -16,8 +16,10 @@ mod flock; mod fs_impl; mod glob; mod mmap; +mod xattr; pub use flock::*; pub use fs_impl::*; pub use glob::*; pub use mmap::*; +pub use xattr::*; diff --git a/syscare-common/src/fs/xattr.rs b/syscare-common/src/fs/xattr.rs new file mode 100644 index 00000000..237c70b9 --- /dev/null +++ b/syscare-common/src/fs/xattr.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Mulan PSL v2 +/* + * Copyright (c) 2024 Huawei Technologies Co., Ltd. + * syscare-common is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + ffi::{c_void, CString, OsStr, OsString}, + os::unix::ffi::{OsStrExt, OsStringExt}, + path::Path, + ptr, +}; + +pub fn getxattr(path: P, name: S) -> std::io::Result +where + P: AsRef, + S: AsRef, +{ + let file_path = CString::new(path.as_ref().as_os_str().as_bytes())?; + let xattr_name = CString::new(name.as_ref().as_bytes())?; + + /* + * SAFETY: + * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. + * In our implementation, the buffer is checked properly, so that would be safe. + */ + let buf_size = + unsafe { nix::libc::getxattr(file_path.as_ptr(), xattr_name.as_ptr(), ptr::null_mut(), 0) }; + if buf_size == -1 { + return Err(std::io::Error::last_os_error()); + } + + let mut buf = vec![0; buf_size.unsigned_abs()]; + let value_ptr = buf.as_mut_ptr().cast::(); + + /* + * SAFETY: + * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. + * In our implementation, the buffer is checked properly, so that would be safe. + */ + let bytes_read = unsafe { + nix::libc::getxattr( + file_path.as_ptr(), + xattr_name.as_ptr(), + value_ptr, + buf.len(), + ) + }; + if bytes_read == -1 { + return Err(std::io::Error::last_os_error()); + } + if buf.last() == Some(&0) { + buf.pop(); + } + + Ok(OsString::from_vec(buf)) +} + +pub fn setxattr(path: P, name: S, value: T) -> std::io::Result<()> +where + P: AsRef, + S: AsRef, + T: AsRef, +{ + let file_path = CString::new(path.as_ref().as_os_str().as_bytes())?; + let xattr_name = CString::new(name.as_ref().as_bytes())?; + let xattr_value = CString::new(value.as_ref().as_bytes())?; + let size = xattr_value.to_bytes_with_nul().len(); + + /* + * SAFETY: + * This libc function is marked 'unsafe' as unchecked buffer may cause overflow. + * In our implementation, the buffer is checked properly, so that would be safe. + */ + let ret = unsafe { + nix::libc::setxattr( + file_path.as_ptr(), + xattr_name.as_ptr(), + xattr_value.as_ptr().cast::(), + size, + 0, + ) + }; + if ret == -1 { + return Err(std::io::Error::last_os_error()); + } + + Ok(()) +} diff --git a/syscare-common/src/io/mod.rs b/syscare-common/src/io/mod.rs deleted file mode 100644 index a36f750c..00000000 --- a/syscare-common/src/io/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -mod os_lines; -mod select; - -pub use os_lines::*; -pub use select::*; diff --git a/syscare-common/src/io/os_lines.rs b/syscare-common/src/io/os_lines.rs deleted file mode 100644 index 08ae0cef..00000000 --- a/syscare-common/src/io/os_lines.rs +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ffi::OsString, io::BufRead, os::unix::prelude::OsStringExt}; - -pub struct OsLines { - buf: R, -} - -impl Iterator for OsLines { - type Item = std::io::Result; - - fn next(&mut self) -> Option { - const CHAR_LF: [u8; 1] = [b'\n']; - const CHAR_CR: [u8; 1] = [b'\r']; - - let mut buf = Vec::new(); - match self.buf.read_until(CHAR_LF[0], &mut buf) { - Ok(0) => None, - Ok(_) => { - // Drop "\n" or "\r\n" on the buf tail - if buf.ends_with(&CHAR_LF) { - buf.pop(); - if buf.ends_with(&CHAR_CR) { - buf.pop(); - } - } - buf.shrink_to_fit(); - Some(Ok(OsString::from_vec(buf))) - } - Err(_) => Some(self.buf.read_to_end(&mut buf).map(|_| { - buf.shrink_to_fit(); - OsString::from_vec(buf) - })), - } - } -} - -pub trait BufReadOsLines: BufRead + Sized { - fn os_lines(self) -> OsLines { - OsLines { buf: self } - } -} - -impl BufReadOsLines for R {} - -#[test] -fn test() { - use crate::fs; - use std::io::BufReader; - - let buf_reader = - BufReader::new(fs::open_file("/proc/self/cmdline").expect("Failed to open procfs")); - let lines = buf_reader.lines(); - for str in lines.flatten() { - println!("{}", str); - assert!(!str.is_empty()); - } - - let buf_reader = - BufReader::new(fs::open_file("/proc/self/cmdline").expect("Failed to open procfs")); - let os_lines = buf_reader.os_lines(); - for str in os_lines.flatten() { - println!("{}", str.to_string_lossy()); - assert!(!str.is_empty()); - } -} diff --git a/syscare-common/src/io/select.rs b/syscare-common/src/io/select.rs deleted file mode 100644 index 5d9400f4..00000000 --- a/syscare-common/src/io/select.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{ - os::unix::io::{AsRawFd, RawFd}, - time::Duration, -}; - -use anyhow::Result; -use nix::sys::{ - select::{select, FdSet}, - time::TimeVal, -}; - -pub enum SelectResult { - Readable(RawFd), - Writable(RawFd), - Error(RawFd), -} - -pub struct Select { - fd_set: FdSet, - readfds: FdSet, - writefds: FdSet, - errorfds: FdSet, - timeout: Option, -} - -impl Select { - pub fn new(fds: I) -> Self - where - I: IntoIterator, - F: AsRawFd, - { - Self::with_timeout(fds, None) - } - - pub fn with_timeout(fds: I, duration: Option) -> Self - where - I: IntoIterator, - F: AsRawFd, - { - let mut fd_set = FdSet::new(); - for fd in fds { - fd_set.insert(fd.as_raw_fd()); - } - let readfds = FdSet::new(); - let writefds = FdSet::new(); - let errorfds = FdSet::new(); - let timeout = duration.map(|t| TimeVal::new(t.as_secs() as i64, t.subsec_micros() as i64)); - - Self { - fd_set, - readfds, - writefds, - errorfds, - timeout, - } - } - - pub fn select(&mut self) -> Result + '_> { - self.readfds = self.fd_set; - self.writefds = self.fd_set; - self.errorfds = self.fd_set; - - select( - None, - &mut self.readfds, - &mut self.writefds, - &mut self.errorfds, - &mut self.timeout, - )?; - - let rd_fds = self.readfds.fds(None).map(SelectResult::Readable); - let wr_fds = self.writefds.fds(None).map(SelectResult::Writable); - let err_fds = self.errorfds.fds(None).map(SelectResult::Error); - - Ok(rd_fds.chain(wr_fds).chain(err_fds)) - } -} diff --git a/syscare-common/src/lib.rs b/syscare-common/src/lib.rs index a3188e6d..b568a2cc 100644 --- a/syscare-common/src/lib.rs +++ b/syscare-common/src/lib.rs @@ -14,7 +14,6 @@ pub mod ffi; pub mod fs; -pub mod io; mod macros; pub mod os; pub mod os_str; diff --git a/syscare-common/src/os/cpu.rs b/syscare-common/src/os/cpu.rs index ead7eee0..9d39bca2 100644 --- a/syscare-common/src/os/cpu.rs +++ b/syscare-common/src/os/cpu.rs @@ -12,39 +12,35 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsStr; +use std::ffi::OsString; -use lazy_static::lazy_static; - -use nix::{ - sched::{sched_getaffinity, CpuSet}, - unistd::getpid, -}; +use num_cpus; use super::platform; -pub fn arch() -> &'static OsStr { +pub fn arch() -> OsString { platform::arch() } pub fn num() -> usize { - lazy_static! { - static ref CPU_NUM: usize = { - let cpu_set = sched_getaffinity(getpid()).unwrap_or_default(); - let mut cpu_count = 0; - for i in 0..CpuSet::count() { - if cpu_set.is_set(i).unwrap_or_default() { - cpu_count += 1; - } - } - cpu_count - }; - } - *CPU_NUM + num_cpus::get() } -#[test] -fn test() { - println!("arch: {}", arch().to_string_lossy()); - println!("num: {}", num()) +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_arch() { + let arch = self::arch(); + println!("cpu arch: {}", arch.to_string_lossy()); + assert!(!arch.is_empty()); + } + + #[test] + fn test_num() { + let num = self::num(); + println!("cpu num: {}", num); + assert_ne!(num, 0); + } } diff --git a/syscare-common/src/os/kernel.rs b/syscare-common/src/os/kernel.rs index a29e663d..c3551679 100644 --- a/syscare-common/src/os/kernel.rs +++ b/syscare-common/src/os/kernel.rs @@ -12,10 +12,165 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsStr; +// SPDX-License-Identifier: Mulan PSL v2 +/* + * Copyright (c) 2024 Huawei Technologies Co., Ltd. + * syscare-common is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + collections::HashSet, + ffi::{CStr, CString, OsStr, OsString}, + fs::File, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, +}; + +use log::{error, trace}; +use nix::{errno::Errno, kmod}; +use object::{Object, ObjectSection}; + +use super::{platform, selinux}; +use crate::fs; + +const SYS_MODULE_DIR: &str = "/sys/module"; + +pub type KernelModuleInfo = ModuleInfo; +pub type KernelModuleGuard = ModuleGuard; -use super::platform; +#[derive(Debug)] +pub struct ModuleInfo { + pub name: OsString, + pub depends: HashSet, + pub module_path: PathBuf, +} + +#[derive(Debug)] +pub struct ModuleGuard(ModuleInfo); + +impl Drop for ModuleGuard { + fn drop(&mut self) { + if !self.0.module_path.exists() { + return; + } + if let Err(e) = self::remove_module(&self.0.name) { + error!( + "Failed to remove kernel module '{}', {}", + self.0.name.to_string_lossy(), + e.to_string().to_lowercase() + ); + } + } +} -pub fn version() -> &'static OsStr { +pub fn version() -> OsString { platform::release() } + +pub fn list_modules() -> std::io::Result> { + const LIST_OPTIONS: fs::TraverseOptions = fs::TraverseOptions { recursive: false }; + + let modules = fs::list_dirs(SYS_MODULE_DIR, LIST_OPTIONS)? + .into_iter() + .filter_map(|path| path.file_name().map(OsStr::to_os_string)) + .collect(); + Ok(modules) +} + +pub fn relable_module_file>(file_path: P) -> std::io::Result<()> { + const KMOD_FILE_TYPE: &str = "modules_object_t"; + + let file_path = file_path.as_ref(); + let mut context = selinux::get_security_context(file_path)?; + if context.get_type() == KMOD_FILE_TYPE { + return Ok(()); + } + + context.set_type(KMOD_FILE_TYPE)?; + selinux::set_security_context(file_path, &context)?; + + Ok(()) +} + +pub fn module_info>(file_path: P) -> std::io::Result { + const MODINFO_SECTION_NAME: &str = ".modinfo"; + const MODINFO_NAME_PREFIX: &[u8] = b"name="; + const MODINFO_DEPENDS_PREFIX: &[u8] = b"depends="; + + let file_path = file_path.as_ref().to_path_buf(); + let mmap = fs::mmap(&file_path)?; + let file = object::File::parse(&*mmap).map_err(|_| Errno::ENOEXEC)?; + if file.format() != object::BinaryFormat::Elf { + return Err(std::io::Error::from(Errno::ENOEXEC)); + } + + let data = file + .section_by_name(MODINFO_SECTION_NAME) + .and_then(|section| section.data().ok()) + .ok_or(Errno::ENOEXEC)?; + let name = data + .split(|b| *b == b'\0') + .find_map(|entry| entry.strip_prefix(MODINFO_NAME_PREFIX)) + .map(|slice| OsStr::from_bytes(slice).to_os_string()) + .ok_or(Errno::ENOEXEC)?; + let depends = data + .split(|b| *b == b'\0') + .find_map(|entry| entry.strip_prefix(MODINFO_DEPENDS_PREFIX)) + .unwrap_or_default() + .split(|b| *b == b',') + .filter(|b| !b.is_empty()) + .map(|b| OsStr::from_bytes(b).to_os_string()) + .collect(); + let module_path = Path::new(SYS_MODULE_DIR).join(&name); + + let kmod = ModuleInfo { + name, + depends, + module_path, + }; + Ok(kmod) +} + +pub fn insert_module>(file_path: P) -> std::io::Result<()> { + const PARAM_VALUES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; + + let file_path = file_path.as_ref(); + trace!("Inserting kernel module '{}'...", file_path.display()); + + let file = File::open(file_path)?; + kmod::finit_module(&file, PARAM_VALUES, kmod::ModuleInitFlags::empty())?; + + Ok(()) +} + +pub fn insert_module_guarded>(file_path: P) -> std::io::Result { + let file_path = file_path.as_ref(); + + let modinfo = self::module_info(file_path)?; + if !modinfo.module_path.exists() { + self::insert_module(file_path)?; + } + + Ok(ModuleGuard(modinfo)) +} + +pub fn remove_module>(module_name: S) -> std::io::Result<()> { + let module_name = module_name.as_ref(); + trace!( + "Removing kernel module '{}'...", + module_name.to_string_lossy() + ); + + let name = CString::new(module_name.as_bytes())?; + kmod::delete_module(&name, kmod::DeleteModuleFlags::O_NONBLOCK)?; + + Ok(()) +} diff --git a/syscare-common/src/os/mod.rs b/syscare-common/src/os/mod.rs index 6a93a20f..9d330101 100644 --- a/syscare-common/src/os/mod.rs +++ b/syscare-common/src/os/mod.rs @@ -15,8 +15,6 @@ pub mod cpu; pub mod kernel; pub mod platform; -pub mod proc_maps; -pub mod proc_mounts; pub mod process; pub mod selinux; pub mod umask; diff --git a/syscare-common/src/os/platform.rs b/syscare-common/src/os/platform.rs index 8d38b44f..178ecd4e 100644 --- a/syscare-common/src/os/platform.rs +++ b/syscare-common/src/os/platform.rs @@ -12,59 +12,71 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsStr; +use std::ffi::OsString; -use lazy_static::lazy_static; -use nix::sys::utsname::{uname, UtsName}; +use nix::sys::utsname; #[inline(always)] -fn info() -> &'static UtsName { - lazy_static! { - static ref PLATFORM_INFO: UtsName = uname().expect("Failed to get uname"); - } - &PLATFORM_INFO +fn sysinfo() -> utsname::UtsName { + utsname::uname().expect("Failed to get system infomation") } -pub fn hostname() -> &'static OsStr { - info().nodename() +pub fn sysname() -> OsString { + self::sysinfo().sysname().to_os_string() } -pub fn sysname() -> &'static OsStr { - info().sysname() +pub fn hostname() -> OsString { + self::sysinfo().nodename().to_os_string() } -pub fn release() -> &'static OsStr { - info().release() +pub fn release() -> OsString { + self::sysinfo().release().to_os_string() } -pub fn version() -> &'static OsStr { - info().version() +pub fn version() -> OsString { + self::sysinfo().version().to_os_string() } -pub fn arch() -> &'static OsStr { - info().machine() +pub fn arch() -> OsString { + self::sysinfo().machine().to_os_string() } -#[test] -fn test() { - let sysname = sysname(); - let hostname = hostname(); - let release = release(); - let version = version(); - let arch = arch(); +#[cfg(test)] +mod test { + use super::*; - println!("sysname: {}", sysname.to_string_lossy()); - assert!(!sysname.is_empty()); + #[test] + fn test_sysname() { + let sysname = self::sysname(); + println!("sysname: {}", sysname.to_string_lossy()); + assert!(!sysname.is_empty()); + } - println!("hostname: {}", hostname.to_string_lossy()); - assert!(!hostname.is_empty()); + #[test] + fn test_hostname() { + let hostname = self::hostname(); + println!("hostname: {}", hostname.to_string_lossy()); + assert!(!hostname.is_empty()); + } - println!("release: {}", release.to_string_lossy()); - assert!(!release.is_empty()); + #[test] + fn test_release() { + let release = self::release(); + println!("release: {}", release.to_string_lossy()); + assert!(!release.is_empty()); + } - println!("version: {}", version.to_string_lossy()); - assert!(!version.is_empty()); + #[test] + fn test_version() { + let version = self::version(); + println!("version: {}", version.to_string_lossy()); + assert!(!version.is_empty()); + } - println!("arch: {}", arch.to_string_lossy()); - assert!(!arch.is_empty()); + #[test] + fn test_arch() { + let arch = self::arch(); + println!("arch: {}", arch.to_string_lossy()); + assert!(!arch.is_empty()); + } } diff --git a/syscare-common/src/os/proc_maps.rs b/syscare-common/src/os/proc_maps.rs deleted file mode 100644 index 2855a4ae..00000000 --- a/syscare-common/src/os/proc_maps.rs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{convert::TryFrom, ffi::OsString, fs::File, io::BufReader}; - -use anyhow::{ensure, Result}; - -use crate::{ - ffi::OsStrExt, - fs, - io::{BufReadOsLines, OsLines}, -}; - -#[derive(Debug)] -pub struct ProcMap { - pub address: OsString, - pub permission: OsString, - pub offset: OsString, - pub dev: OsString, - pub inode: OsString, - pub path_name: OsString, -} - -impl TryFrom for ProcMap { - type Error = anyhow::Error; - - fn try_from(value: OsString) -> std::result::Result { - const MAP_FIELD_NUM: usize = 6; - - let fields = value.split_whitespace().collect::>(); - ensure!( - fields.len() == MAP_FIELD_NUM, - "Failed to parse process mapping" - ); - - Ok(Self { - address: fields[0].to_owned(), - permission: fields[1].to_owned(), - offset: fields[2].to_owned(), - dev: fields[3].to_owned(), - inode: fields[4].to_owned(), - path_name: fields[5].to_owned(), - }) - } -} - -pub struct ProcMaps { - lines: OsLines>, -} - -impl ProcMaps { - pub fn new(pid: i32) -> Result { - let file_path = format!("/proc/{}/maps", pid); - let lines = BufReader::new(fs::open_file(file_path)?).os_lines(); - - Ok(Self { lines }) - } -} - -impl Iterator for ProcMaps { - type Item = ProcMap; - - fn next(&mut self) -> Option { - self.lines - .next() - .and_then(Result::ok) - .map(ProcMap::try_from) - .and_then(Result::ok) - } -} - -#[test] -fn test() { - use super::process; - - for map in ProcMaps::new(process::id()).expect("Failed to read proc maps") { - println!("{:#?}", map); - } -} diff --git a/syscare-common/src/os/proc_mounts.rs b/syscare-common/src/os/proc_mounts.rs deleted file mode 100644 index 1e720bfc..00000000 --- a/syscare-common/src/os/proc_mounts.rs +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::ffi::{OsStr, OsString}; -use std::fs::File; -use std::io::BufReader; -use std::os::unix::ffi::{OsStrExt as StdOsStrExt, OsStringExt}; -use std::path::PathBuf; - -use anyhow::Result; - -use crate::{ - ffi::OsStrExt, - fs, - io::{BufReadOsLines, OsLines}, -}; - -#[derive(Debug)] -pub struct MountInfo { - pub mount_id: u32, - pub parent_id: u32, - pub device_id: OsString, - pub root: PathBuf, - pub mount_point: PathBuf, - pub mount_opts: Vec, - pub optional: Vec, - pub filesystem: OsString, - pub mount_source: PathBuf, - pub super_opts: Vec, -} - -struct MountInfoParser<'a> { - data: &'a [u8], - num: usize, - pos: usize, -} - -impl<'a> Iterator for MountInfoParser<'a> { - type Item = &'a OsStr; - - fn next(&mut self) -> Option { - const OPTION_INDEX: usize = 6; - const NORMAL_SPLITTER: char = ' '; - const OPTION_SPLITTER: char = '-'; - - let data = &self.data[self.pos..]; - let new_str = OsStr::from_bytes(data); - if new_str.is_empty() { - return None; - } - - for (index, char) in new_str.char_indices() { - let pattern; - let skip_len; - - match self.num { - OPTION_INDEX => { - pattern = OPTION_SPLITTER; - skip_len = 2; - } - _ => { - pattern = NORMAL_SPLITTER; - skip_len = 1; - } - }; - if char == pattern { - self.num += 1; - self.pos += index + skip_len; - - return Some(OsStr::from_bytes(&data[..index])); - } - } - - self.pos = self.data.len() - 1; - Some(OsStr::from_bytes(data)) - } -} - -pub struct Mounts { - lines: OsLines>, -} - -impl Mounts { - pub fn new() -> Result { - const MOUNTINFO_PATH: &str = "/proc/self/mountinfo"; - - Ok(Self { - lines: BufReader::new(fs::open_file(MOUNTINFO_PATH)?).os_lines(), - }) - } -} - -impl Mounts { - fn parse_line(str: OsString) -> Option { - const VALUE_SPLITTER: char = ','; - - let str_bytes = str.into_vec(); - let mut iter = MountInfoParser { - data: &str_bytes, - num: 0, - pos: 0, - }; - - Some(MountInfo { - mount_id: iter.next()?.to_string_lossy().parse::().ok()?, - parent_id: iter.next()?.to_string_lossy().parse::().ok()?, - device_id: iter.next()?.to_os_string(), - root: PathBuf::from(iter.next()?), - mount_point: PathBuf::from(iter.next()?), - mount_opts: iter - .next()? - .split(VALUE_SPLITTER) - .map(OsStrExt::trim) - .map(OsString::from) - .collect::>(), - optional: iter - .next()? - .split_whitespace() - .map(OsString::from) - .collect::>(), - filesystem: iter.next()?.to_os_string(), - mount_source: PathBuf::from(iter.next()?), - super_opts: iter - .next()? - .split(VALUE_SPLITTER) - .map(OsStrExt::trim) - .map(OsString::from) - .collect::>(), - }) - } -} - -impl Iterator for Mounts { - type Item = MountInfo; - - fn next(&mut self) -> Option { - self.lines.next()?.ok().and_then(Self::parse_line) - } -} - -#[test] -fn test() { - let mount_info = Mounts::new().expect("Failed to read mount info"); - for mount in mount_info { - println!(); - println!("{:#?}", mount); - assert!(mount.mount_point.exists()) - } -} diff --git a/syscare-common/src/os/process.rs b/syscare-common/src/os/process.rs index 45695878..fc8c6cd0 100644 --- a/syscare-common/src/os/process.rs +++ b/syscare-common/src/os/process.rs @@ -13,72 +13,48 @@ */ use std::ffi::{OsStr, OsString}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -use lazy_static::lazy_static; -use nix::unistd::getpid; - -use crate::fs; +use nix::errno::Errno; +use nix::unistd; pub fn id() -> i32 { - lazy_static! { - static ref PROCESS_ID: i32 = getpid().as_raw(); - } - *PROCESS_ID + unistd::getpid().as_raw() } -pub fn path() -> &'static Path { - lazy_static! { - static ref PROCESS_PATH: PathBuf = - std::env::current_exe().unwrap_or_else(|_| PathBuf::from("/")); - } - PROCESS_PATH.as_path() +pub fn path() -> std::io::Result { + std::env::current_exe() } -pub fn name() -> &'static OsStr { - lazy_static! { - static ref PROCESS_NAME: OsString = fs::file_name(path()); - } - PROCESS_NAME.as_os_str() +pub fn name() -> std::io::Result { + self::path()? + .file_name() + .map(OsStr::to_os_string) + .ok_or(std::io::Error::from(Errno::EINVAL)) } #[cfg(test)] -mod tests_process { - use crate::os::process::{id, name, path}; - use std::process::Command; - use std::{println, string::ToString}; - - fn build_commd(s: &str) -> String { - let mut cmd = "ps -ef |grep ".to_string(); - cmd = cmd + s + "|grep -v grep"; - let output = Command::new("bash").arg("-c").arg(cmd).output().unwrap(); - String::from_utf8(output.stdout).unwrap() - } +mod test { + use super::*; #[test] fn test_id() { - let process_id = id().to_string(); - println!("This process id is {}", process_id); - - let sys_proc = build_commd(&process_id); - assert!(!sys_proc.is_empty()); + let pid = self::id(); + println!("pid: {}", pid); + assert!(pid > 1) } #[test] fn test_path() { - let process_path = path().display().to_string(); - println!("This path is {:#?}", process_path); - - let sys_path = build_commd(&process_path); - assert!(!sys_path.is_empty()); + let path = self::path().expect("Failed to get executable path"); + println!("path: {}", path.display()); + assert!(path.exists()); } #[test] fn test_name() { - let process_name = name().to_string_lossy(); - println!("This name is {:#?}", process_name); - - let sys_name = build_commd(&process_name); - assert!(!sys_name.is_empty()); + let name = self::name().expect("Failed to get executable name"); + println!("name: {}", name.to_string_lossy()); + assert!(!name.is_empty()); } } diff --git a/syscare-common/src/os/selinux.rs b/syscare-common/src/os/selinux.rs index bb9864ca..e1aa73ea 100644 --- a/syscare-common/src/os/selinux.rs +++ b/syscare-common/src/os/selinux.rs @@ -12,119 +12,304 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsString; -use std::os::unix::ffi::OsStringExt as UnixOsStringExt; -use std::path::Path; +use std::{ + ffi::{OsStr, OsString}, + fmt::Debug, + os::unix::ffi::{OsStrExt, OsStringExt}, + path::Path, +}; -use anyhow::{bail, ensure, Context, Result}; +use nix::errno::Errno; -use crate::{concat_os, ffi::OsStrExt, fs}; +use crate::fs; const SELINUX_SYS_FILE: &str = "/sys/fs/selinux/enforce"; -const SELINUX_XATTR_NAME: &str = "security.selinux"; -const SELINUX_XATTR_SPLITTER: &str = ":"; -const SECURITY_XATTR_LEN: usize = 4; +const SELINUX_STATUS_PERMISSIVE: &str = "0"; +const SELINUX_STATUS_ENFORCING: &str = "1"; + +const SECURITY_CONTEXT_XATTR_NAME: &str = "security.selinux"; +const SECURITY_CONTEXT_SPLITER: u8 = b':'; +const SECURITY_CONTEXT_SPLITER_COUNT: usize = 3; +const SECURITY_CONTEXT_ATTR_COUNT: usize = SECURITY_CONTEXT_SPLITER_COUNT + 1; + +const SECURITY_CONTEXT_USER_INDEX: usize = 0; +const SECURITY_CONTEXT_ROLE_INDEX: usize = 1; +const SECURITY_CONTEXT_TYPE_INDEX: usize = 2; +const SECURITY_CONTEXT_LEVEL_INDEX: usize = 3; + +pub type SELinuxStatus = Status; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { + Disabled, Permissive, Enforcing, - Disabled, } impl std::fmt::Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{:?}", self)) + write!(f, "{:?}", self) } } -pub fn get_status() -> Result { - if !Path::new(SELINUX_SYS_FILE).is_file() { - return Ok(Status::Disabled); +pub fn get_status() -> Status { + let value = fs::read_to_string(SELINUX_SYS_FILE).unwrap_or_default(); + match value.as_str() { + SELINUX_STATUS_PERMISSIVE => Status::Permissive, + SELINUX_STATUS_ENFORCING => Status::Enforcing, + _ => Status::Disabled, } +} - let value = OsString::from_vec(fs::read(SELINUX_SYS_FILE)?) - .to_string_lossy() - .parse::() - .context("Failed to parse selinux status")?; +pub fn set_status(value: Status) -> std::io::Result<()> { + let contents = match value { + Status::Permissive => SELINUX_STATUS_PERMISSIVE, + Status::Enforcing => SELINUX_STATUS_ENFORCING, + _ => return Err(std::io::Error::from(Errno::EINVAL)), + }; + fs::write(SELINUX_SYS_FILE, contents)?; - Ok(match value { - 0 => Status::Permissive, - 1 => Status::Enforcing, - _ => Status::Disabled, - }) + Ok(()) } -pub fn set_status(value: Status) -> Result<()> { - if (value != Status::Permissive) && (value != Status::Enforcing) { - bail!("Status {} is invalid", value); +#[derive(Clone, PartialEq, Eq)] +pub struct SecurityContext(OsString); + +impl SecurityContext { + fn get_attribute(&self, index: usize) -> &OsStr { + self.0 + .as_bytes() + .splitn(SECURITY_CONTEXT_ATTR_COUNT, |&b| { + b == SECURITY_CONTEXT_SPLITER + }) + .nth(index) + .map(OsStr::from_bytes) + .expect("Unexpected security context format") } - fs::write( - SELINUX_SYS_FILE, - match value { - Status::Enforcing => "1", - _ => "0", - }, - )?; - Ok(()) + fn set_attribute>(&mut self, index: usize, value: S) -> std::io::Result<()> { + let value = value.as_ref().as_bytes(); + + if value.is_empty() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + if (index != SECURITY_CONTEXT_LEVEL_INDEX) && (value.contains(&SECURITY_CONTEXT_SPLITER)) { + return Err(std::io::Error::from(Errno::EINVAL)); + } + let attrs = self.0.as_bytes().splitn(SECURITY_CONTEXT_ATTR_COUNT, |&b| { + b == SECURITY_CONTEXT_SPLITER + }); + + let mut new_context = Vec::new(); + for (i, attr) in attrs.enumerate() { + new_context.extend_from_slice(if i != index { attr } else { value }); + new_context.push(SECURITY_CONTEXT_SPLITER); + } + new_context.pop(); + + self.0 = OsString::from_vec(new_context); + Ok(()) + } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SecurityContext { - pub user: OsString, - pub role: OsString, - pub kind: OsString, - pub level: OsString, +impl SecurityContext { + pub fn parse>(value: S) -> std::io::Result { + let context = value.as_ref(); + if context.is_empty() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + + let spliter_count = context + .as_bytes() + .iter() + .filter(|&b| *b == SECURITY_CONTEXT_SPLITER) + .count(); + if spliter_count < SECURITY_CONTEXT_SPLITER_COUNT { + return Err(std::io::Error::from(Errno::EINVAL)); + } + + Ok(Self(context.to_os_string())) + } + + pub fn get_user(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_USER_INDEX) + } + + pub fn get_role(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_ROLE_INDEX) + } + + pub fn get_type(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_TYPE_INDEX) + } + + pub fn get_level(&self) -> &OsStr { + self.get_attribute(SECURITY_CONTEXT_LEVEL_INDEX) + } + + pub fn set_user>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_USER_INDEX, value) + } + + pub fn set_role>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_ROLE_INDEX, value) + } + + pub fn set_type>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_TYPE_INDEX, value) + } + + pub fn set_level>(&mut self, value: S) -> std::io::Result<()> { + self.set_attribute(SECURITY_CONTEXT_LEVEL_INDEX, value) + } + + pub fn as_os_str(&self) -> &OsStr { + &self.0 + } } -impl AsRef for SecurityContext { - fn as_ref(&self) -> &SecurityContext { - self +impl Debug for SecurityContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) } } -pub fn get_security_context

(file_path: P) -> Result +impl AsRef for SecurityContext { + fn as_ref(&self) -> &OsStr { + self.as_os_str() + } +} + +pub fn get_security_context

(file_path: P) -> std::io::Result where P: AsRef, { - let value = fs::getxattr(file_path, SELINUX_XATTR_NAME)?; - let data = value.split(SELINUX_XATTR_SPLITTER).collect::>(); - ensure!( - data.len() == SECURITY_XATTR_LEN, - "Failed to parse selinux security context" - ); - - Ok(SecurityContext { - user: data[0].to_os_string(), - role: data[1].to_os_string(), - kind: data[2].to_os_string(), - level: data[3].to_os_string(), - }) + SecurityContext::parse(fs::getxattr(file_path, SECURITY_CONTEXT_XATTR_NAME)?) } -pub fn set_security_context(file_path: P, value: S) -> Result<()> +pub fn set_security_context

(file_path: P, value: &SecurityContext) -> std::io::Result<()> where P: AsRef, - S: AsRef, { - let old_value = get_security_context(&file_path)?; - let new_value = value.as_ref(); + fs::setxattr(file_path, SECURITY_CONTEXT_XATTR_NAME, value) +} + +#[cfg(test)] +mod test { + use std::{fs, path::Path}; - if &old_value == new_value { - return Ok(()); + use super::*; + + #[test] + fn test_selinux_status() { + let status = self::get_status(); + println!("SELinux status: {}", status); + + let sys_file = Path::new(self::SELINUX_SYS_FILE); + if sys_file.exists() { + assert!(self::get_status() == Status::Disabled); + } else { + assert!(self::get_status() == Status::Disabled); + } + assert!(self::set_status(Status::Disabled).is_err()); } - let new_context = concat_os!( - &new_value.user, - SELINUX_XATTR_SPLITTER, - &new_value.role, - SELINUX_XATTR_SPLITTER, - &new_value.kind, - SELINUX_XATTR_SPLITTER, - &new_value.level, - ); - fs::setxattr(&file_path, SELINUX_XATTR_NAME, new_context)?; + #[test] + fn test_security_context_parse() { + const TEST_CASES: &[&str] = &[ + "system_u:object_r:bin_t:s0", + "user_u:role_r:type_t:s0:c1,c2", + "user.dom:role-1:type_x:s0:c1.c2", + "a:b:c:d.e-f,g", + "a_:b_:c_:d_", + ]; + for str in TEST_CASES { + let result = SecurityContext::parse(str).is_ok(); + assert!(result, "Failed to parse security context '{}'", str); + } + } - Ok(()) + #[test] + fn test_security_context_get() { + const TEST_CASES: &[(&str, [&str; 4])] = &[ + ( + "system_u:object_r:bin_t:s0", + ["system_u", "object_r", "bin_t", "s0"], + ), + ( + "user_u:role_r:type_t:s0:c1,c2", + ["user_u", "role_r", "type_t", "s0:c1,c2"], + ), + ( + "user.dom:role-1:type_x:s0:c1.c2", + ["user.dom", "role-1", "type_x", "s0:c1.c2"], + ), + ("a:b:c:d.e-f,g", ["a", "b", "c", "d.e-f,g"]), + ("a_:b_:c_:d_", ["a_", "b_", "c_", "d_"]), + ]; + + for (case, attrs) in TEST_CASES { + let context = SecurityContext::parse(case).expect("Failed to parse security context"); + assert_eq!(context.get_user(), attrs[0]); + assert_eq!(context.get_role(), attrs[1]); + assert_eq!(context.get_type(), attrs[2]); + assert_eq!(context.get_level(), attrs[3]); + } + } + + #[test] + fn test_security_context_set() { + const DEFAULT_CONTEXT: &str = "unconfined_u:object_r:default_t:s0"; + const TEST_CASES: &[(&str, [&str; 4])] = &[ + ( + "system_u:object_r:bin_t:s0", + ["system_u", "object_r", "bin_t", "s0"], + ), + ( + "user_u:role_r:type_t:s0:c1,c2", + ["user_u", "role_r", "type_t", "s0:c1,c2"], + ), + ( + "user.dom:role-1:type_x:s0:c1.c2", + ["user.dom", "role-1", "type_x", "s0:c1.c2"], + ), + ("a:b:c:d.e-f,g", ["a", "b", "c", "d.e-f,g"]), + ("a_:b_:c_:d_", ["a_", "b_", "c_", "d_"]), + ]; + + for (result, attrs) in TEST_CASES { + let mut context = + SecurityContext::parse(DEFAULT_CONTEXT).expect("Failed to parse security context"); + assert!(context.set_user(attrs[0]).is_ok()); + assert!(context.set_role(attrs[1]).is_ok()); + assert!(context.set_type(attrs[2]).is_ok()); + assert!(context.set_level(attrs[3]).is_ok()); + println!("{:?}", context); + + assert_eq!(context.as_os_str(), *result); + } + } + + #[test] + fn test_get_set_security_context() { + const TEST_FILE: &str = "selinux_test"; + const TEST_CONTEXT: &str = "unconfined_u:object_r:default_t:s0"; + + let file_path = std::env::temp_dir().join(TEST_FILE); + + fs::remove_file(&file_path).ok(); + fs::write(&file_path, TEST_FILE).expect("Failed to write test file"); + + let set_context = SecurityContext::parse(TEST_CONTEXT).expect("Invalid security context"); + self::set_security_context(&file_path, &set_context) + .expect("Failed to set security context"); + println!("set context: {:#?}", set_context); + + let get_context = + self::get_security_context(&file_path).expect("Failed to get security context"); + println!("get context: {:#?}", get_context); + + assert_eq!(set_context, get_context); + fs::remove_file(&file_path).expect("Failed to remove test file"); + } } diff --git a/syscare-common/src/os/umask.rs b/syscare-common/src/os/umask.rs index ab624cf1..cb0d3ee6 100644 --- a/syscare-common/src/os/umask.rs +++ b/syscare-common/src/os/umask.rs @@ -12,45 +12,49 @@ * See the Mulan PSL v2 for more details. */ -use nix::sys::stat::{umask, Mode}; +use nix::{libc::mode_t, sys::stat}; -pub fn set_umask(mode: u32) -> u32 { - umask(Mode::from_bits_truncate(mode)).bits() +pub fn set_umask(mode: mode_t) -> mode_t { + stat::umask(stat::Mode::from_bits_truncate(mode)).bits() } -#[test] -fn test() { - use std::{fs, fs::File, os::unix::fs::PermissionsExt}; - - const FILE_PATH: &str = "/tmp/umask_test"; - const UMASK1: u32 = 0o077; // 10600 - const UMASK2: u32 = 0o022; // 10644 - - fs::remove_file(FILE_PATH).ok(); - - println!("Testing umask {:03o}...", UMASK1); - set_umask(UMASK1); - let file1 = File::create(FILE_PATH).expect("Failed to create file"); - let perm1 = file1 - .metadata() - .map(|s| s.permissions()) - .expect("Failed to read file permission"); - - println!("umask: {:03o}, perm: {:05o}", UMASK1, perm1.mode()); - - drop(file1); - fs::remove_file(FILE_PATH).ok(); - - println!("Testing umask {:03o}...", UMASK2); - set_umask(UMASK2); - let file2 = File::create(FILE_PATH).expect("Failed to create file"); - let perm2 = file2 - .metadata() - .map(|s| s.permissions()) - .expect("Failed to read file permission"); - - println!("umask: {:03o}, perm: {:05o}", UMASK2, perm2.mode()); - - drop(file2); - fs::remove_file(FILE_PATH).ok(); +#[cfg(test)] +mod test { + #[test] + fn test_set_umask() { + use std::{fs, fs::File, os::unix::fs::PermissionsExt}; + + let file_path = std::env::temp_dir().join("umask_test"); + const UMASK1: u32 = 0o077; // 10600 + const UMASK2: u32 = 0o022; // 10644 + + fs::remove_file(&file_path).ok(); + + println!("Testing umask {:03o}...", UMASK1); + super::set_umask(UMASK1); + let file1 = File::create(&file_path).expect("Failed to create file"); + let perm1 = file1 + .metadata() + .map(|s| s.permissions()) + .expect("Failed to read file permission"); + println!("umask: {:03o}, perm: {:05o}", UMASK1, perm1.mode()); + assert_eq!(perm1.mode() & 0o777, 0o600); + + drop(file1); + fs::remove_file(&file_path).expect("Failed to remove file"); + + println!("Testing umask {:03o}...", UMASK2); + super::set_umask(UMASK2); + let file2 = File::create(&file_path).expect("Failed to create file"); + let perm2 = file2 + .metadata() + .map(|s| s.permissions()) + .expect("Failed to read file permission"); + + println!("umask: {:03o}, perm: {:05o}", UMASK2, perm2.mode()); + assert_eq!(perm2.mode() & 0o777, 0o644); + + drop(file2); + fs::remove_file(&file_path).expect("Failed to remove file"); + } } diff --git a/syscare-common/src/os/user.rs b/syscare-common/src/os/user.rs index db2e10ec..1e79cdb8 100644 --- a/syscare-common/src/os/user.rs +++ b/syscare-common/src/os/user.rs @@ -12,80 +12,94 @@ * See the Mulan PSL v2 for more details. */ -use std::{ - ffi::{CString, OsStr}, - path::{Path, PathBuf}, -}; - -use lazy_static::lazy_static; -use nix::unistd::{getuid, Gid, Uid, User}; - -use crate::ffi::CStrExt; - -fn info() -> &'static User { - lazy_static! { - static ref USER_INFO: User = User::from_uid(getuid()) - .unwrap_or_default() - .unwrap_or(User { - name: String::from("root"), - passwd: CString::default(), - uid: Uid::from_raw(0), - gid: Gid::from_raw(0), - gecos: CString::default(), - dir: PathBuf::from("/root"), - shell: PathBuf::from("/bin/sh"), - }); - } - &USER_INFO +use std::{ffi::OsString, os::unix::ffi::OsStringExt, path::PathBuf}; + +use nix::unistd; + +#[inline(always)] +fn userinfo() -> unistd::User { + unistd::User::from_uid(unistd::getuid()) + .expect("Failed to get user infomation") + .expect("Invalid user id") } -pub fn name() -> &'static str { - self::info().name.as_str() +pub fn uid() -> u32 { + unistd::getuid().as_raw() } -pub fn passwd() -> &'static OsStr { - self::info().passwd.as_os_str() +pub fn gid() -> u32 { + unistd::getgid().as_raw() } -pub fn id() -> u32 { - self::info().uid.as_raw() +pub fn name() -> String { + self::userinfo().name } -pub fn gid() -> u32 { - self::info().gid.as_raw() +pub fn passwd() -> OsString { + OsString::from_vec(self::userinfo().passwd.into_bytes()) } -pub fn gecos() -> &'static OsStr { - self::info().gecos.as_os_str() +pub fn gecos() -> OsString { + OsString::from_vec(self::userinfo().gecos.into_bytes()) } -pub fn home() -> &'static Path { - self::info().dir.as_path() +pub fn home() -> PathBuf { + self::userinfo().dir } -pub fn shell() -> &'static Path { - self::info().shell.as_path() +pub fn shell() -> PathBuf { + self::userinfo().shell } -#[test] -fn test() { - println!("name: {}", self::name()); - assert!(!self::name().is_empty()); +#[cfg(test)] +mod test { + use super::*; - println!("passwd: {}", self::passwd().to_string_lossy()); - assert!(!self::passwd().is_empty()); + #[test] + fn test_uid() { + let uid = self::uid(); + println!("uid: {}", uid); + assert!(uid < u32::MAX); + } - println!("id: {}", self::id()); - assert!(id() < u32::MAX); + #[test] + fn test_gid() { + let gid = self::gid(); + println!("gid: {}", gid); + assert!(gid < u32::MAX); + } - println!("gid: {}", self::gid()); - assert!(gid() < u32::MAX); + #[test] + fn test_name() { + let name = self::name(); + println!("name: {}", name); + assert!(!name.is_empty()); + } + + #[test] + fn test_passwd() { + let passwd = self::passwd(); + println!("passwd: {}", passwd.to_string_lossy()); + assert!(!passwd.is_empty()); + } - println!("gecos: {}", self::gecos().to_string_lossy()); + #[test] + fn test_gecos() { + let gecos = self::gecos(); + println!("gecos: {}", gecos.to_string_lossy()); + } - println!("home: {}", self::home().display()); - assert!(self::home().exists()); + #[test] + fn test_home() { + let home = self::home(); + println!("home: {}", home.display()); + assert!(home.exists()); + } - println!("shell: {}", self::shell().display()); - assert!(self::home().exists()); + #[test] + fn test_shell() { + let shell = self::shell(); + println!("shell: {}", shell.display()); + assert!(shell.exists()); + } } diff --git a/syscare-common/src/process/args.rs b/syscare-common/src/process/args.rs deleted file mode 100644 index 7d7e0941..00000000 --- a/syscare-common/src/process/args.rs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::ffi::{OsStr, OsString}; - -#[derive(Clone, Default)] -pub struct CommandArgs { - args: Vec, -} - -impl CommandArgs { - pub fn new() -> Self { - Self { args: Vec::new() } - } - - pub fn arg(&mut self, arg: S) -> &mut Self - where - S: AsRef, - { - self.args.push(arg.as_ref().to_os_string()); - self - } - - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - for arg in args { - self.arg(arg); - } - self - } -} - -impl IntoIterator for CommandArgs { - type Item = OsString; - - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.args.into_iter() - } -} diff --git a/syscare-common/src/process/child.rs b/syscare-common/src/process/child.rs index 0e8cf546..5379ef20 100644 --- a/syscare-common/src/process/child.rs +++ b/syscare-common/src/process/child.rs @@ -13,58 +13,57 @@ */ use std::{ - ffi::OsString, - ops::Deref, - os::unix::process::ExitStatusExt, - process::{Child as StdChild, ExitStatus as StdExitStatus}, - thread::JoinHandle, + ffi::OsString, ops::Deref, os::unix::process::ExitStatusExt, process, thread::JoinHandle, }; use anyhow::{anyhow, ensure, Context, Result}; use log::trace; -use super::{Stdio, StdioLevel}; +use super::output; pub struct Child { - pub(super) id: u32, pub(super) name: String, - pub(super) stdio_level: StdioLevel, - pub(super) inner: StdChild, + pub(super) child: process::Child, + pub(super) log_level: output::LogLevel, } impl Child { - fn capture_stdio(&mut self) -> Result> { - Stdio::new( - self.name.clone(), - self.inner - .stdout - .take() - .context("Failed to capture stdout")?, - self.inner - .stderr - .take() - .context("Failed to capture stderr")?, - self.stdio_level, - ) - .capture() + fn redirect_outputs(&mut self) -> Result> { + let stdout = self + .child + .stdout + .take() + .context("Failed to capture stdout")?; + let stderr = self + .child + .stderr + .take() + .context("Failed to capture stderr")?; + let outputs = output::Outputs::new(stdout, stderr, self.log_level); + + std::thread::Builder::new() + .name(self.name.clone()) + .spawn(|| outputs.redirect()) + .with_context(|| format!("Failed to create thread {}", self.name)) } } impl Child { pub fn kill(&mut self) -> Result<()> { - self.inner + let id = self.child.id(); + self.child .kill() - .with_context(|| format!("Failed to kill process {} ({})", self.name, self.id)) + .with_context(|| format!("Failed to kill process {} ({})", self.name, id)) } pub fn wait(&mut self) -> Result { + let id = self.child.id(); let status = self - .inner + .child .wait() - .with_context(|| format!("Failed to wait process {} ({})", self.name, self.id))?; - + .with_context(|| format!("Failed to wait process {} ({})", self.name, id))?; let exit_status = ExitStatus { - id: self.id, + id, name: self.name.clone(), status, }; @@ -79,24 +78,25 @@ impl Child { } pub fn wait_with_output(&mut self) -> Result { - let stdio_thread = self.capture_stdio()?; + let thread = self.redirect_outputs()?; let status = self.wait()?; - let (stdout, stderr) = stdio_thread + let (stdout, stderr) = thread .join() .map_err(|_| anyhow!("Failed to join stdio thread"))?; - Ok(Output { + let output = Output { status, stdout, stderr, - }) + }; + Ok(output) } } pub struct ExitStatus { id: u32, name: String, - status: StdExitStatus, + status: process::ExitStatus, } impl ExitStatus { diff --git a/syscare-common/src/process/command.rs b/syscare-common/src/process/command.rs deleted file mode 100644 index 06d3568e..00000000 --- a/syscare-common/src/process/command.rs +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ffi::OsStr, - path::Path, - process::{Command as StdCommand, Stdio}, -}; - -use anyhow::{Context, Result}; -use log::{trace, Level}; - -use super::{Child, ExitStatus, Output, StdioLevel}; - -pub struct Command { - inner: StdCommand, - stdio_level: StdioLevel, -} - -impl Command { - pub fn new>(program: S) -> Self { - Self { - inner: StdCommand::new(program), - stdio_level: StdioLevel::default(), - } - } - - pub fn arg>(&mut self, arg: S) -> &mut Self { - self.inner.arg(arg); - self - } - - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - for arg in args { - self.arg(arg.as_ref()); - } - self - } - - pub fn env(&mut self, key: K, val: V) -> &mut Self - where - K: AsRef, - V: AsRef, - { - self.inner.env(key, val); - self - } - - pub fn envs(&mut self, vars: I) -> &mut Self - where - I: IntoIterator, - K: AsRef, - V: AsRef, - { - for (ref key, ref val) in vars { - self.env(key, val); - } - self - } - - pub fn env_clear(&mut self) -> &mut Self { - self.inner.env_clear(); - self - } - - pub fn current_dir>(&mut self, dir: P) -> &mut Self { - self.inner.current_dir(dir); - self - } - - pub fn stdin>(&mut self, cfg: T) -> &mut Self { - self.inner.stdin(cfg); - self - } - - pub fn stdout>>(&mut self, level: T) -> &mut Self { - self.stdio_level.stdout = level.into(); - self - } - - pub fn stderr(&mut self, level: Level) -> &mut Self { - self.stdio_level.stderr = level.into(); - self - } - - pub fn spawn(&mut self) -> Result { - let name = Path::new(self.inner.get_program()) - .file_name() - .context("Failed to get process name")? - .to_string_lossy() - .to_string(); - - trace!("Executing {:?}", self.inner); - let child = self - .inner - .spawn() - .with_context(|| format!("Failed to start {}", name))?; - - Ok(Child { - id: child.id(), - name, - stdio_level: self.stdio_level, - inner: child, - }) - } - - pub fn run(&mut self) -> Result { - self.inner.stdout(Stdio::null()).stderr(Stdio::null()); - - self.spawn()?.wait() - } - - pub fn run_with_output(&mut self) -> Result { - self.inner.stdout(Stdio::piped()).stderr(Stdio::piped()); - - self.spawn()?.wait_with_output() - } -} diff --git a/syscare-common/src/process/envs.rs b/syscare-common/src/process/envs.rs deleted file mode 100644 index 038ad60e..00000000 --- a/syscare-common/src/process/envs.rs +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - collections::HashMap, - ffi::{OsStr, OsString}, -}; - -pub struct CommandEnvs { - envs: HashMap, -} - -impl CommandEnvs { - pub fn new() -> Self { - Self { - envs: HashMap::new(), - } - } - - pub fn env(&mut self, key: K, value: V) -> &mut Self - where - K: AsRef, - V: AsRef, - { - self.envs - .insert(key.as_ref().to_os_string(), value.as_ref().to_os_string()); - self - } - - pub fn envs(&mut self, vars: I) -> &mut Self - where - I: IntoIterator, - K: AsRef, - V: AsRef, - { - for (key, value) in vars { - self.env(key, value); - } - self - } -} - -impl Default for CommandEnvs { - fn default() -> Self { - Self::new() - } -} - -impl IntoIterator for CommandEnvs { - type Item = (OsString, OsString); - - type IntoIter = std::collections::hash_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.envs.into_iter() - } -} diff --git a/syscare-common/src/process/mod.rs b/syscare-common/src/process/mod.rs index 894707cc..1fe472c9 100644 --- a/syscare-common/src/process/mod.rs +++ b/syscare-common/src/process/mod.rs @@ -12,151 +12,434 @@ * See the Mulan PSL v2 for more details. */ -mod args; +use std::{ + collections::HashMap, + ffi::{OsStr, OsString}, + path::Path, + process, +}; + +use anyhow::{Context, Result}; +use log::{trace, Level}; + mod child; -mod command; -mod envs; -mod stdio; +mod output; + +#[derive(Debug, Clone)] +pub struct CommandArgs(Vec); + +impl CommandArgs { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn arg(&mut self, arg: S) -> &mut Self + where + S: AsRef, + { + self.0.push(arg.as_ref().to_os_string()); + self + } + + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + for arg in args { + self.arg(arg); + } + self + } +} -pub use args::*; -pub use child::*; -pub use command::*; -pub use envs::*; +impl Default for CommandArgs { + fn default() -> Self { + Self::new() + } +} -use stdio::{Stdio, StdioLevel}; +impl IntoIterator for CommandArgs { + type Item = OsString; -#[test] -fn test() { - use log::Level; - use std::fs::File; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[derive(Debug, Clone)] +pub struct CommandEnvs(HashMap); + +impl CommandEnvs { + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn env(&mut self, key: K, value: V) -> &mut Self + where + K: AsRef, + V: AsRef, + { + self.0 + .insert(key.as_ref().to_os_string(), value.as_ref().to_os_string()); + self + } + + pub fn envs(&mut self, vars: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + for (key, value) in vars { + self.env(key, value); + } + self + } +} - use crate::ffi::OsStrExt; +impl Default for CommandEnvs { + fn default() -> Self { + Self::new() + } +} + +impl IntoIterator for CommandEnvs { + type Item = (OsString, OsString); - println!("Testing Command::new()..."); - let mut echo_cmd = Command::new("echo"); - let mut env_cmd = Command::new("env"); - let mut pwd_cmd = Command::new("pwd"); - let mut grep_cmd = Command::new("grep"); - let mut ls_cmd = Command::new("ls"); + type IntoIter = std::collections::hash_map::IntoIter; - let mut test_cmd = Command::new("test"); - let mut cat_cmd = Command::new("cat"); - - let mut err_cmd = Command::new("/cmd/not/exist"); - - println!("Testing Command::arg()..."); - echo_cmd.arg("Test:"); - - test_cmd.arg("1"); - ls_cmd.arg("/file/not/exist"); - - println!("Testing Command::args()..."); - echo_cmd.args(["Hello", "World!"]); - - println!("Testing Command::env_clear()..."); - env_cmd.env_clear(); - - println!("Testing Command::env()..."); - env_cmd.env("test_key1", "test_val1"); - - println!("Testing Command::envs()..."); - env_cmd.envs([("test_key2", "test_val2"), ("test_key3", "test_val3")]); - - println!("Testing Command::current_dir()..."); - pwd_cmd.current_dir("/tmp"); - - println!("Testing Command::stdin()..."); - grep_cmd.stdin(File::open("/proc/self/maps").expect("Failed to open file")); - grep_cmd.arg("vdso"); - - println!("Testing Command::stdout()..."); - echo_cmd.stdout(Level::Info); - - println!("Testing Command::stderr()..."); - echo_cmd.stderr(Level::Info); - - println!("Testing Command::spawn()..."); - let mut echo_proc = echo_cmd.spawn().expect("Failed to spawn process"); - let mut env_proc = env_cmd.spawn().expect("Failed to spawn process"); - let mut pwd_proc = pwd_cmd.spawn().expect("Failed to spawn process"); - let mut grep_proc = grep_cmd.spawn().expect("Failed to spawn process"); - let mut ls_proc = ls_cmd.spawn().expect("Failed to spawn process"); - - let mut test_proc = test_cmd.spawn().expect("Failed to spawn process"); - let mut cat_proc = cat_cmd.spawn().expect("Failed to spawn process"); - - assert_eq!(err_cmd.spawn().is_err(), true); - - println!("Testing Child::kill()..."); - cat_proc.kill().expect("Failed to kill process"); - - println!("Testing Child::wait()..."); - let test_status = test_proc.wait().expect("Failed to wait process"); - let cat_status = cat_proc.wait().expect("Process should not be waited"); - - println!("Testing Child::wait_with_output()..."); - let echo_output = echo_proc - .wait_with_output() - .expect("Failed to wait process"); - let env_output = env_proc.wait_with_output().expect("Failed to wait process"); - let pwd_output = pwd_proc.wait_with_output().expect("Failed to wait process"); - let grep_output = grep_proc - .wait_with_output() - .expect("Failed to wait process"); - let ls_output = ls_proc.wait_with_output().expect("Failed to wait process"); - - println!("Testing ExitStatus::exit_code()..."); - assert_eq!(test_status.exit_code(), 0); - assert_eq!(cat_status.exit_code(), 137); - - println!("Testing ExitStatus::exit_ok()..."); - assert_eq!(test_status.exit_ok().is_ok(), true); - assert_eq!(cat_status.exit_ok().is_ok(), false); - - println!("Testing ExitStatus::success()..."); - assert_eq!(test_status.success(), true); - assert_eq!(cat_status.success(), false); - - println!("Testing Output::exit_code()..."); - assert_eq!(echo_output.exit_code(), 0); - assert_eq!(env_output.exit_code(), 0); - assert_eq!(pwd_output.exit_code(), 0); - assert_eq!(grep_output.exit_code(), 0); - assert_eq!(ls_output.exit_code(), 2); - - println!("Testing Output::exit_ok()..."); - assert_eq!(echo_output.exit_ok().is_ok(), true); - assert_eq!(env_output.exit_ok().is_ok(), true); - assert_eq!(pwd_output.exit_ok().is_ok(), true); - assert_eq!(grep_output.exit_ok().is_ok(), true); - assert_eq!(ls_output.exit_ok().is_ok(), false); - - println!("Testing Output::success()..."); - assert_eq!(echo_output.success(), true); - assert_eq!(env_output.success(), true); - assert_eq!(pwd_output.success(), true); - assert_eq!(grep_output.success(), true); - assert_eq!(ls_output.success(), false); - - println!("Testing Output::stdout..."); - assert_eq!(echo_output.stdout.is_empty(), false); - assert_eq!(env_output.stdout.is_empty(), false); - assert_eq!(pwd_output.stdout.is_empty(), false); - assert_eq!(grep_output.stdout.is_empty(), false); - assert_eq!(ls_output.stdout.is_empty(), true); - - assert_eq!(echo_output.stdout, "Test: Hello World!"); - assert_eq!( - env_output.stdout, - "test_key1=test_val1\ntest_key2=test_val2\ntest_key3=test_val3" - ); - assert_eq!(pwd_output.stdout, "/tmp"); - assert_eq!(grep_output.stdout.contains("vdso"), true); - - println!("Testing ProcessOutput::stderr..."); - assert_eq!(echo_output.stderr.is_empty(), true); - assert_eq!(env_output.stderr.is_empty(), true); - assert_eq!(pwd_output.stderr.is_empty(), true); - assert_eq!(grep_output.stderr.is_empty(), true); - assert_eq!(ls_output.stderr.is_empty(), false); + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +pub struct Command { + inner: process::Command, + log_level: output::LogLevel, +} + +impl Command { + pub fn new>(program: S) -> Self { + Self { + inner: process::Command::new(program), + log_level: output::LogLevel::default(), + } + } + + pub fn arg>(&mut self, arg: S) -> &mut Self { + self.inner.arg(arg); + self + } + + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + for arg in args { + self.arg(arg.as_ref()); + } + self + } + + pub fn env(&mut self, key: K, val: V) -> &mut Self + where + K: AsRef, + V: AsRef, + { + self.inner.env(key, val); + self + } + + pub fn envs(&mut self, vars: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + for (key, val) in vars { + self.env(key, val); + } + self + } + + pub fn env_clear(&mut self) -> &mut Self { + self.inner.env_clear(); + self + } + + pub fn current_dir>(&mut self, dir: P) -> &mut Self { + self.inner.current_dir(dir); + self + } + + pub fn stdin>(&mut self, cfg: T) -> &mut Self { + self.inner.stdin(cfg); + self + } + + pub fn stdout(&mut self, level: Level) -> &mut Self { + self.log_level.stdout = Some(level); + self + } + + pub fn stderr(&mut self, level: Level) -> &mut Self { + self.log_level.stderr = Some(level); + self + } + + pub fn pipe_output(&mut self) -> &mut Self { + self.inner + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()); + self + } + + pub fn ignore_output(&mut self) -> &mut Self { + self.inner + .stdout(process::Stdio::null()) + .stderr(process::Stdio::null()); + self + } + + pub fn spawn(&mut self) -> Result { + let name = Path::new(self.inner.get_program()) + .file_name() + .map(|s| s.to_string_lossy().to_string()) + .unwrap_or_default(); + + trace!("Executing {:?}", self.inner); + let child = self + .inner + .spawn() + .with_context(|| format!("Failed to start {}", name))?; + let log_level = self.log_level; + + Ok(child::Child { + name, + child, + log_level, + }) + } + + pub fn run(&mut self) -> Result { + self.ignore_output().spawn()?.wait() + } + + pub fn run_with_output(&mut self) -> Result { + self.pipe_output().spawn()?.wait_with_output() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use log::Level; + use std::ffi::OsStr; + use std::path::Path; + use std::time::Duration; + + #[test] + fn test_command_args_new() { + let args = CommandArgs::new(); + assert!(args.0.is_empty()); + } + + #[test] + fn test_command_args_default() { + let args = CommandArgs::default(); + assert!(args.0.is_empty()); + } + + #[test] + fn test_command_args_arg() { + let mut args = CommandArgs::new(); + args.arg("test"); + assert_eq!(args.0.len(), 1); + assert_eq!(args.0[0], OsStr::new("test")); + } + + #[test] + fn test_command_args_args() { + let mut args = CommandArgs::new(); + args.args(vec!["arg1", "arg2"]); + assert_eq!(args.0.len(), 2); + assert_eq!(args.0[0], OsStr::new("arg1")); + assert_eq!(args.0[1], OsStr::new("arg2")); + } + + #[test] + fn test_command_args_into_iter() { + let mut args = CommandArgs::new(); + args.args(vec!["a", "b", "c"]); + let mut iter = args.into_iter(); + assert_eq!(iter.next(), Some(OsString::from("a"))); + assert_eq!(iter.next(), Some(OsString::from("b"))); + assert_eq!(iter.next(), Some(OsString::from("c"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_command_envs_new() { + let envs = CommandEnvs::new(); + assert!(envs.0.is_empty()); + } + + #[test] + fn test_command_envs_default() { + let envs = CommandEnvs::default(); + assert!(envs.0.is_empty()); + } + + #[test] + fn test_command_envs_env() { + let mut envs = CommandEnvs::new(); + envs.env("KEY", "VALUE"); + assert_eq!(envs.0.len(), 1); + assert_eq!( + envs.0.get(&OsString::from("KEY")), + Some(&OsString::from("VALUE")) + ); + } + + #[test] + fn test_command_envs_envs() { + let mut envs = CommandEnvs::new(); + envs.envs([("K1", "V1"), ("K2", "V2")]); + assert_eq!(envs.0.len(), 2); + assert_eq!( + envs.0.get(&OsString::from("K1")), + Some(&OsString::from("V1")) + ); + assert_eq!( + envs.0.get(&OsString::from("K2")), + Some(&OsString::from("V2")) + ); + } + + #[test] + fn test_command_new() { + let cmd = Command::new("test"); + assert_eq!(cmd.inner.get_program(), OsStr::new("test")); + assert!(cmd.inner.get_args().next().is_none()); + } + + #[test] + fn test_command_arg() { + let mut cmd = Command::new("test"); + cmd.arg("arg1"); + let mut args = cmd.inner.get_args(); + assert_eq!(args.next(), Some(OsStr::new("arg1"))); + assert!(args.next().is_none()); + } + + #[test] + fn test_command_args() { + let mut cmd = Command::new("test"); + cmd.args(["a", "b"]); + let mut args = cmd.inner.get_args(); + assert_eq!(args.next(), Some(OsStr::new("a"))); + assert_eq!(args.next(), Some(OsStr::new("b"))); + assert!(args.next().is_none()); + } + + #[test] + fn test_command_env() { + let mut cmd = Command::new("test"); + cmd.env("K", "V"); + let envs = cmd.inner.get_envs().collect::>(); + assert_eq!(envs.get(&OsStr::new("K")), Some(&Some(OsStr::new("V")))); + } + + #[test] + fn test_command_envs() { + let mut cmd = Command::new("test"); + cmd.envs([("K1", "V1"), ("K2", "V2")]); + + let envs = cmd.inner.get_envs().collect::>(); + assert_eq!(envs.len(), 2); + assert_eq!(envs.get(&OsStr::new("K1")), Some(&Some(OsStr::new("V1")))); + assert_eq!(envs.get(&OsStr::new("K2")), Some(&Some(OsStr::new("V2")))); + } + + #[test] + fn test_command_env_clear() { + let mut cmd = Command::new("test"); + cmd.env("K", "V").env_clear(); + let envs = cmd.inner.get_envs().collect::>(); + assert!(envs.is_empty()); + } + + #[test] + fn test_command_current_dir() { + let mut cmd = Command::new("test"); + cmd.current_dir("/tmp"); + assert_eq!(cmd.inner.get_current_dir(), Some(Path::new("/tmp"))); + } + + #[test] + fn test_command_stdout_stderr() { + let mut cmd = Command::new("test"); + cmd.stdout(Level::Info).stderr(Level::Error); + assert_eq!(cmd.log_level.stdout, Some(Level::Info)); + assert_eq!(cmd.log_level.stderr, Some(Level::Error)); + } + + #[test] + fn test_command_pipe_output() { + let mut cmd = Command::new("test"); + + cmd.pipe_output(); + assert!(cmd.inner.spawn().unwrap().stdout.is_some()); + assert!(cmd.inner.spawn().unwrap().stderr.is_some()); + } + + #[test] + fn test_command_ignore_output() { + let mut cmd = Command::new("test"); + + cmd.pipe_output(); + assert!(cmd.inner.spawn().unwrap().stdout.is_some()); + assert!(cmd.inner.spawn().unwrap().stderr.is_some()); + } + + #[test] + fn test_command_spawn_kill() { + let mut cmd = Command::new("yes"); + cmd.ignore_output(); + + let mut child = cmd.spawn().unwrap(); + std::thread::sleep(Duration::from_millis(100)); + + assert!(child.kill().is_ok()); + } + + #[test] + fn test_command_spawn_wait() { + let mut cmd = Command::new("echo"); + cmd.arg("test").ignore_output(); + + let mut child = cmd.spawn().unwrap(); + let status = child.wait().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_command_run() { + let mut cmd = Command::new("true"); + let status = cmd.run().unwrap(); + assert!(status.success()); + } + + #[test] + fn test_command_run_with_output() { + let mut cmd = Command::new("env"); + let output = cmd.run_with_output().unwrap(); + + assert!(output.status.success()); + assert!(!output.stdout.is_empty()); + } } diff --git a/syscare-common/src/process/output.rs b/syscare-common/src/process/output.rs new file mode 100644 index 00000000..5e97c7ed --- /dev/null +++ b/syscare-common/src/process/output.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Mulan PSL v2 +/* + * Copyright (c) 2024 Huawei Technologies Co., Ltd. + * syscare-common is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use std::{ + ffi::OsString, + io::Read, + os::unix::{ffi::OsStringExt, io::AsRawFd}, + process::{ChildStderr, ChildStdout}, +}; + +use log::{error, log, Level}; +use nix::poll::{poll, PollFd, PollFlags}; + +const STREAM_BUFFER_SIZE: usize = 4096; + +#[derive(Debug, Clone, Copy)] +pub struct LogLevel { + pub stdout: Option, + pub stderr: Option, +} + +impl Default for LogLevel { + fn default() -> Self { + Self { + stdout: None, + stderr: Some(Level::Error), + } + } +} + +struct Stream { + stream: R, + buffer: Vec, + offset: usize, + log_level: Option, + is_closed: bool, +} + +impl Stream { + fn new(stream: R, log_level: Option) -> Self { + Self { + stream, + buffer: Vec::with_capacity(STREAM_BUFFER_SIZE), + offset: 0, + log_level, + is_closed: false, + } + } + + fn read_buf(&mut self) -> std::io::Result { + if self.buffer.capacity().wrapping_sub(self.buffer.len()) < STREAM_BUFFER_SIZE { + self.buffer.reserve(STREAM_BUFFER_SIZE); + } + + let spare_cap = self.buffer.spare_capacity_mut(); + let spare_buf = unsafe { + std::slice::from_raw_parts_mut(spare_cap.as_mut_ptr() as *mut u8, spare_cap.len()) + }; + + let len = self.stream.read(spare_buf)?; + unsafe { + self.buffer.set_len(self.buffer.len() + len); + } + + Ok(len) + } + + fn print_logs(&mut self) { + if let Some(level) = self.log_level { + let start = self.offset; + if start >= self.buffer.len() { + return; + } + + let slice = if !self.is_closed { + let end = match self.buffer[start..].iter().rposition(|&b| b == b'\n') { + Some(pos) => start + pos, + None => return, + }; + self.offset = end + 1; // skip '\n' + &self.buffer[start..end] + } else { + self.offset = self.buffer.len(); + &self.buffer[start..] + }; + if slice.is_empty() { + return; + } + + let lines = slice.split(|&b| b == b'\n').map(String::from_utf8_lossy); + for line in lines { + log!(level, "{}", line); + } + } + } + + fn handle_revents(&mut self, revents: PollFlags) { + if revents.contains(PollFlags::POLLIN) { + match self.read_buf() { + Ok(0) => self.is_closed = true, // EOF + Ok(_) => {} + Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {} + Err(e) => { + error!("Failed to read stream, {}", e); + self.is_closed = true; + } + } + } + if revents.contains(PollFlags::POLLHUP) { + self.is_closed = true; + } + + self.print_logs(); + } +} + +pub struct Outputs { + fds: [PollFd; 2], + stdout: Stream, + stderr: Stream, +} + +impl Outputs { + pub fn new(stdout: ChildStdout, stderr: ChildStderr, log_level: LogLevel) -> Self { + Self { + fds: [ + PollFd::new(stdout.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLHUP), + PollFd::new(stderr.as_raw_fd(), PollFlags::POLLIN | PollFlags::POLLHUP), + ], + stdout: Stream::new(stdout, log_level.stdout), + stderr: Stream::new(stderr, log_level.stderr), + } + } + + pub fn redirect(mut self) -> (OsString, OsString) { + const POLL_TIMEOUT: i32 = -1; + + loop { + match poll(&mut self.fds, POLL_TIMEOUT) { + Ok(events) => { + if events == 0 { + break; + } + for (i, fd) in self.fds.iter().enumerate() { + let revents = fd.revents().expect("Invalid poll event"); + match i { + 0 => self.stdout.handle_revents(revents), + 1 => self.stderr.handle_revents(revents), + _ => unreachable!("Invalid poll fd"), + }; + } + } + Err(e) => { + error!("Failed to poll events, {}", e); + break; + } + } + if self.stdout.is_closed && self.stderr.is_closed { + break; + } + } + + ( + OsString::from_vec(self.stdout.buffer), + OsString::from_vec(self.stderr.buffer), + ) + } +} diff --git a/syscare-common/src/process/stdio.rs b/syscare-common/src/process/stdio.rs deleted file mode 100644 index 9a93e563..00000000 --- a/syscare-common/src/process/stdio.rs +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscare-common is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - collections::HashMap, - ffi::OsString, - io::BufReader, - os::unix::{ - ffi::OsStringExt, - io::{AsRawFd, RawFd}, - }, - process::{ChildStderr, ChildStdout}, - thread::JoinHandle, -}; - -use anyhow::{Context, Result}; -use log::{error, log, Level}; - -use crate::io::{BufReadOsLines, OsLines, Select, SelectResult}; - -#[derive(Debug, Clone, Copy)] -pub struct StdioLevel { - pub(super) stdout: Option, - pub(super) stderr: Option, -} - -impl Default for StdioLevel { - fn default() -> Self { - Self { - stdout: None, - stderr: Some(Level::Error), - } - } -} - -pub enum StdioOutput { - Stdout(OsString), - Stderr(OsString), -} - -enum StdioLines { - Stdout(OsLines>), - Stderr(OsLines>), -} - -struct StdioReader { - select: Select, - stdio_map: HashMap, - line_buf: Vec, -} - -impl StdioReader { - fn new(stdout: ChildStdout, stderr: ChildStderr) -> Self { - let line_buf = Vec::new(); - let stdio_map = HashMap::from([ - ( - stdout.as_raw_fd(), - StdioLines::Stdout(BufReader::new(stdout).os_lines()), - ), - ( - stderr.as_raw_fd(), - StdioLines::Stderr(BufReader::new(stderr).os_lines()), - ), - ]); - let select = Select::new(stdio_map.keys().copied()); - - Self { - select, - stdio_map, - line_buf, - } - } -} - -impl Iterator for StdioReader { - type Item = StdioOutput; - - fn next(&mut self) -> Option { - match self.select.select().context("Failed to select stdio") { - Ok(result) => { - let stdio_map = &mut self.stdio_map; - let outputs = result.into_iter().filter_map(|income| match income { - SelectResult::Readable(fd) => { - stdio_map.get_mut(&fd).and_then(|stdio| match stdio { - StdioLines::Stdout(lines) => { - lines.next().and_then(Result::ok).map(StdioOutput::Stdout) - } - StdioLines::Stderr(lines) => { - lines.next().and_then(Result::ok).map(StdioOutput::Stderr) - } - }) - } - _ => None, - }); - self.line_buf.extend(outputs); - } - Err(e) => { - error!("{:?}", e); - } - }; - - self.line_buf.pop() - } -} - -pub struct Stdio { - name: String, - stdout: ChildStdout, - stderr: ChildStderr, - level: StdioLevel, -} - -impl Stdio { - pub fn new(name: String, stdout: ChildStdout, stderr: ChildStderr, level: StdioLevel) -> Self { - Self { - name, - stdout, - stderr, - level, - } - } - - pub fn capture(self) -> Result> { - let stdio_level = self.level; - let stdio_reader = StdioReader::new(self.stdout, self.stderr); - - let thread_name = self.name.as_str(); - let thread = std::thread::Builder::new() - .name(thread_name.to_string()) - .spawn(move || -> (OsString, OsString) { - let mut stdout_buf = Vec::new(); - let mut stderr_buf = Vec::new(); - - for output in stdio_reader { - match output { - StdioOutput::Stdout(str) => { - if let Some(level) = stdio_level.stdout { - log!(level, "{}", str.to_string_lossy()); - } - stdout_buf.extend(str.into_vec()); - stdout_buf.push(b'\n'); - } - StdioOutput::Stderr(str) => { - if let Some(level) = stdio_level.stderr { - log!(level, "{}", str.to_string_lossy()); - } - stderr_buf.extend(str.into_vec()); - stderr_buf.push(b'\n'); - } - } - } - if stdout_buf.ends_with(b"\n") { - stdout_buf.pop(); - } - if stderr_buf.ends_with(b"\n") { - stderr_buf.pop(); - } - - ( - OsString::from_vec(stdout_buf), - OsString::from_vec(stderr_buf), - ) - }) - .with_context(|| format!("Failed to create thread {}", thread_name))?; - - Ok(thread) - } -} diff --git a/syscare-common/src/util/digest.rs b/syscare-common/src/util/digest.rs index 086b636d..4ed7357b 100644 --- a/syscare-common/src/util/digest.rs +++ b/syscare-common/src/util/digest.rs @@ -14,23 +14,21 @@ use std::path::Path; -use sha2::Digest; -use sha2::Sha256; +use nix::errno::Errno; +use sha2::{Digest, Sha256}; use crate::fs; -pub fn bytes>(bytes: S) -> String { - let mut hasher = Sha256::new(); - hasher.update(bytes); - - format!("{:#x}", hasher.finalize()) +pub fn bytes>(bytes: T) -> String { + format!("{:#x}", Sha256::digest(bytes)) } -pub fn file>(file: P) -> std::io::Result { - let mut hasher = Sha256::new(); - hasher.update(fs::read(file)?); - - Ok(format!("{:#x}", hasher.finalize())) +pub fn file>(path: P) -> std::io::Result { + let file_path = path.as_ref(); + if !file_path.is_file() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + Ok(self::bytes(&*fs::mmap(file_path)?)) } pub fn file_list(file_list: I) -> std::io::Result @@ -39,9 +37,201 @@ where P: AsRef, { let mut hasher = Sha256::new(); + for file in file_list { - hasher.update(fs::read(file)?); + let file_path = file.as_ref(); + if file_path.is_file() { + hasher.update(&*fs::mmap(file)?); + } } Ok(format!("{:#x}", hasher.finalize())) } + +pub fn dir>(path: P) -> std::io::Result { + let dir_path = path.as_ref(); + if !dir_path.is_dir() { + return Err(std::io::Error::from(Errno::EINVAL)); + } + + let mut file_list = fs::list_files(path, fs::TraverseOptions { recursive: true })?; + file_list.sort_unstable(); + + self::file_list(file_list) +} + +pub fn path>(path: P) -> std::io::Result { + if path.as_ref().is_file() { + self::file(path) + } else { + self::dir(path) + } +} + +#[cfg(test)] +mod tests { + use std::{ + fs::File, + io::Write, + path::PathBuf, + time::{SystemTime, UNIX_EPOCH}, + }; + + use super::*; + + fn unique_name(prefix: &str) -> std::io::Result { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(format!("{}_{}", prefix, timestamp.as_nanos())) + } + + fn create_temp_file(content: &[u8]) -> std::io::Result { + let temp_file = std::env::temp_dir().join(self::unique_name("digest_test")?); + + let mut file = File::create(&temp_file)?; + file.write_all(content)?; + + Ok(temp_file) + } + + fn create_temp_dir() -> std::io::Result { + let temp_dir = std::env::temp_dir(); + let test_dir = temp_dir.join(unique_name("test_dir")?); + + fs::create_dir(&test_dir)?; + File::create(test_dir.join("file1.txt"))?.write_all(b"file1")?; + File::create(test_dir.join("file2.bin"))?.write_all(b"file2")?; + fs::create_dir(test_dir.join("subdir"))?; + File::create(test_dir.join("subdir/file3.txt"))?.write_all(b"file3")?; + + Ok(test_dir) + } + + #[test] + fn test_bytes() -> std::io::Result<()> { + assert_eq!( + self::bytes(b"hello"), + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + ); + Ok(()) + } + + #[test] + fn test_file() -> std::io::Result<()> { + let file_path = self::create_temp_file(b"hello")?; + let hash = self::file(&file_path)?; + + std::fs::remove_file(&file_path).ok(); + assert_eq!( + hash, + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + ); + + Ok(()) + } + + #[test] + fn test_file_not_exists() -> std::io::Result<()> { + let result = self::file("/non_exist_file"); + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_file_is_dir() -> std::io::Result<()> { + let result = self::file(std::env::temp_dir()); + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_file_list() -> std::io::Result<()> { + let files = vec![ + self::create_temp_file(b"file1")?, + self::create_temp_file(b"file2")?, + self::create_temp_file(b"file3")?, + ]; + + let hash = self::file_list(&files)?; + for file in files { + std::fs::remove_file(file)?; + } + assert_eq!( + hash, + "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" + ); + + Ok(()) + } + + #[test] + fn test_dir() -> std::io::Result<()> { + let test_dir = self::create_temp_dir()?; + let hash = self::dir(&test_dir)?; + std::fs::remove_dir_all(test_dir)?; + + assert_eq!( + hash, + "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" + ); + Ok(()) + } + + #[test] + fn test_dir_not_exists() -> std::io::Result<()> { + let result = self::dir("/non_exist_file"); + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_dir_is_file() -> std::io::Result<()> { + let file_path = self::create_temp_file(b"hello")?; + let result = self::dir(&file_path); + std::fs::remove_file(file_path)?; + + assert!(result.is_err()); + if let Err(e) = &result { + assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput); + } + + Ok(()) + } + + #[test] + fn test_path() -> std::io::Result<()> { + let file_path = self::create_temp_file(b"hello")?; + let dir_path = self::create_temp_dir()?; + + let file_hash = self::path(&file_path)?; + let dir_hash = self::path(&dir_path)?; + std::fs::remove_file(&file_path)?; + std::fs::remove_dir_all(&dir_path)?; + + assert_eq!( + file_hash, + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + ); + assert_eq!( + dir_hash, + "d944e85974a48cfc20a944738d9617ad5ffde6e1219cf4c362dc058a47419848" + ); + Ok(()) + } +} -- Gitee From 6fba851cbbb8a7a2079ac999b5bf24de9f0cba98 Mon Sep 17 00:00:00 2001 From: renoseven Date: Thu, 29 May 2025 18:13:14 +0800 Subject: [PATCH 08/13] syscared: adapt common crate change Signed-off-by: renoseven --- syscared/src/main.rs | 21 ++++++++------------- syscared/src/patch/driver/kpatch/sys.rs | 12 ++++++------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/syscared/src/main.rs b/syscared/src/main.rs index f965497e..bfff3d7d 100644 --- a/syscared/src/main.rs +++ b/syscared/src/main.rs @@ -59,10 +59,6 @@ const LOG_DIR_PERM: u32 = 0o700; const SOCKET_FILE_PERM: u32 = 0o660; const SOCKET_FILE_PERM_STRICT: u32 = 0o600; -const MAIN_THREAD_NAME: &str = "main"; -const UNNAMED_THREAD_NAME: &str = ""; -const LOG_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; - struct Daemon { args: Arguments, config: Config, @@ -74,15 +70,14 @@ impl Daemon { now: &mut DeferredNow, record: &Record, ) -> std::io::Result<()> { + const UNNAMED_THREAD_NAME: &str = ""; + const LOG_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.6f"; + thread_local! { - static THREAD_NAME: String = std::thread::current().name().and_then(|name| { - if name == MAIN_THREAD_NAME { - return os::process::name().to_str(); - } - Some(name) - }) - .unwrap_or(UNNAMED_THREAD_NAME) - .to_string(); + static THREAD_NAME: String = std::thread::current() + .name() + .unwrap_or(UNNAMED_THREAD_NAME) + .to_string(); } THREAD_NAME.with(|thread_name| { @@ -100,7 +95,7 @@ impl Daemon { fn new() -> Result { // Check root permission ensure!( - os::user::id() == 0, + os::user::uid() == 0, "This command has to be run with superuser privileges (under the root user on most systems)." ); diff --git a/syscared/src/patch/driver/kpatch/sys.rs b/syscared/src/patch/driver/kpatch/sys.rs index fd5160cb..adabfb48 100644 --- a/syscared/src/patch/driver/kpatch/sys.rs +++ b/syscared/src/patch/driver/kpatch/sys.rs @@ -19,7 +19,7 @@ use log::debug; use nix::kmod; use syscare_abi::PatchStatus; -use syscare_common::{ffi::OsStrExt, fs, os}; +use syscare_common::{ffi::OsStrExt, fs, os::selinux}; use crate::patch::entity::KernelPatch; @@ -39,7 +39,7 @@ pub fn list_kernel_modules() -> Result> { pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { const KPATCH_PATCH_SEC_TYPE: &str = "modules_object_t"; - if os::selinux::get_status()? != os::selinux::Status::Enforcing { + if selinux::get_status() != selinux::Status::Enforcing { return Ok(()); } @@ -47,10 +47,10 @@ pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { "Relabeling patch module '{}'...", patch.module_name.to_string_lossy() ); - let mut sec_context = os::selinux::get_security_context(&patch.patch_file)?; - if sec_context.kind != KPATCH_PATCH_SEC_TYPE { - sec_context.kind = OsString::from(KPATCH_PATCH_SEC_TYPE); - os::selinux::set_security_context(&patch.patch_file, sec_context)?; + let mut sec_context = selinux::get_security_context(&patch.patch_file)?; + if sec_context.get_type() != KPATCH_PATCH_SEC_TYPE { + sec_context.set_type(OsString::from(KPATCH_PATCH_SEC_TYPE))?; + selinux::set_security_context(&patch.patch_file, &sec_context)?; } Ok(()) -- Gitee From db2a2e04a758109d51a7a0f38e60b2278ffabab9 Mon Sep 17 00:00:00 2001 From: renoseven Date: Tue, 3 Jun 2025 15:55:03 +0800 Subject: [PATCH 09/13] syscared: rewrite patch parsing & management Signed-off-by: renoseven --- syscared/src/config.rs | 22 +- syscared/src/patch/driver/kpatch/mod.rs | 302 +++++++---------- syscared/src/patch/driver/kpatch/sys.rs | 119 +++---- syscared/src/patch/driver/kpatch/target.rs | 144 +++----- syscared/src/patch/driver/mod.rs | 84 +++-- syscared/src/patch/driver/upatch/entity.rs | 60 ---- syscared/src/patch/driver/upatch/mod.rs | 348 +++++++------------- syscared/src/patch/driver/upatch/monitor.rs | 30 +- syscared/src/patch/driver/upatch/sys.rs | 32 +- syscared/src/patch/driver/upatch/target.rs | 158 ++++----- syscared/src/patch/entity/kpatch.rs | 224 ++++++++++++- syscared/src/patch/entity/patch.rs | 5 +- syscared/src/patch/entity/symbol.rs | 54 --- syscared/src/patch/entity/upatch.rs | 181 +++++++++- syscared/src/patch/manager.rs | 169 +++++++--- syscared/src/patch/mod.rs | 1 - syscared/src/patch/resolver/kpatch.rs | 226 ------------- syscared/src/patch/resolver/mod.rs | 63 ---- syscared/src/patch/resolver/upatch.rs | 182 ---------- 19 files changed, 1053 insertions(+), 1351 deletions(-) delete mode 100644 syscared/src/patch/driver/upatch/entity.rs delete mode 100644 syscared/src/patch/entity/symbol.rs delete mode 100644 syscared/src/patch/resolver/kpatch.rs delete mode 100644 syscared/src/patch/resolver/mod.rs delete mode 100644 syscared/src/patch/resolver/upatch.rs diff --git a/syscared/src/config.rs b/syscared/src/config.rs index 71032731..5e0d7361 100644 --- a/syscared/src/config.rs +++ b/syscared/src/config.rs @@ -83,17 +83,27 @@ pub struct Config { impl Config { pub fn parse>(path: P) -> Result { let config_path = path.as_ref(); - let instance = serde_yaml::from_reader(fs::open_file(config_path)?) - .map_err(|_| anyhow!("Failed to parse config {}", config_path.display()))?; - - Ok(instance) + let config = serde_yaml::from_reader(fs::open_file(config_path)?).map_err(|e| { + anyhow!( + "Failed to parse config '{}', {}", + config_path.display(), + e.to_string().to_lowercase() + ) + })?; + + Ok(config) } pub fn write>(&self, path: P) -> Result<()> { let config_path = path.as_ref(); let config_file = fs::create_file(config_path)?; - serde_yaml::to_writer(config_file, self) - .map_err(|_| anyhow!("Failed to write config {}", config_path.display()))?; + serde_yaml::to_writer(config_file, self).map_err(|e| { + anyhow!( + "Failed to write config '{}', {}", + config_path.display(), + e.to_string().to_lowercase() + ) + })?; Ok(()) } diff --git a/syscared/src/patch/driver/kpatch/mod.rs b/syscared/src/patch/driver/kpatch/mod.rs index ba177ac5..8e396703 100644 --- a/syscared/src/patch/driver/kpatch/mod.rs +++ b/syscared/src/patch/driver/kpatch/mod.rs @@ -13,103 +13,58 @@ */ use std::{ - ffi::{OsStr, OsString}, + collections::{HashMap, HashSet}, + ffi::OsString, fmt::Write, iter::FromIterator, }; -use anyhow::{ensure, Result}; -use indexmap::{indexset, IndexMap, IndexSet}; -use log::{debug, info}; +use anyhow::{anyhow, ensure, Context, Result}; +use log::debug; use syscare_abi::PatchStatus; -use syscare_common::{concat_os, os, util::digest}; - -use crate::{ - config::KernelPatchConfig, - patch::entity::{KernelPatch, KernelPatchFunction}, +use syscare_common::{ + concat_os, + os::{self, kernel, selinux}, + util::digest, }; +use crate::{config::KernelPatchConfig, patch::entity::KernelPatch}; + mod sys; mod target; use target::PatchTarget; pub struct KernelPatchDriver { - target_map: IndexMap, - blocked_targets: IndexSet, + target_map: HashMap, // object name -> object + blocked_targets: HashSet, } impl KernelPatchDriver { pub fn new(config: &KernelPatchConfig) -> Result { Ok(Self { - target_map: IndexMap::new(), - blocked_targets: IndexSet::from_iter(config.blocked.iter().cloned()), + target_map: HashMap::new(), + blocked_targets: HashSet::from_iter(config.blocked.iter().cloned()), }) } } impl KernelPatchDriver { - fn group_patch_targets(patch: &KernelPatch) -> IndexSet<&OsStr> { - let mut patch_targets = IndexSet::new(); - - for function in &patch.functions { - patch_targets.insert(function.object.as_os_str()); - } - patch_targets - } - - pub fn group_patch_functions( - patch: &KernelPatch, - ) -> IndexMap<&OsStr, Vec<&KernelPatchFunction>> { - let mut patch_function_map: IndexMap<&OsStr, Vec<&KernelPatchFunction>> = IndexMap::new(); - - for function in &patch.functions { - patch_function_map - .entry(function.object.as_os_str()) - .or_default() - .push(function); - } - patch_function_map - } -} - -impl KernelPatchDriver { - fn add_patch_target(&mut self, patch: &KernelPatch) { - for target_name in Self::group_patch_targets(patch) { - if !self.target_map.contains_key(target_name) { - self.target_map.insert( - target_name.to_os_string(), - PatchTarget::new(target_name.to_os_string()), - ); - } + fn register_patch(&mut self, patch: &KernelPatch) { + for object_name in patch.functions.keys() { + self.target_map + .entry(object_name.clone()) + .or_insert_with(|| PatchTarget::new(object_name.clone())) + .add_patch(patch); } } - fn remove_patch_target(&mut self, patch: &KernelPatch) { - for target_name in Self::group_patch_targets(patch) { - if let Some(target) = self.target_map.get_mut(target_name) { - if !target.has_function() { - self.target_map.remove(target_name); - } - } - } - } - - fn add_patch_functions(&mut self, patch: &KernelPatch) { - for (target_name, functions) in Self::group_patch_functions(patch) { - if let Some(target) = self.target_map.get_mut(target_name) { - target.add_functions(patch.uuid, functions); - } - } - } - - fn remove_patch_functions(&mut self, patch: &KernelPatch) { - for (target_name, functions) in Self::group_patch_functions(patch) { - if let Some(target) = self.target_map.get_mut(target_name) { - target.remove_functions(&patch.uuid, functions); - } - } + fn unregister_patch(&mut self, patch: &KernelPatch) { + self.target_map.retain(|_, object| { + object.remove_patch(patch); + object.is_patched() + }); } } @@ -130,13 +85,14 @@ impl KernelPatchDriver { const KERNEL_NAME_PREFIX: &str = "kernel-"; let patch_target = patch.pkg_name.as_str(); + if !patch_target.starts_with(KERNEL_NAME_PREFIX) { + return Ok(()); + } + let current_kernel = concat_os!(KERNEL_NAME_PREFIX, os::kernel::version()); debug!("Patch target: '{}'", patch_target); debug!("Current kernel: '{}'", current_kernel.to_string_lossy()); - if !patch_target.starts_with(KERNEL_NAME_PREFIX) { - return Ok(()); - } ensure!( current_kernel == patch_target, "Kpatch: Patch is incompatible", @@ -147,156 +103,134 @@ impl KernelPatchDriver { fn check_dependency(patch: &KernelPatch) -> Result<()> { const VMLINUX_MODULE_NAME: &str = "vmlinux"; - let mut non_exist_kmod = IndexSet::new(); - - let kmod_list = sys::list_kernel_modules()?; - for kmod_name in Self::group_patch_targets(patch) { - if kmod_name == VMLINUX_MODULE_NAME { - continue; - } - if kmod_list.iter().any(|name| name == kmod_name) { - continue; - } - non_exist_kmod.insert(kmod_name); - } - - ensure!(non_exist_kmod.is_empty(), { - let mut err_msg = String::new(); - - writeln!(&mut err_msg, "Kpatch: Patch target does not exist")?; - for kmod_name in non_exist_kmod { - writeln!(&mut err_msg, "* Module '{}'", kmod_name.to_string_lossy())?; + let depend_modules = patch.functions.keys().cloned().collect::>(); + let inserted_modules = + kernel::list_modules().context("Kpatch: Failed to list kernel modules")?; + let needed_modules = depend_modules + .difference(&inserted_modules) + .filter(|&module_name| module_name != VMLINUX_MODULE_NAME) + .collect::>(); + + ensure!(needed_modules.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Kpatch: Patch target does not exist")?; + for name in needed_modules { + writeln!(msg, "* Module '{}'", name.to_string_lossy())?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } - pub fn check_conflict_functions(&self, patch: &KernelPatch) -> Result<()> { - let mut conflict_patches = indexset! {}; - - let target_functions = Self::group_patch_functions(patch); - for (target_name, functions) in target_functions { - if let Some(target) = self.target_map.get(target_name) { - conflict_patches.extend( - target - .get_conflicts(functions) - .into_iter() - .map(|record| record.uuid), - ); - } - } - - ensure!(conflict_patches.is_empty(), { - let mut err_msg = String::new(); + pub fn check_conflicted_patches(&self, patch: &KernelPatch) -> Result<()> { + let conflicted: HashSet<_> = self + .target_map + .values() + .flat_map(|object| object.get_conflicted_patches(patch)) + .collect(); - writeln!(&mut err_msg, "Kpatch: Patch is conflicted with")?; - for uuid in conflict_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(conflicted.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Kpatch: Patch is conflicted with")?; + for uuid in conflicted { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } - pub fn check_override_functions(&self, patch: &KernelPatch) -> Result<()> { - let mut override_patches = indexset! {}; - - let target_functions = Self::group_patch_functions(patch); - for (target_name, functions) in target_functions { - if let Some(target) = self.target_map.get(target_name) { - override_patches.extend( - target - .get_overrides(&patch.uuid, functions) - .into_iter() - .map(|record| record.uuid), - ); - } - } - - ensure!(override_patches.is_empty(), { - let mut err_msg = String::new(); + pub fn check_overridden_patches(&self, patch: &KernelPatch) -> Result<()> { + let overridden: HashSet<_> = self + .target_map + .values() + .flat_map(|object| object.get_overridden_patches(patch)) + .collect(); - writeln!(&mut err_msg, "Kpatch: Patch is overrided by")?; - for uuid in override_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(overridden.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Kpatch: Patch is overridden by")?; + for uuid in overridden { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } } impl KernelPatchDriver { - pub fn status(&self, patch: &KernelPatch) -> Result { - sys::read_patch_status(patch) - } - - pub fn check(&self, patch: &KernelPatch) -> Result<()> { + pub fn check_patch(&self, patch: &KernelPatch) -> Result<()> { Self::check_consistency(patch)?; Self::check_compatiblity(patch)?; Self::check_dependency(patch)?; - Ok(()) } - pub fn apply(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Applying patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); + pub fn get_patch_status(&self, patch: &KernelPatch) -> Result { + sys::get_patch_status(&patch.status_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to get patch status, {}", + e.to_string().to_lowercase() + ) + }) + } + pub fn load_patch(&mut self, patch: &KernelPatch) -> Result<()> { ensure!( !self.blocked_targets.contains(&patch.target_name), - "Patch target '{}' is blocked", + "Kpatch: Patch target '{}' is blocked", patch.target_name.to_string_lossy(), ); - sys::selinux_relable_patch(patch)?; - sys::apply_patch(patch)?; - self.add_patch_target(patch); - Ok(()) + if selinux::get_status() == selinux::Status::Enforcing { + kernel::relable_module_file(&patch.patch_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to relable patch file, {}", + e.to_string().to_lowercase() + ) + })?; + } + sys::load_patch(&patch.patch_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to load patch, {}", + e.to_string().to_lowercase() + ) + }) } - pub fn remove(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Removing patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - sys::remove_patch(patch)?; - self.remove_patch_target(patch); - - Ok(()) + pub fn remove_patch(&mut self, patch: &KernelPatch) -> Result<()> { + sys::remove_patch(&patch.module.name).map_err(|e| { + anyhow!( + "Kpatch: Failed to remove patch, {}", + e.to_string().to_lowercase() + ) + }) } - pub fn active(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Activating patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - sys::active_patch(patch)?; - self.add_patch_functions(patch); + pub fn active_patch(&mut self, patch: &KernelPatch) -> Result<()> { + sys::active_patch(&patch.status_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to active patch, {}", + e.to_string().to_lowercase() + ) + })?; + self.register_patch(patch); Ok(()) } - pub fn deactive(&mut self, patch: &KernelPatch) -> Result<()> { - info!( - "Deactivating patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - sys::deactive_patch(patch)?; - self.remove_patch_functions(patch); + pub fn deactive_patch(&mut self, patch: &KernelPatch) -> Result<()> { + sys::deactive_patch(&patch.status_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to deactive patch, {}", + e.to_string().to_lowercase() + ) + })?; + self.unregister_patch(patch); Ok(()) } diff --git a/syscared/src/patch/driver/kpatch/sys.rs b/syscared/src/patch/driver/kpatch/sys.rs index adabfb48..838fe3b8 100644 --- a/syscared/src/patch/driver/kpatch/sys.rs +++ b/syscared/src/patch/driver/kpatch/sys.rs @@ -12,106 +12,69 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::{CString, OsString}; +use std::{ffi::OsStr, fs, path::Path}; -use anyhow::{anyhow, bail, Context, Result}; use log::debug; -use nix::kmod; +use nix::errno::Errno; use syscare_abi::PatchStatus; -use syscare_common::{ffi::OsStrExt, fs, os::selinux}; +use syscare_common::os::kernel; -use crate::patch::entity::KernelPatch; +const KPATCH_STATUS_DISABLE: &str = "0"; +const KPATCH_STATUS_ENABLE: &str = "1"; -const SYS_MODULE_DIR: &str = "/sys/module"; -const KPATCH_STATUS_DISABLED: &str = "0"; -const KPATCH_STATUS_ENABLED: &str = "1"; - -pub fn list_kernel_modules() -> Result> { - let module_names = fs::list_dirs(SYS_MODULE_DIR, fs::TraverseOptions { recursive: false })? - .into_iter() - .filter_map(|dir| dir.file_name().map(|name| name.to_os_string())) - .collect(); - - Ok(module_names) -} - -pub fn selinux_relable_patch(patch: &KernelPatch) -> Result<()> { - const KPATCH_PATCH_SEC_TYPE: &str = "modules_object_t"; - - if selinux::get_status() != selinux::Status::Enforcing { - return Ok(()); - } +pub fn load_patch>(patch_file: P) -> std::io::Result<()> { + let patch_file = patch_file.as_ref(); debug!( - "Relabeling patch module '{}'...", - patch.module_name.to_string_lossy() + "Kpatch: Inserting patch module '{}'...", + patch_file.display() ); - let mut sec_context = selinux::get_security_context(&patch.patch_file)?; - if sec_context.get_type() != KPATCH_PATCH_SEC_TYPE { - sec_context.set_type(OsString::from(KPATCH_PATCH_SEC_TYPE))?; - selinux::set_security_context(&patch.patch_file, &sec_context)?; - } - - Ok(()) + kernel::insert_module(patch_file) } -pub fn read_patch_status(patch: &KernelPatch) -> Result { - let sys_file = patch.sys_file.as_path(); - debug!("Reading {}", sys_file.display()); - - let status = match fs::read_to_string(sys_file) { - Ok(str) => match str.trim() { - KPATCH_STATUS_DISABLED => Ok(PatchStatus::Deactived), - KPATCH_STATUS_ENABLED => Ok(PatchStatus::Actived), - _ => bail!("Kpatch: Invalid patch status"), - }, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(PatchStatus::NotApplied), - Err(e) => Err(e), - } - .context("Kpatch: Failed to read patch status")?; +pub fn remove_patch>(module_name: S) -> std::io::Result<()> { + let module_name = module_name.as_ref(); - Ok(status) + debug!( + "Kpatch: Removing patch module '{}'...", + module_name.to_string_lossy() + ); + kernel::remove_module(module_name) } -fn write_patch_status(patch: &KernelPatch, value: &str) -> Result<()> { - let sys_file = patch.sys_file.as_path(); +pub fn active_patch>(status_file: P) -> std::io::Result<()> { + let status_file = status_file.as_ref(); - debug!("Writing '{}' to {}", value, sys_file.display()); - fs::write(sys_file, value).context("Kpatch: Failed to write patch status") -} - -pub fn apply_patch(patch: &KernelPatch) -> Result<()> { debug!( - "Inserting patch module '{}'...", - patch.module_name.to_string_lossy() + "Kpatch: Writing '{}' to '{}'...", + stringify!(KPATCH_STATUS_ENABLE), + status_file.display() ); - let patch_module = fs::open_file(&patch.patch_file)?; - kmod::finit_module( - &patch_module, - CString::new("")?.as_c_str(), - kmod::ModuleInitFlags::empty(), - ) - .map_err(|e| anyhow!("Kpatch: {}", std::io::Error::from(e))) + fs::write(status_file, KPATCH_STATUS_ENABLE) } -pub fn remove_patch(patch: &KernelPatch) -> Result<()> { +pub fn deactive_patch>(status_file: P) -> std::io::Result<()> { + let status_file = status_file.as_ref(); + debug!( - "Removing patch module '{}'...", - patch.module_name.to_string_lossy() + "Kpatch: Writing '{}' to '{}'...", + stringify!(KPATCH_STATUS_DISABLE), + status_file.display() ); - - kmod::delete_module( - patch.module_name.to_cstring()?.as_c_str(), - kmod::DeleteModuleFlags::O_NONBLOCK, - ) - .map_err(|e| anyhow!("Kpatch: {}", std::io::Error::from(e))) + fs::write(status_file, KPATCH_STATUS_DISABLE) } -pub fn active_patch(patch: &KernelPatch) -> Result<()> { - self::write_patch_status(patch, KPATCH_STATUS_ENABLED) -} +pub fn get_patch_status>(status_file: P) -> std::io::Result { + let status_file = status_file.as_ref(); + if !status_file.exists() { + return Ok(PatchStatus::NotApplied); + } -pub fn deactive_patch(patch: &KernelPatch) -> Result<()> { - self::write_patch_status(patch, KPATCH_STATUS_DISABLED) + debug!("Kpatch: Reading '{}'...", status_file.display()); + match fs::read_to_string(status_file)?.trim() { + KPATCH_STATUS_DISABLE => Ok(PatchStatus::Deactived), + KPATCH_STATUS_ENABLE => Ok(PatchStatus::Actived), + _ => Err(std::io::Error::from(Errno::EINVAL)), + } } diff --git a/syscared/src/patch/driver/kpatch/target.rs b/syscared/src/patch/driver/kpatch/target.rs index 49f1185e..3f4a4650 100644 --- a/syscared/src/patch/driver/kpatch/target.rs +++ b/syscared/src/patch/driver/kpatch/target.rs @@ -12,126 +12,88 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsString; +use std::{ + collections::{hash_map::Entry, HashMap}, + ffi::OsString, +}; -use indexmap::IndexMap; +use indexmap::IndexSet; use uuid::Uuid; -use crate::patch::entity::KernelPatchFunction; - -#[derive(Debug)] -pub struct PatchFunction { - pub uuid: Uuid, - pub name: OsString, - pub size: u64, -} - -impl PatchFunction { - fn new(uuid: Uuid, function: &KernelPatchFunction) -> Self { - Self { - uuid, - name: function.name.to_os_string(), - size: function.new_size, - } - } - - fn is_same_function(&self, uuid: &Uuid, function: &KernelPatchFunction) -> bool { - (self.uuid == *uuid) && (self.name == function.name) && (self.size == function.new_size) - } -} +use crate::patch::entity::KernelPatch; #[derive(Debug)] pub struct PatchTarget { - name: OsString, - function_map: IndexMap>, // function addr -> function collision list + object_name: OsString, + collision_map: HashMap>, // function name -> patch collision list } impl PatchTarget { - pub fn new(name: OsString) -> Self { + pub fn new(object_name: OsString) -> Self { Self { - name, - function_map: IndexMap::new(), + object_name, + collision_map: HashMap::new(), } } } impl PatchTarget { - pub fn has_function(&self) -> bool { - self.function_map.is_empty() - } - - pub fn add_functions<'a, I>(&mut self, uuid: Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - if self.name != function.object { - continue; + pub fn add_patch(&mut self, patch: &KernelPatch) { + if let Some(functions) = patch.functions.get(&self.object_name) { + for function in functions { + self.collision_map + .entry(function.name.clone()) + .or_default() + .insert(patch.uuid); } - self.function_map - .entry(function.name.clone()) - .or_default() - .push(PatchFunction::new(uuid, function)); } } - pub fn remove_functions<'a, I>(&mut self, uuid: &Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - if self.name != function.object { - continue; - } - if let Some(collision_list) = self.function_map.get_mut(&function.name) { - if let Some(index) = collision_list - .iter() - .position(|patch_function| patch_function.is_same_function(uuid, function)) + pub fn remove_patch(&mut self, patch: &KernelPatch) { + if let Some(functions) = patch.functions.get(&self.object_name) { + for function in functions { + if let Entry::Occupied(mut entry) = self.collision_map.entry(function.name.clone()) { - collision_list.remove(index); - if collision_list.is_empty() { - self.function_map.remove(&function.name); + let patch_set = entry.get_mut(); + patch_set.shift_remove(&patch.uuid); + + if patch_set.is_empty() { + entry.remove(); } } } } } -} -impl PatchTarget { - pub fn get_conflicts<'a, I>( + pub fn is_patched(&self) -> bool { + !self.collision_map.is_empty() + } + + pub fn get_conflicted_patches<'a>( &'a self, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - if self.name != function.object { - return None; - } - self.function_map - .get(&function.name) - .and_then(|list| list.last()) - }) + patch: &'a KernelPatch, + ) -> impl Iterator + 'a { + let functions = patch.functions.get(&self.object_name).into_iter().flatten(); + functions + .filter_map(move |function| self.collision_map.get(&function.name)) + .flatten() + .copied() + .filter(move |&uuid| uuid != patch.uuid) } - pub fn get_overrides<'a, I>( + pub fn get_overridden_patches<'a>( &'a self, - uuid: &'a Uuid, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - if self.name != function.object { - return None; - } - self.function_map - .get(&function.name) - .and_then(|list| list.last()) - .filter(|patch_function| !patch_function.is_same_function(uuid, function)) - }) + patch: &'a KernelPatch, + ) -> impl Iterator + 'a { + let functions = patch.functions.get(&self.object_name).into_iter().flatten(); + functions + .filter_map(move |function| self.collision_map.get(&function.name)) + .flat_map(move |collision_list| { + collision_list + .iter() + .copied() + .skip_while(move |&uuid| uuid != patch.uuid) + .skip(1) + }) } } diff --git a/syscared/src/patch/driver/mod.rs b/syscared/src/patch/driver/mod.rs index 707bf2f9..3752ca85 100644 --- a/syscared/src/patch/driver/mod.rs +++ b/syscared/src/patch/driver/mod.rs @@ -14,7 +14,7 @@ use anyhow::{Context, Result}; -use log::info; +use log::{debug, info}; use syscare_abi::PatchStatus; mod kpatch; @@ -39,17 +39,17 @@ pub struct PatchDriver { } impl PatchDriver { - fn check_conflict_functions(&self, patch: &Patch) -> Result<()> { + fn check_conflicted_patches(&self, patch: &Patch) -> Result<()> { match patch { - Patch::KernelPatch(kpatch) => self.kpatch.check_conflict_functions(kpatch), - Patch::UserPatch(upatch) => self.upatch.check_conflict_functions(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.check_conflicted_patches(kpatch), + Patch::UserPatch(upatch) => self.upatch.check_conflicted_patches(upatch), } } - fn check_override_functions(&self, patch: &Patch) -> Result<()> { + fn check_overridden_patches(&self, patch: &Patch) -> Result<()> { match patch { - Patch::KernelPatch(kpatch) => self.kpatch.check_override_functions(kpatch), - Patch::UserPatch(upatch) => self.upatch.check_override_functions(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.check_overridden_patches(kpatch), + Patch::UserPatch(upatch) => self.upatch.check_overridden_patches(upatch), } } } @@ -70,54 +70,62 @@ impl PatchDriver { }) } - /// Fetch and return the patch status. - pub fn patch_status(&self, patch: &Patch) -> Result { - match patch { - Patch::KernelPatch(kpatch) => self.kpatch.status(kpatch), - Patch::UserPatch(upatch) => self.upatch.status(upatch), + /// Perform patch confliction check.
+ /// Used for patch check. + pub fn check_confliction(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + if flag == PatchOpFlag::Force { + return Ok(()); } - .with_context(|| format!("Failed to get patch '{}' status", patch)) + self.check_conflicted_patches(patch) + .with_context(|| format!("Patch '{}' is conflicted", patch)) } /// Perform patch file intergrity & consistency check.
/// Should be used before patch application. pub fn check_patch(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Checking patch '{}'...", patch); + if flag == PatchOpFlag::Force { return Ok(()); } match patch { - Patch::KernelPatch(kpatch) => self.kpatch.check(kpatch), - Patch::UserPatch(upatch) => self.upatch.check(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.check_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.check_patch(upatch), } .with_context(|| format!("Patch '{}' is not patchable", patch)) } - /// Perform patch confliction check.
- /// Used for patch check. - pub fn check_confliction(&self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { - if flag == PatchOpFlag::Force { - return Ok(()); + /// Fetch and return the patch status. + pub fn get_patch_status(&self, patch: &Patch) -> Result { + debug!("Fetching patch '{}' status...", patch); + + match patch { + Patch::KernelPatch(kpatch) => self.kpatch.get_patch_status(kpatch), + Patch::UserPatch(upatch) => self.upatch.get_patch_status(upatch), } - self.check_conflict_functions(patch) - .with_context(|| format!("Patch '{}' is conflicted", patch)) + .with_context(|| format!("Failed to get patch '{}' status", patch)) } - /// Apply a patch.
+ /// Load a patch.
/// After this action, the patch status would be changed to 'DEACTIVED'. - pub fn apply_patch(&mut self, patch: &Patch) -> Result<()> { + pub fn load_patch(&mut self, patch: &Patch) -> Result<()> { + info!("Loading patch '{}'...", patch); + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.apply(kpatch), - Patch::UserPatch(upatch) => self.upatch.apply(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.load_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.load_patch(upatch), } - .with_context(|| format!("Failed to apply patch '{}'", patch)) + .with_context(|| format!("Failed to load patch '{}'", patch)) } /// Remove a patch.
/// After this action, the patch status would be changed to 'NOT-APPLIED'. pub fn remove_patch(&mut self, patch: &Patch) -> Result<()> { + info!("Removing patch '{}'...", patch); + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.remove(kpatch), - Patch::UserPatch(upatch) => self.upatch.remove(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.remove_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.remove_patch(upatch), } .with_context(|| format!("Failed to remove patch '{}'", patch)) } @@ -125,12 +133,15 @@ impl PatchDriver { /// Active a patch.
/// After this action, the patch status would be changed to 'ACTIVED'. pub fn active_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Activating patch '{}'...", patch); + if flag != PatchOpFlag::Force { - self.check_conflict_functions(patch)?; + self.check_conflicted_patches(patch)?; } + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.active(kpatch), - Patch::UserPatch(upatch) => self.upatch.active(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.active_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.active_patch(upatch), } .with_context(|| format!("Failed to active patch '{}'", patch)) } @@ -138,12 +149,15 @@ impl PatchDriver { /// Deactive a patch.
/// After this action, the patch status would be changed to 'DEACTIVED'. pub fn deactive_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Deactivating patch '{}'...", patch); + if flag != PatchOpFlag::Force { - self.check_override_functions(patch)?; + self.check_overridden_patches(patch)?; } + match patch { - Patch::KernelPatch(kpatch) => self.kpatch.deactive(kpatch), - Patch::UserPatch(upatch) => self.upatch.deactive(upatch), + Patch::KernelPatch(kpatch) => self.kpatch.deactive_patch(kpatch), + Patch::UserPatch(upatch) => self.upatch.deactive_patch(upatch), } .with_context(|| format!("Failed to deactive patch '{}'", patch)) } diff --git a/syscared/src/patch/driver/upatch/entity.rs b/syscared/src/patch/driver/upatch/entity.rs deleted file mode 100644 index 5d0a9c01..00000000 --- a/syscared/src/patch/driver/upatch/entity.rs +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::path::PathBuf; - -use indexmap::{indexset, IndexSet}; - -#[derive(Debug)] -pub struct PatchEntity { - pub patch_file: PathBuf, - process_list: IndexSet, -} - -impl PatchEntity { - pub fn new(patch_file: PathBuf) -> Self { - Self { - patch_file, - process_list: indexset! {}, - } - } -} - -impl PatchEntity { - pub fn add_process(&mut self, pid: i32) { - self.process_list.insert(pid); - } - - pub fn remove_process(&mut self, pid: i32) { - self.process_list.remove(&pid); - } - - pub fn clean_dead_process(&mut self, process_list: &IndexSet) { - self.process_list.retain(|pid| process_list.contains(pid)); - } - - pub fn need_actived(&self, process_list: &IndexSet) -> IndexSet { - process_list - .difference(&self.process_list) - .copied() - .collect() - } - - pub fn need_deactived(&self, process_list: &IndexSet) -> IndexSet { - process_list - .intersection(&self.process_list) - .copied() - .collect() - } -} diff --git a/syscared/src/patch/driver/upatch/mod.rs b/syscared/src/patch/driver/upatch/mod.rs index c31ef78e..cc00f531 100644 --- a/syscared/src/patch/driver/upatch/mod.rs +++ b/syscared/src/patch/driver/upatch/mod.rs @@ -13,6 +13,7 @@ */ use std::{ + collections::{HashMap, HashSet}, ffi::OsStr, fmt::Write, iter::FromIterator, @@ -21,21 +22,16 @@ use std::{ sync::Arc, }; -use anyhow::{bail, ensure, Context, Result}; -use indexmap::{indexset, IndexMap, IndexSet}; -use log::{debug, info, warn}; +use anyhow::{bail, ensure, Result}; +use log::{debug, warn}; use parking_lot::RwLock; use uuid::Uuid; use syscare_abi::PatchStatus; use syscare_common::{fs, util::digest}; -use crate::{ - config::UserPatchConfig, - patch::{driver::upatch::entity::PatchEntity, entity::UserPatch}, -}; +use crate::{config::UserPatchConfig, patch::entity::UserPatch}; -mod entity; mod monitor; mod sys; mod target; @@ -44,19 +40,19 @@ use monitor::UserPatchMonitor; use target::PatchTarget; pub struct UserPatchDriver { - status_map: IndexMap, - target_map: Arc>>, - skipped_files: Arc>, + status_map: HashMap, + target_map: Arc>>, + skipped_files: Arc>, monitor: UserPatchMonitor, } impl UserPatchDriver { pub fn new(config: &UserPatchConfig) -> Result { - let target_map = Arc::new(RwLock::new(IndexMap::new())); - let skipped_files = Arc::new(IndexSet::from_iter(config.skipped.iter().cloned())); + let target_map = Arc::new(RwLock::new(HashMap::new())); + let skipped_files = Arc::new(HashSet::from_iter(config.skipped.iter().cloned())); Ok(Self { - status_map: IndexMap::new(), + status_map: HashMap::new(), target_map: target_map.clone(), skipped_files: skipped_files.clone(), monitor: UserPatchMonitor::new(move |target_elfs| { @@ -70,7 +66,7 @@ impl UserPatchDriver { impl UserPatchDriver { #[inline] - fn get_patch_status(&self, uuid: &Uuid) -> PatchStatus { + fn read_patch_status(&self, uuid: &Uuid) -> PatchStatus { self.status_map .get(uuid) .copied() @@ -78,7 +74,7 @@ impl UserPatchDriver { } #[inline] - fn set_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { + fn write_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { *self.status_map.entry(*uuid).or_default() = value; } @@ -87,28 +83,6 @@ impl UserPatchDriver { } } -impl UserPatchDriver { - fn add_patch_target(&mut self, patch: &UserPatch) { - let target_elf = patch.target_elf.as_path(); - let mut target_map = self.target_map.write(); - - if !target_map.contains_key(target_elf) { - target_map.insert(target_elf.to_path_buf(), PatchTarget::default()); - } - } - - fn remove_patch_target(&mut self, patch: &UserPatch) { - let target_elf = patch.target_elf.as_path(); - let mut target_map = self.target_map.write(); - - if let Some(target) = target_map.get_mut(target_elf) { - if !target.is_patched() { - target_map.remove(target_elf); - } - } - } -} - impl UserPatchDriver { fn check_consistency(patch: &UserPatch) -> Result<()> { let real_checksum = digest::file(&patch.patch_file)?; @@ -122,56 +96,39 @@ impl UserPatchDriver { Ok(()) } - fn check_compatiblity(_patch: &UserPatch) -> Result<()> { - Ok(()) - } - - pub fn check_conflict_functions(&self, patch: &UserPatch) -> Result<()> { - let conflict_patches = match self.target_map.read().get(&patch.target_elf) { - Some(target) => target - .get_conflicts(&patch.functions) - .into_iter() - .map(|record| record.uuid) - .collect(), - None => indexset! {}, + pub fn check_conflicted_patches(&self, patch: &UserPatch) -> Result<()> { + let conflicted = match self.target_map.read().get(&patch.target_elf) { + Some(target) => target.get_conflicted_patches(patch).collect(), + None => HashSet::new(), }; - ensure!(conflict_patches.is_empty(), { - let mut err_msg = String::new(); - - writeln!(&mut err_msg, "Upatch: Patch is conflicted with")?; - for uuid in conflict_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(conflicted.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Upatch: Patch is conflicted with")?; + for uuid in conflicted.into_iter() { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); Ok(()) } - pub fn check_override_functions(&self, patch: &UserPatch) -> Result<()> { - let override_patches = match self.target_map.read().get(&patch.target_elf) { - Some(target) => target - .get_overrides(&patch.uuid, &patch.functions) - .into_iter() - .map(|record| record.uuid) - .collect(), - None => indexset! {}, + pub fn check_overridden_patches(&self, patch: &UserPatch) -> Result<()> { + let overridden = match self.target_map.read().get(&patch.target_elf) { + Some(target) => target.get_overridden_patches(patch).collect(), + None => HashSet::new(), }; - ensure!(override_patches.is_empty(), { - let mut err_msg = String::new(); - - writeln!(&mut err_msg, "Upatch: Patch is overrided by")?; - for uuid in override_patches.into_iter() { - writeln!(&mut err_msg, "* Patch '{}'", uuid)?; + ensure!(overridden.is_empty(), { + let mut msg = String::new(); + writeln!(msg, "Upatch: Patch is overridden by")?; + for uuid in overridden.into_iter() { + writeln!(msg, "* Patch '{}'", uuid)?; } - err_msg.pop(); - - err_msg + msg.pop(); + msg }); - Ok(()) } } @@ -187,10 +144,10 @@ impl UserPatchDriver { } fn find_target_process>( - skipped_files: &IndexSet, + skipped_files: &HashSet, target_elf: P, - ) -> Result> { - let mut target_pids = IndexSet::new(); + ) -> Result> { + let mut target_pids = HashSet::new(); let target_path = target_elf.as_ref(); let target_inode = target_path.metadata()?.st_ino(); @@ -233,8 +190,8 @@ impl UserPatchDriver { } fn patch_new_process( - target_map: &RwLock>, - skipped_files: &IndexSet, + target_map: &RwLock>, + skipped_files: &HashSet, target_elf: &Path, ) { let process_list = match Self::find_target_process(skipped_files, target_elf) { @@ -242,33 +199,32 @@ impl UserPatchDriver { Err(_) => return, }; - let mut patch_target_map = target_map.write(); - let patch_target = match patch_target_map.get_mut(target_elf) { + let mut target_map = target_map.write(); + let patch_target = match target_map.get_mut(target_elf) { Some(target) => target, None => return, }; + patch_target.clean_dead_process(&process_list); - for (patch_uuid, patch_entity) in patch_target.all_patches() { - patch_entity.clean_dead_process(&process_list); + let all_patches = patch_target.all_patches().collect::>(); + let need_actived = patch_target.need_actived(&process_list); - // Active patch - let need_actived = patch_entity.need_actived(&process_list); + for (uuid, patch_file) in all_patches { if !need_actived.is_empty() { debug!( - "Activating patch '{}' ({}) for process {:?}", - patch_uuid, + "Upatch: Activating patch '{}' ({}) for process {:?}", + uuid, target_elf.display(), need_actived, ); } - - for pid in need_actived { - match sys::active_patch(patch_uuid, pid, target_elf, &patch_entity.patch_file) { - Ok(_) => patch_entity.add_process(pid), + for &pid in &need_actived { + match sys::active_patch(&uuid, pid, target_elf, &patch_file) { + Ok(_) => patch_target.add_process(pid), Err(e) => { warn!( "Upatch: Failed to active patch '{}' for process {}, {}", - patch_uuid, + uuid, pid, e.to_string().to_lowercase(), ); @@ -280,198 +236,140 @@ impl UserPatchDriver { } impl UserPatchDriver { - pub fn status(&self, patch: &UserPatch) -> Result { - Ok(self.get_patch_status(&patch.uuid)) - } - - pub fn check(&self, patch: &UserPatch) -> Result<()> { + pub fn check_patch(&self, patch: &UserPatch) -> Result<()> { Self::check_consistency(patch)?; - Self::check_compatiblity(patch)?; - Ok(()) } - pub fn apply(&mut self, patch: &UserPatch) -> Result<()> { - info!( - "Applying patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - - self.add_patch_target(patch); - self.set_patch_status(&patch.uuid, PatchStatus::Deactived); + pub fn get_patch_status(&self, patch: &UserPatch) -> Result { + Ok(self.read_patch_status(&patch.uuid)) + } + pub fn load_patch(&mut self, patch: &UserPatch) -> Result<()> { + self.write_patch_status(&patch.uuid, PatchStatus::Deactived); Ok(()) } - pub fn remove(&mut self, patch: &UserPatch) -> Result<()> { - info!( - "Removing patch '{}' ({})", - patch.uuid, - patch.patch_file.display() - ); - - self.remove_patch_target(patch); + pub fn remove_patch(&mut self, patch: &UserPatch) -> Result<()> { self.remove_patch_status(&patch.uuid); - Ok(()) } - pub fn active(&mut self, patch: &UserPatch) -> Result<()> { - let patch_uuid = &patch.uuid; - let patch_file = patch.patch_file.as_path(); - let patch_functions = patch.functions.as_slice(); - let target_elf = patch.target_elf.as_path(); - - let process_list = Self::find_target_process(&self.skipped_files, target_elf)?; + pub fn active_patch(&mut self, patch: &UserPatch) -> Result<()> { + let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; let mut target_map = self.target_map.write(); - let patch_target = target_map - .get_mut(target_elf) - .context("Upatch: Cannot find patch target")?; - let mut patch_entity = match patch_target.get_patch(patch_uuid) { - Some(_) => bail!("Upatch: Patch is already exist"), - None => PatchEntity::new(patch_file.to_path_buf()), - }; + let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); + patch_target.clean_dead_process(&process_list); + + // If target is not patched before, start watching it + let start_watch = !patch_target.is_patched(); // Active patch - info!( - "Activating patch '{}' ({}) for {}", - patch_uuid, - patch_file.display(), - target_elf.display(), - ); + let need_actived = patch_target.need_actived(&process_list); + let mut results = Vec::new(); - for pid in patch_entity.need_actived(&process_list) { - let result = sys::active_patch(patch_uuid, pid, target_elf, patch_file); - if result.is_ok() { - patch_entity.add_process(pid); - } + for pid in need_actived { + let result = sys::active_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); results.push((pid, result)); } - // Check results, return error if all process fails + // Return error if all process fails if !results.is_empty() && results.iter().all(|(_, result)| result.is_err()) { - let mut err_msg = String::new(); - - writeln!(err_msg, "Upatch: Failed to active patch")?; + let mut msg = String::new(); + writeln!(msg, "Upatch: Failed to active patch")?; for (pid, result) in &results { if let Err(e) = result { - writeln!(err_msg, "* Process {}: {}", pid, e)?; + writeln!(msg, "* Process {}: {}", pid, e)?; } } - err_msg.pop(); - bail!(err_msg); + msg.pop(); + bail!(msg); } - // Print failure results - for (pid, result) in &results { - if let Err(e) = result { - warn!( - "Upatch: Failed to active patch '{}' for process {}, {}", - patch_uuid, - pid, - e.to_string().to_lowercase(), - ); + // Process results + for (pid, result) in results { + match result { + Ok(_) => patch_target.add_process(pid), + Err(e) => { + warn!( + "Upatch: Failed to active patch '{}' for process {}, {}", + patch.uuid, + pid, + e.to_string().to_lowercase(), + ); + } } } - - // If target is no patched before, start watching it - let need_start_watch = !patch_target.is_patched(); - - // Apply patch to target - patch_target.add_patch(*patch_uuid, patch_entity); - patch_target.add_functions(*patch_uuid, patch_functions); + patch_target.add_patch(patch); // Drop the lock drop(target_map); - if need_start_watch { - self.monitor.watch_file(target_elf)?; + if start_watch { + self.monitor.watch_file(&patch.target_elf)?; } - self.set_patch_status(patch_uuid, PatchStatus::Actived); + self.write_patch_status(&patch.uuid, PatchStatus::Actived); Ok(()) } - pub fn deactive(&mut self, patch: &UserPatch) -> Result<()> { - let patch_uuid = &patch.uuid; - let patch_file = patch.patch_file.as_path(); - let patch_functions = patch.functions.as_slice(); - let target_elf = patch.target_elf.as_path(); - - let process_list = Self::find_target_process(&self.skipped_files, target_elf)?; + pub fn deactive_patch(&mut self, patch: &UserPatch) -> Result<()> { + let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; let mut target_map = self.target_map.write(); - let patch_target = target_map - .get_mut(target_elf) - .context("Upatch: Cannot find patch target")?; - let patch_entity = patch_target - .get_patch(patch_uuid) - .context("Upatch: Cannot find patch entity")?; - - // Remove dead process - patch_entity.clean_dead_process(&process_list); + let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); + patch_target.clean_dead_process(&process_list); // Deactive patch - info!( - "Deactivating patch '{}' ({}) for {}", - patch_uuid, - patch_file.display(), - target_elf.display(), - ); - - let need_deactived = patch_entity.need_deactived(&process_list); + let need_deactive = patch_target.need_deactived(&process_list); let mut results = Vec::new(); - for pid in need_deactived { - let result = sys::deactive_patch(patch_uuid, pid, target_elf, patch_file); - if result.is_ok() { - patch_entity.remove_process(pid) - } + for pid in need_deactive { + let result = + sys::deactive_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); results.push((pid, result)); } - // Check results, return error if any process failes + // Return error if all process fails if !results.is_empty() && results.iter().any(|(_, result)| result.is_err()) { - let mut err_msg = String::new(); - - writeln!(err_msg, "Upatch: Failed to deactive patch")?; + let mut msg = String::new(); + writeln!(msg, "Upatch: Failed to deactive patch")?; for (pid, result) in &results { if let Err(e) = result { - writeln!(err_msg, "* Process {}: {}", pid, e)?; + writeln!(msg, "* Process {}: {}", pid, e)?; } } - err_msg.pop(); - bail!(err_msg); + msg.pop(); + bail!(msg); } - // Print failure results - for (pid, result) in &results { - if let Err(e) = result { - warn!( - "Upatch: Failed to deactive patch '{}' for process {}, {}", - patch_uuid, - pid, - e.to_string().to_lowercase(), - ); + // Process results + for (pid, result) in results { + match result { + Ok(_) => patch_target.remove_process(pid), + Err(e) => { + warn!( + "Upatch: Failed to deactive patch '{}' for process {}, {}", + patch.uuid, + pid, + e.to_string().to_lowercase(), + ); + } } } + patch_target.remove_patch(patch); - // Remove patch functions from target - patch_target.remove_patch(patch_uuid); - patch_target.remove_functions(patch_uuid, patch_functions); - - // If target is no longer patched, stop watching it - let need_stop_watch = !patch_target.is_patched(); + // If target is no longer has patch, stop watching it + let stop_watch = !patch_target.is_patched(); drop(target_map); - if need_stop_watch { - self.monitor.ignore_file(target_elf)?; + if stop_watch { + self.monitor.ignore_file(&patch.target_elf)?; } - self.set_patch_status(patch_uuid, PatchStatus::Deactived); + self.write_patch_status(&patch.uuid, PatchStatus::Deactived); Ok(()) } } diff --git a/syscared/src/patch/driver/upatch/monitor.rs b/syscared/src/patch/driver/upatch/monitor.rs index c0c1b3cb..150d1a35 100644 --- a/syscared/src/patch/driver/upatch/monitor.rs +++ b/syscared/src/patch/driver/upatch/monitor.rs @@ -20,7 +20,7 @@ use std::{ time::Duration, }; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use indexmap::IndexMap; use inotify::{Inotify, WatchDescriptor, WatchMask}; use log::info; @@ -75,13 +75,19 @@ impl UserPatchMonitor { Some(inotify) => { let wd = inotify .add_watch(watch_file, WatchMask::OPEN) - .with_context(|| format!("Failed to watch file {}", watch_file.display()))?; + .map_err(|e| { + anyhow!( + "Failed to watch file '{}', {}", + watch_file.display(), + e.to_string().to_lowercase() + ) + })?; self.watch_file_map .write() .insert(wd.clone(), watch_file.to_owned()); self.watch_wd_map.lock().insert(watch_file.to_owned(), wd); - info!("Start watching file {}", watch_file.display()); + info!("Start watching file '{}'", watch_file.display()); } None => bail!("Inotify does not exist"), } @@ -97,10 +103,14 @@ impl UserPatchMonitor { Some(inotify) => { self.watch_file_map.write().remove(&wd); - inotify.rm_watch(wd).with_context(|| { - format!("Failed to stop watch file {}", ignore_file.display()) + inotify.rm_watch(wd).map_err(|e| { + anyhow!( + "Failed to stop watch file '{}', {}", + ignore_file.display(), + e.to_string().to_lowercase() + ) })?; - info!("Stop watching file {}", ignore_file.display()); + info!("Stop watching file '{}'", ignore_file.display()); } None => bail!("Inotify does not exist"), } @@ -124,7 +134,13 @@ where thread::Builder::new() .name(MONITOR_THREAD_NAME.to_string()) .spawn(move || self.thread_main()) - .with_context(|| format!("Failed to create thread '{}'", MONITOR_THREAD_NAME)) + .map_err(|e| { + anyhow!( + "Failed to create thread '{}', {}", + MONITOR_THREAD_NAME, + e.to_string().to_lowercase() + ) + }) } #[inline] diff --git a/syscared/src/patch/driver/upatch/sys.rs b/syscared/src/patch/driver/upatch/sys.rs index ecc05224..85188401 100644 --- a/syscared/src/patch/driver/upatch/sys.rs +++ b/syscared/src/patch/driver/upatch/sys.rs @@ -1,14 +1,27 @@ use std::path::Path; use anyhow::{bail, Result}; -use log::Level; +use log::{debug, Level}; use uuid::Uuid; use syscare_common::process::Command; const UPATCH_MANAGE_BIN: &str = "upatch-manage"; -pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) -> Result<()> { +pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> +where + P: AsRef, + Q: AsRef, +{ + let target_elf = target_elf.as_ref(); + let patch_file = patch_file.as_ref(); + + debug!( + "Upatch: Patching '{}' to '{}' (pid: {})...", + patch_file.display(), + target_elf.display(), + pid, + ); let exit_code = Command::new(UPATCH_MANAGE_BIN) .arg("patch") .arg("--uuid") @@ -29,7 +42,20 @@ pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) } } -pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: &Path, patch_file: &Path) -> Result<()> { +pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> +where + P: AsRef, + Q: AsRef, +{ + let target_elf = target_elf.as_ref(); + let patch_file = patch_file.as_ref(); + + debug!( + "Upatch: Unpatching '{}' from '{}' (pid: {})...", + patch_file.display(), + target_elf.display(), + pid, + ); let exit_code = Command::new(UPATCH_MANAGE_BIN) .arg("unpatch") .arg("--uuid") diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs index 26c3ed32..e0e9f882 100644 --- a/syscared/src/patch/driver/upatch/target.rs +++ b/syscared/src/patch/driver/upatch/target.rs @@ -12,125 +12,113 @@ * See the Mulan PSL v2 for more details. */ -use std::ffi::OsString; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + path::PathBuf, +}; -use indexmap::IndexMap; +use indexmap::IndexSet; use uuid::Uuid; -use crate::patch::entity::UserPatchFunction; - -use super::entity::PatchEntity; - -#[derive(Debug)] -pub struct PatchFunction { - pub uuid: Uuid, - pub name: OsString, - pub size: u64, -} - -impl PatchFunction { - pub fn new(uuid: Uuid, function: &UserPatchFunction) -> Self { - Self { - uuid, - name: function.name.to_os_string(), - size: function.new_size, - } - } - - pub fn is_same_function(&self, uuid: &Uuid, function: &UserPatchFunction) -> bool { - (self.uuid == *uuid) && (self.name == function.name) && (self.size == function.new_size) - } -} +use crate::patch::entity::UserPatch; #[derive(Debug, Default)] pub struct PatchTarget { - patch_map: IndexMap, // patched file data - function_map: IndexMap>, // function addr -> function collision list + process_list: HashSet, + patch_map: HashMap, // uuid -> patch file + collision_map: HashMap>, // function old addr -> patch collision list } impl PatchTarget { - pub fn is_patched(&self) -> bool { - !self.patch_map.is_empty() + pub fn add_process(&mut self, pid: i32) { + self.process_list.insert(pid); } - pub fn add_patch(&mut self, uuid: Uuid, entity: PatchEntity) { - self.patch_map.insert(uuid, entity); + pub fn remove_process(&mut self, pid: i32) { + self.process_list.remove(&pid); } - pub fn remove_patch(&mut self, uuid: &Uuid) { - self.patch_map.remove(uuid); + pub fn clean_dead_process(&mut self, process_list: &HashSet) { + self.process_list.retain(|pid| process_list.contains(pid)); } - pub fn get_patch(&mut self, uuid: &Uuid) -> Option<&mut PatchEntity> { - self.patch_map.get_mut(uuid) + pub fn need_actived(&self, process_list: &HashSet) -> HashSet { + process_list + .difference(&self.process_list) + .copied() + .collect() } - pub fn all_patches(&mut self) -> impl IntoIterator { - self.patch_map.iter_mut() + pub fn need_deactived(&self, process_list: &HashSet) -> HashSet { + process_list + .intersection(&self.process_list) + .copied() + .collect() } } impl PatchTarget { - pub fn add_functions<'a, I>(&mut self, uuid: Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - self.function_map + pub fn add_patch(&mut self, patch: &UserPatch) { + for function in &patch.functions { + self.collision_map .entry(function.old_addr) .or_default() - .push(PatchFunction::new(uuid, function)); + .insert(patch.uuid); } + self.patch_map.insert(patch.uuid, patch.patch_file.clone()); } - pub fn remove_functions<'a, I>(&mut self, uuid: &Uuid, functions: I) - where - I: IntoIterator, - { - for function in functions { - if let Some(collision_list) = self.function_map.get_mut(&function.old_addr) { - if let Some(index) = collision_list - .iter() - .position(|patch_function| patch_function.is_same_function(uuid, function)) - { - collision_list.remove(index); - if collision_list.is_empty() { - self.function_map.remove(&function.old_addr); - } + pub fn remove_patch(&mut self, patch: &UserPatch) { + for function in &patch.functions { + if let Entry::Occupied(mut entry) = self.collision_map.entry(function.old_addr) { + let patch_set = entry.get_mut(); + patch_set.shift_remove(&patch.uuid); + + if patch_set.is_empty() { + entry.remove(); } } } + self.patch_map.remove(&patch.uuid); } -} -impl PatchTarget { - pub fn get_conflicts<'a, I>( + pub fn is_patched(&self) -> bool { + !self.collision_map.is_empty() + } + + pub fn all_patches(&self) -> impl Iterator + '_ { + self.patch_map + .iter() + .map(|(uuid, path)| (*uuid, path.to_path_buf())) + } + + pub fn get_conflicted_patches<'a>( &'a self, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - self.function_map - .get(&function.old_addr) - .and_then(|list| list.last()) - }) + patch: &'a UserPatch, + ) -> impl Iterator + 'a { + patch + .functions + .iter() + .filter_map(move |function| self.collision_map.get(&function.old_addr)) + .flatten() + .copied() + .filter(move |&uuid| uuid != patch.uuid) } - pub fn get_overrides<'a, I>( + pub fn get_overridden_patches<'a>( &'a self, - uuid: &'a Uuid, - functions: I, - ) -> impl IntoIterator - where - I: IntoIterator, - { - functions.into_iter().filter_map(move |function| { - self.function_map - .get(&function.old_addr) - .and_then(|list| list.last()) - .filter(|patch_function| !patch_function.is_same_function(uuid, function)) - }) + patch: &'a UserPatch, + ) -> impl Iterator + 'a { + patch + .functions + .iter() + .filter_map(move |function| self.collision_map.get(&function.old_addr)) + .flat_map(move |collision_list| { + collision_list + .iter() + .copied() + .skip_while(move |&uuid| uuid != patch.uuid) + .skip(1) + }) } } diff --git a/syscared/src/patch/entity/kpatch.rs b/syscared/src/patch/entity/kpatch.rs index 4c0be9c8..09e759dd 100644 --- a/syscared/src/patch/entity/kpatch.rs +++ b/syscared/src/patch/entity/kpatch.rs @@ -12,11 +12,78 @@ * See the Mulan PSL v2 for more details. */ -use std::{ffi::OsString, path::PathBuf, sync::Arc}; +use std::{ + collections::HashMap, + ffi::{CStr, OsStr, OsString}, + path::{Path, PathBuf}, + sync::Arc, +}; -use syscare_abi::PatchInfo; +use anyhow::{anyhow, Context, Result}; +use object::{File, Object, ObjectSection}; use uuid::Uuid; +use syscare_abi::{PatchEntity, PatchInfo}; +use syscare_common::{ffi::CStrExt, fs, os::kernel}; + +mod ffi { + use std::os::raw::{c_char, c_long, c_ulong}; + + use object::{Pod, Relocation, SectionRelocationIterator}; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + /// Corresponds to `struct kpatch_patch_func` defined in `kpatch-patch.h` + pub struct KpatchFunction { + pub new_addr: c_ulong, + pub new_size: c_ulong, + pub old_addr: c_ulong, + pub old_size: c_ulong, + pub sympos: u64, + pub name: *const c_char, + pub obj_name: *const c_char, + pub ref_name: *const c_char, + pub ref_offset: c_long, + } + + pub const KPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); + pub const KPATCH_FUNCTION_OFFSET: usize = 40; + pub const KPATCH_OBJECT_OFFSET: usize = 48; + + /* + * SAFETY: This struct is + * - #[repr(C)] + * - have no invalid byte values + * - have no padding + */ + unsafe impl Pod for KpatchFunction {} + + pub struct KpatchRelocation { + pub _addr: (u64, Relocation), + pub name: (u64, Relocation), + pub object: (u64, Relocation), + } + + pub struct KpatchRelocationIterator<'data, 'file>(pub SectionRelocationIterator<'data, 'file>); + + impl Iterator for KpatchRelocationIterator<'_, '_> { + type Item = KpatchRelocation; + + fn next(&mut self) -> Option { + if let (Some(addr), Some(name), Some(object)) = + (self.0.next(), self.0.next(), self.0.next()) + { + return Some(KpatchRelocation { + _addr: addr, + name, + object, + }); + } + None + } + } +} + /// Kernel patch function definition #[derive(Clone)] pub struct KernelPatchFunction { @@ -63,10 +130,157 @@ pub struct KernelPatch { pub name: OsString, pub info: Arc, pub pkg_name: String, - pub module_name: OsString, pub target_name: OsString, - pub functions: Vec, pub patch_file: PathBuf, - pub sys_file: PathBuf, + pub status_file: PathBuf, + pub module: kernel::ModuleInfo, + pub functions: HashMap>, // object name -> function list pub checksum: String, } + +impl KernelPatch { + fn parse_functions(patch_file: &Path) -> Result>> { + const KPATCH_FUNCS_SECTION: &str = ".kpatch.funcs"; + const KPATCH_STRINGS_SECTION: &str = ".kpatch.strings"; + + let mmap = fs::mmap(patch_file).map_err(|e| { + anyhow!( + "Failed to mmap '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + let file = File::parse(mmap.as_ref()).map_err(|e| { + anyhow!( + "Failed to parse '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + + // Read sections + let function_section = file + .section_by_name(KPATCH_FUNCS_SECTION) + .with_context(|| format!("Cannot find section '{}'", KPATCH_FUNCS_SECTION))?; + let string_section = file + .section_by_name(KPATCH_STRINGS_SECTION) + .with_context(|| format!("Cannot find section '{}'", KPATCH_STRINGS_SECTION))?; + let function_data = function_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + KPATCH_FUNCS_SECTION, + e.to_string().to_lowercase() + ) + })?; + let string_data = string_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + KPATCH_STRINGS_SECTION, + e.to_string().to_lowercase() + ) + })?; + + // Resolve patch functions + let (slice, _) = object::slice_from_bytes::( + function_data, + function_data.len() / ffi::KPATCH_FUNCTION_SIZE, + ) + .map_err(|_| anyhow!("Invalid patch function layout"))?; + + let mut functions: Vec<_> = slice + .iter() + .map(|function| KernelPatchFunction { + name: OsString::new(), + object: OsString::new(), + old_addr: function.old_addr, + old_size: function.old_size, + new_addr: function.new_addr, + new_size: function.new_size, + }) + .collect(); + + // Relocate patch functions + for relocation in ffi::KpatchRelocationIterator(function_section.relocations()) { + let (name_offset, name_reloc) = relocation.name; + let (object_offset, obj_reloc) = relocation.object; + + // Relocate patch function name + let name_index = + (name_offset as usize - ffi::KPATCH_FUNCTION_OFFSET) / ffi::KPATCH_FUNCTION_SIZE; + let name_function = functions + .get_mut(name_index) + .with_context(|| format!("Invalid patch function index, index={}", name_index))?; + let name_addend = name_reloc.addend() as usize; + name_function.name = CStr::from_bytes_with_next_nul(&string_data[name_addend..]) + .map_err(|_| anyhow!("Invalid patch function name"))? + .to_os_string(); + + // Relocate patch function object + let object_index = + (object_offset as usize - ffi::KPATCH_OBJECT_OFFSET) / ffi::KPATCH_FUNCTION_SIZE; + let object_function = functions + .get_mut(object_index) + .with_context(|| format!("Invalid patch object index, index={}", object_index))?; + let object_addend = obj_reloc.addend() as usize; + object_function.object = CStr::from_bytes_with_next_nul(&string_data[object_addend..]) + .map_err(|_| anyhow!("Invalid patch object name"))? + .to_os_string(); + } + + // group functions by it's object + let mut function_map: HashMap<_, Vec<_>> = HashMap::new(); + for function in functions { + function_map + .entry(function.object.clone()) + .or_default() + .push(function); + } + + Ok(function_map) + } + + pub fn parse( + name: S, + patch_info: Arc, + patch_entity: &PatchEntity, + patch_file: P, + ) -> Result + where + S: AsRef, + P: AsRef, + { + const KPATCH_SYS_DIR: &str = "/sys/kernel/livepatch"; + const KPATCH_STATUS_FILE_NAME: &str = "enabled"; + + let patch_file = patch_file.as_ref(); + let module = kernel::module_info(patch_file).map_err(|e| { + anyhow!( + "Failed to parse '{}' modinfo, {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + + let patch = Self { + uuid: patch_entity.uuid, + name: name.as_ref().to_os_string(), + info: patch_info.clone(), + pkg_name: patch_info.target.full_name(), + target_name: patch_entity.patch_name.as_os_str().to_os_string(), + patch_file: patch_file.to_path_buf(), + status_file: PathBuf::from(KPATCH_SYS_DIR) + .join(&module.name) + .join(KPATCH_STATUS_FILE_NAME), + module, + functions: Self::parse_functions(patch_file)?, + checksum: patch_entity.checksum.clone(), + }; + Ok(patch) + } +} + +impl std::fmt::Display for KernelPatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name.to_string_lossy()) + } +} diff --git a/syscared/src/patch/entity/patch.rs b/syscared/src/patch/entity/patch.rs index 2ff398a6..09413899 100644 --- a/syscared/src/patch/entity/patch.rs +++ b/syscared/src/patch/entity/patch.rs @@ -78,6 +78,9 @@ impl std::cmp::Ord for Patch { impl std::fmt::Display for Patch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.name().to_string_lossy()) + match self { + Patch::KernelPatch(patch) => write!(f, "{}", patch), + Patch::UserPatch(patch) => write!(f, "{}", patch), + } } } diff --git a/syscared/src/patch/entity/symbol.rs b/syscared/src/patch/entity/symbol.rs deleted file mode 100644 index c7845aa6..00000000 --- a/syscared/src/patch/entity/symbol.rs +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::ffi::OsString; - -/// Patch function definiation -#[derive(Clone)] -pub struct PatchFunction { - pub name: OsString, - pub target: OsString, - pub old_addr: u64, - pub old_size: u64, - pub new_addr: u64, - pub new_size: u64, -} - -impl std::fmt::Debug for PatchFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PatchFunction") - .field("name", &self.name) - .field("target", &self.target) - .field("old_addr", &format!("0x{}", self.old_addr)) - .field("old_size", &format!("0x{}", self.old_size)) - .field("new_addr", &format!("0x{}", self.new_addr)) - .field("new_size", &format!("0x{}", self.new_size)) - .finish() - } -} - -impl std::fmt::Display for PatchFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "name: {}, target: {}, old_addr: 0x{:x}, old_size: 0x{:x}, new_addr: 0x{:x}, new_size: 0x{:x}", - self.name.to_string_lossy(), - self.target.to_string_lossy(), - self.old_addr, - self.old_size, - self.new_addr, - self.new_size, - ) - } -} diff --git a/syscared/src/patch/entity/upatch.rs b/syscared/src/patch/entity/upatch.rs index 0fbacb4a..aaaee1c7 100644 --- a/syscared/src/patch/entity/upatch.rs +++ b/syscared/src/patch/entity/upatch.rs @@ -12,11 +12,66 @@ * See the Mulan PSL v2 for more details. */ -use std::{ffi::OsString, path::PathBuf, sync::Arc}; +use std::{ + ffi::{CStr, OsStr, OsString}, + path::{Path, PathBuf}, + sync::Arc, +}; -use syscare_abi::PatchInfo; +use anyhow::{anyhow, Context, Result}; +use object::{File, Object, ObjectSection}; use uuid::Uuid; +use syscare_abi::{PatchEntity, PatchInfo}; +use syscare_common::{ffi::CStrExt, fs}; + +mod ffi { + use std::os::raw::{c_char, c_ulong}; + + use object::{Pod, Relocation, SectionRelocationIterator}; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + /// Corresponds to `struct upatch_path_func` defined in `upatch-patch.h` + pub struct UpatchFunction { + pub new_addr: c_ulong, + pub new_size: c_ulong, + pub old_addr: c_ulong, + pub old_size: c_ulong, + pub sympos: c_ulong, + pub name: *const c_char, + } + + /* + * SAFETY: This struct is + * - #[repr(C)] + * - have no invalid byte values + * - have no padding + */ + unsafe impl Pod for UpatchFunction {} + + pub const UPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); + pub const UPATCH_FUNCTION_OFFSET: usize = 40; + + pub struct UpatchRelocation { + pub _addr: (u64, Relocation), + pub name: (u64, Relocation), + } + + pub struct UpatchRelocationIterator<'data, 'file>(pub SectionRelocationIterator<'data, 'file>); + + impl Iterator for UpatchRelocationIterator<'_, '_> { + type Item = UpatchRelocation; + + fn next(&mut self) -> Option { + if let (Some(addr), Some(name)) = (self.0.next(), self.0.next()) { + return Some(UpatchRelocation { _addr: addr, name }); + } + None + } + } +} + /// User patch function definition #[derive(Clone)] pub struct UserPatchFunction { @@ -31,10 +86,10 @@ impl std::fmt::Debug for UserPatchFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UserPatchFunction") .field("name", &self.name) - .field("old_addr", &format!("0x{}", self.old_addr)) - .field("old_size", &format!("0x{}", self.old_size)) - .field("new_addr", &format!("0x{}", self.new_addr)) - .field("new_size", &format!("0x{}", self.new_size)) + .field("old_addr", &format!("0x{:x}", self.old_addr)) + .field("old_size", &format!("0x{:x}", self.old_size)) + .field("new_addr", &format!("0x{:x}", self.new_addr)) + .field("new_size", &format!("0x{:x}", self.new_size)) .finish() } } @@ -60,8 +115,118 @@ pub struct UserPatch { pub name: OsString, pub info: Arc, pub pkg_name: String, - pub functions: Vec, - pub patch_file: PathBuf, pub target_elf: PathBuf, + pub patch_file: PathBuf, + pub functions: Vec, pub checksum: String, } + +impl UserPatch { + fn parse_functions(patch_file: &Path) -> Result> { + const UPATCH_FUNCS_SECTION: &str = ".upatch.funcs"; + const UPATCH_STRINGS_SECTION: &str = ".upatch.strings"; + + let mmap = fs::mmap(patch_file).map_err(|e| { + anyhow!( + "Failed to mmap '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + let file = File::parse(mmap.as_ref()).map_err(|e| { + anyhow!( + "Failed to parse '{}', {}", + patch_file.display(), + e.to_string().to_lowercase() + ) + })?; + + // Read sections + let function_section = file + .section_by_name(UPATCH_FUNCS_SECTION) + .with_context(|| format!("Cannot find section '{}'", UPATCH_FUNCS_SECTION))?; + let string_section = file + .section_by_name(UPATCH_STRINGS_SECTION) + .with_context(|| format!("Cannot find section '{}'", UPATCH_STRINGS_SECTION))?; + let function_data = function_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + UPATCH_FUNCS_SECTION, + e.to_string().to_lowercase() + ) + })?; + let string_data = string_section.data().map_err(|e| { + anyhow!( + "Failed to read section '{}', {}", + UPATCH_STRINGS_SECTION, + e.to_string().to_lowercase() + ) + })?; + + // Resolve patch functions + let (slice, _) = object::slice_from_bytes::( + function_data, + function_data.len() / ffi::UPATCH_FUNCTION_SIZE, + ) + .map_err(|_| anyhow!("Invalid patch function layout"))?; + + let mut functions: Vec<_> = slice + .iter() + .map(|function| UserPatchFunction { + name: OsString::new(), + old_addr: function.old_addr, + old_size: function.old_size, + new_addr: function.new_addr, + new_size: function.new_size, + }) + .collect(); + + // Relocate patch functions + for relocation in ffi::UpatchRelocationIterator(function_section.relocations()) { + let (value, reloc) = relocation.name; + + let index = (value as usize - ffi::UPATCH_FUNCTION_OFFSET) / ffi::UPATCH_FUNCTION_SIZE; + let function = functions + .get_mut(index) + .with_context(|| format!("Invalid patch function index, index={}", index))?; + let addend = reloc.addend() as usize; + + function.name = CStr::from_bytes_with_next_nul(&string_data[addend..]) + .map_err(|_| anyhow!("Invalid patch function name"))? + .to_os_string(); + } + + Ok(functions) + } + + pub fn parse( + name: S, + patch_info: Arc, + patch_entity: &PatchEntity, + patch_file: P, + ) -> Result + where + S: AsRef, + P: AsRef, + { + let patch_file = patch_file.as_ref(); + + let patch = Self { + uuid: patch_entity.uuid, + name: name.as_ref().to_os_string(), + info: patch_info.clone(), + pkg_name: patch_info.target.full_name(), + target_elf: patch_entity.patch_target.clone(), + patch_file: patch_file.to_path_buf(), + functions: Self::parse_functions(patch_file)?, + checksum: patch_entity.checksum.clone(), + }; + Ok(patch) + } +} + +impl std::fmt::Display for UserPatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name.to_string_lossy()) + } +} diff --git a/syscared/src/patch/manager.rs b/syscared/src/patch/manager.rs index 4bf65ac9..72484300 100644 --- a/syscared/src/patch/manager.rs +++ b/syscared/src/patch/manager.rs @@ -24,10 +24,16 @@ use lazy_static::lazy_static; use log::{debug, error, info, trace, warn}; use uuid::Uuid; -use syscare_abi::PatchStatus; +use syscare_abi::{PatchEntity, PatchInfo, PatchStatus, PatchType, PATCH_INFO_MAGIC}; use syscare_common::{concat_os, ffi::OsStrExt, fs, util::serde}; -use crate::{config::PatchConfig, patch::resolver::PatchResolver}; +use crate::{ + config::PatchConfig, + patch::{ + entity::{KernelPatch, UserPatch}, + PATCH_INFO_FILE_NAME, + }, +}; use super::{ driver::{PatchDriver, PatchOpFlag}, @@ -40,7 +46,7 @@ type TransitionAction = &'static (dyn Fn(&mut PatchManager, &Patch, PatchOpFlag) -> Result<()> + Sync); const PATCH_CHECK: TransitionAction = &PatchManager::driver_check_patch; -const PATCH_APPLY: TransitionAction = &PatchManager::driver_apply_patch; +const PATCH_LOAD: TransitionAction = &PatchManager::driver_load_patch; const PATCH_REMOVE: TransitionAction = &PatchManager::driver_remove_patch; const PATCH_ACTIVE: TransitionAction = &PatchManager::driver_active_patch; const PATCH_DEACTIVE: TransitionAction = &PatchManager::driver_deactive_patch; @@ -49,9 +55,9 @@ const PATCH_DECLINE: TransitionAction = &PatchManager::driver_decline_patch; lazy_static! { static ref STATUS_TRANSITION_MAP: IndexMap> = indexmap! { - (PatchStatus::NotApplied, PatchStatus::Deactived) => vec![PATCH_CHECK, PATCH_APPLY], - (PatchStatus::NotApplied, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_APPLY, PATCH_ACTIVE], - (PatchStatus::NotApplied, PatchStatus::Accepted) => vec![PATCH_CHECK, PATCH_APPLY, PATCH_ACTIVE, PATCH_ACCEPT], + (PatchStatus::NotApplied, PatchStatus::Deactived) => vec![PATCH_CHECK, PATCH_LOAD], + (PatchStatus::NotApplied, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_LOAD, PATCH_ACTIVE], + (PatchStatus::NotApplied, PatchStatus::Accepted) => vec![PATCH_CHECK, PATCH_LOAD, PATCH_ACTIVE, PATCH_ACCEPT], (PatchStatus::Deactived, PatchStatus::NotApplied) => vec![PATCH_REMOVE], (PatchStatus::Deactived, PatchStatus::Actived) => vec![PATCH_CHECK, PATCH_ACTIVE], (PatchStatus::Deactived, PatchStatus::Accepted) => vec![PATCH_ACTIVE, PATCH_ACCEPT], @@ -133,7 +139,7 @@ impl PatchManager { .unwrap_or_default(); if status == PatchStatus::Unknown { - status = self.driver_patch_status(patch, PatchOpFlag::Normal)?; + status = self.driver_get_patch_status(patch)?; self.set_patch_status(patch, status)?; } @@ -141,6 +147,7 @@ impl PatchManager { } pub fn check_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { + info!("Check patch '{}'", patch); self.driver.check_patch(patch, flag)?; self.driver.check_confliction(patch, flag)?; @@ -245,20 +252,20 @@ impl PatchManager { debug!("Reading patch status..."); let status_map: IndexMap = - serde::deserialize(&self.patch_status_file).context("Failed to read patch status")?; + serde::deserialize(&self.patch_status_file) + .context("Failed to read patch status file")?; for (uuid, status) in &status_map { debug!("Patch '{}' status: {}", uuid, status); } - - let restore_list = status_map - .into_iter() - .filter(|(_, status)| !accepted_only || (*status == PatchStatus::Accepted)); - for (uuid, status) in restore_list { + for (uuid, status) in status_map { + if accepted_only && (status != PatchStatus::Accepted) { + continue; + } match self.find_patch_by_uuid(&uuid) { Ok(patch) => { - debug!("Restore patch '{}' status to '{}'", patch, status); + info!("Restore patch '{}' status to '{}'", patch, status); if let Err(e) = self.do_status_transition(&patch, status, PatchOpFlag::Force) { - error!("{}", e); + error!("{:?}", e); } } Err(e) => { @@ -277,7 +284,7 @@ impl PatchManager { let status_keys = self.status_map.keys().copied().collect::>(); for patch_uuid in status_keys { if !self.patch_map.contains_key(&patch_uuid) { - trace!("Patch '{}' was removed, remove its status", patch_uuid); + trace!("Patch '{}' was removed", patch_uuid); self.status_map.remove(&patch_uuid); } } @@ -329,19 +336,110 @@ impl PatchManager { } impl PatchManager { + fn parse_user_patch( + root_dir: &Path, + patch_info: Arc, + patch_entity: &PatchEntity, + ) -> Result { + let patch_name = concat_os!( + patch_info.target.short_name(), + "/", + patch_info.name(), + "/", + patch_entity.patch_target.file_name().unwrap_or_default() + ); + let patch_file = root_dir.join(&patch_entity.patch_name); + + let patch = UserPatch::parse(&patch_name, patch_info, patch_entity, patch_file) + .with_context(|| { + format!( + "Failed to parse patch '{}' ({})", + patch_entity.uuid, + patch_name.to_string_lossy(), + ) + })?; + + debug!("Found patch '{}' ({})", patch.uuid, patch); + Ok(patch) + } + + fn parse_kernel_patch( + root_dir: &Path, + patch_info: Arc, + patch_entity: &PatchEntity, + ) -> Result { + const KPATCH_EXTENSION: &str = "ko"; + + let patch_name = concat_os!( + patch_info.target.short_name(), + "/", + patch_info.name(), + "/", + &patch_entity.patch_target, + ); + let mut patch_file = root_dir.join(&patch_entity.patch_name); + patch_file.set_extension(KPATCH_EXTENSION); + + let patch = KernelPatch::parse(&patch_name, patch_info, patch_entity, patch_file) + .with_context(|| { + format!( + "Failed to parse patch '{}' ({})", + patch_entity.uuid, + patch_name.to_string_lossy(), + ) + })?; + + debug!("Found patch '{}' ({})", patch.uuid, patch); + Ok(patch) + } + + fn parse_patches(root_dir: &Path) -> Result> { + let root_name = root_dir.file_name().expect("Invalid patch root directory"); + let patch_metadata = root_dir.join(PATCH_INFO_FILE_NAME); + let patch_info = Arc::new( + serde::deserialize_with_magic::(patch_metadata, PATCH_INFO_MAGIC) + .with_context(|| { + format!( + "Failed to parse patch '{}' metadata", + root_name.to_string_lossy(), + ) + })?, + ); + + patch_info + .entities + .iter() + .map(|patch_entity| { + let patch_info = patch_info.clone(); + match patch_info.kind { + PatchType::UserPatch => Ok(Patch::UserPatch(Self::parse_user_patch( + root_dir, + patch_info, + patch_entity, + )?)), + PatchType::KernelPatch => Ok(Patch::KernelPatch(Self::parse_kernel_patch( + root_dir, + patch_info, + patch_entity, + )?)), + } + }) + .collect::>>() + } + fn scan_patches>(directory: P) -> Result>> { const TRAVERSE_OPTION: fs::TraverseOptions = fs::TraverseOptions { recursive: false }; let mut patch_map = IndexMap::new(); - info!("Scanning patches from {}...", directory.as_ref().display()); - for patch_dir in fs::list_dirs(directory, TRAVERSE_OPTION)? { - let resolve_result = PatchResolver::resolve_patch(&patch_dir) - .with_context(|| format!("Failed to resolve patch from {}", patch_dir.display())); - match resolve_result { + info!( + "Scanning patches from '{}'...", + directory.as_ref().display() + ); + for root_dir in fs::list_dirs(directory, TRAVERSE_OPTION)? { + match Self::parse_patches(&root_dir) { Ok(patches) => { for patch in patches { - debug!("Detected patch '{}'", patch); patch_map.insert(*patch.uuid(), Arc::new(patch)); } } @@ -399,15 +497,12 @@ impl PatchManager { bail!("Cannot set patch '{}' status to '{}'", patch, value); } - let (index, _) = self.status_map.insert_full(*patch.uuid(), value); - if let Some(last_index) = self - .status_map - .last() - .and_then(|(key, _)| self.status_map.get_index_of(key)) - { - if index != last_index { - self.status_map.move_index(index, last_index); - } + let uuid = *patch.uuid(); + let (curr_index, _) = self.status_map.insert_full(uuid, value); + + let last_index = self.status_map.len().saturating_sub(1); + if curr_index != last_index { + self.status_map.move_index(curr_index, last_index); } Ok(()) @@ -415,16 +510,16 @@ impl PatchManager { } impl PatchManager { - fn driver_patch_status(&self, patch: &Patch, _flag: PatchOpFlag) -> Result { - self.driver.patch_status(patch) - } - fn driver_check_patch(&mut self, patch: &Patch, flag: PatchOpFlag) -> Result<()> { self.driver.check_patch(patch, flag) } - fn driver_apply_patch(&mut self, patch: &Patch, _flag: PatchOpFlag) -> Result<()> { - self.driver.apply_patch(patch)?; + fn driver_get_patch_status(&self, patch: &Patch) -> Result { + self.driver.get_patch_status(patch) + } + + fn driver_load_patch(&mut self, patch: &Patch, _flag: PatchOpFlag) -> Result<()> { + self.driver.load_patch(patch)?; self.set_patch_status(patch, PatchStatus::Deactived) } diff --git a/syscared/src/patch/mod.rs b/syscared/src/patch/mod.rs index 26a97da6..edd247ab 100644 --- a/syscared/src/patch/mod.rs +++ b/syscared/src/patch/mod.rs @@ -16,7 +16,6 @@ pub mod driver; pub mod entity; pub mod manager; pub mod monitor; -pub mod resolver; pub mod transaction; const PATCH_INFO_FILE_NAME: &str = "patch_info"; diff --git a/syscared/src/patch/resolver/kpatch.rs b/syscared/src/patch/resolver/kpatch.rs deleted file mode 100644 index f8885ff1..00000000 --- a/syscared/src/patch/resolver/kpatch.rs +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ffi::{CStr, OsString}, - path::{Path, PathBuf}, - sync::Arc, -}; - -use anyhow::{anyhow, Context, Result}; -use object::{NativeFile, Object, ObjectSection}; - -use syscare_abi::{PatchEntity, PatchInfo}; -use syscare_common::{ - concat_os, - ffi::{CStrExt, OsStrExt}, - fs, -}; - -use super::PatchResolverImpl; -use crate::patch::entity::{KernelPatch, KernelPatchFunction, Patch}; - -const KPATCH_SUFFIX: &str = ".ko"; -const KPATCH_SYS_DIR: &str = "/sys/kernel/livepatch"; -const KPATCH_SYS_FILE_NAME: &str = "enabled"; - -mod ffi { - use std::os::raw::{c_char, c_long, c_ulong}; - - use object::{ - read::elf::{ElfSectionRelocationIterator, FileHeader}, - Pod, Relocation, - }; - - #[repr(C)] - #[derive(Debug, Clone, Copy)] - /// Corresponds to `struct kpatch_patch_func` defined in `kpatch-patch.h` - pub struct KpatchFunction { - pub new_addr: c_ulong, - pub new_size: c_ulong, - pub old_addr: c_ulong, - pub old_size: c_ulong, - pub sympos: u64, - pub name: *const c_char, - pub obj_name: *const c_char, - pub ref_name: *const c_char, - pub ref_offset: c_long, - } - - pub const KPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); - pub const KPATCH_FUNCTION_OFFSET: usize = 40; - pub const KPATCH_OBJECT_OFFSET: usize = 48; - - /* - * SAFETY: This struct is - * - #[repr(C)] - * - have no invalid byte values - * - have no padding - */ - unsafe impl Pod for KpatchFunction {} - - pub struct KpatchRelocation { - pub _addr: (u64, Relocation), - pub name: (u64, Relocation), - pub object: (u64, Relocation), - } - - pub struct KpatchRelocationIterator<'data, 'file, Elf: FileHeader>( - ElfSectionRelocationIterator<'data, 'file, Elf, &'data [u8]>, - ); - - impl<'data, 'file, Elf: FileHeader> KpatchRelocationIterator<'data, 'file, Elf> { - pub fn new(relocations: ElfSectionRelocationIterator<'data, 'file, Elf>) -> Self { - Self(relocations) - } - } - - impl Iterator for KpatchRelocationIterator<'_, '_, Elf> { - type Item = KpatchRelocation; - - fn next(&mut self) -> Option { - if let (Some(addr), Some(name), Some(object)) = - (self.0.next(), self.0.next(), self.0.next()) - { - return Some(KpatchRelocation { - _addr: addr, - name, - object, - }); - } - None - } - } -} - -const KPATCH_FUNCS_SECTION: &str = ".kpatch.funcs"; -const KPATCH_STRINGS_SECTION: &str = ".kpatch.strings"; - -pub struct KpatchResolverImpl; - -impl KpatchResolverImpl { - #[inline] - fn resolve_patch_file(patch: &mut KernelPatch) -> Result<()> { - let patch_file = fs::mmap(&patch.patch_file) - .with_context(|| format!("Failed to mmap file {}", patch.patch_file.display()))?; - let patch_elf = NativeFile::parse(patch_file.as_ref()).context("Invalid patch format")?; - - // Read sections - let function_section = patch_elf - .section_by_name(KPATCH_FUNCS_SECTION) - .with_context(|| format!("Cannot find section '{}'", KPATCH_FUNCS_SECTION))?; - let string_section = patch_elf - .section_by_name(KPATCH_STRINGS_SECTION) - .with_context(|| format!("Cannot find section '{}'", KPATCH_STRINGS_SECTION))?; - let function_data = function_section - .data() - .with_context(|| format!("Failed to read section '{}'", KPATCH_FUNCS_SECTION))?; - let string_data = string_section - .data() - .with_context(|| format!("Failed to read section '{}'", KPATCH_FUNCS_SECTION))?; - - // Resolve patch functions - let patch_functions = &mut patch.functions; - let kpatch_function_slice = object::slice_from_bytes::( - function_data, - function_data.len() / ffi::KPATCH_FUNCTION_SIZE, - ) - .map(|(f, _)| f) - .map_err(|_| anyhow!("Invalid data format")) - .context("Failed to resolve patch functions")?; - - for function in kpatch_function_slice { - patch_functions.push(KernelPatchFunction { - name: OsString::new(), - object: OsString::new(), - old_addr: function.old_addr, - old_size: function.old_size, - new_addr: function.new_addr, - new_size: function.new_size, - }); - } - - // Relocate patch functions - for relocation in ffi::KpatchRelocationIterator::new(function_section.relocations()) { - let (name_reloc_offset, name_reloc) = relocation.name; - let (object_reloc_offset, obj_reloc) = relocation.object; - - // Relocate patch function name - let name_index = (name_reloc_offset as usize - ffi::KPATCH_FUNCTION_OFFSET) - / ffi::KPATCH_FUNCTION_SIZE; - let name_function = patch_functions - .get_mut(name_index) - .context("Failed to find patch function")?; - let name_offset = name_reloc.addend() as usize; - let name_string = CStr::from_bytes_with_next_nul(&string_data[name_offset..]) - .context("Failed to parse patch object name")? - .to_os_string(); - - name_function.name = name_string; - - // Relocate patch function object - let object_index = (object_reloc_offset as usize - ffi::KPATCH_OBJECT_OFFSET) - / ffi::KPATCH_FUNCTION_SIZE; - let object_function = patch_functions - .get_mut(object_index) - .context("Failed to find patch function")?; - let object_offset = obj_reloc.addend() as usize; - let object_string = CStr::from_bytes_with_next_nul(&string_data[object_offset..]) - .context("Failed to parse patch function name")? - .to_os_string(); - - object_function.object = object_string; - } - - Ok(()) - } -} - -impl PatchResolverImpl for KpatchResolverImpl { - fn resolve_patch( - &self, - patch_root: &Path, - patch_info: Arc, - patch_entity: &PatchEntity, - ) -> Result { - let target_name = patch_entity.patch_name.as_os_str().to_os_string(); - let module_name = patch_entity.patch_name.replace(['-', '.'], "_"); - let patch_file = patch_root.join(concat_os!(&patch_entity.patch_name, KPATCH_SUFFIX)); - let sys_file = PathBuf::from(KPATCH_SYS_DIR) - .join(&module_name) - .join(KPATCH_SYS_FILE_NAME); - - let mut patch = KernelPatch { - uuid: patch_entity.uuid, - name: concat_os!( - patch_info.target.short_name(), - "/", - patch_info.name(), - "/", - &patch_entity.patch_target - ), - info: patch_info.clone(), - pkg_name: patch_info.target.full_name(), - target_name, - module_name, - patch_file, - sys_file, - functions: Vec::new(), - checksum: patch_entity.checksum.clone(), - }; - Self::resolve_patch_file(&mut patch).context("Failed to resolve patch")?; - - Ok(Patch::KernelPatch(patch)) - } -} diff --git a/syscared/src/patch/resolver/mod.rs b/syscared/src/patch/resolver/mod.rs deleted file mode 100644 index 7c4a504d..00000000 --- a/syscared/src/patch/resolver/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{path::Path, sync::Arc}; - -use super::{entity::Patch, PATCH_INFO_FILE_NAME}; - -use anyhow::{Context, Result}; -use syscare_abi::{PatchEntity, PatchInfo, PatchType, PATCH_INFO_MAGIC}; -use syscare_common::util::serde; - -mod kpatch; -mod upatch; - -use kpatch::KpatchResolverImpl; -use upatch::UpatchResolverImpl; - -pub trait PatchResolverImpl { - fn resolve_patch( - &self, - patch_root: &Path, - patch_info: Arc, - patch_entity: &PatchEntity, - ) -> Result; -} - -pub struct PatchResolver; - -impl PatchResolver { - pub fn resolve_patch>(directory: P) -> Result> { - let patch_root = directory.as_ref(); - let patch_info = Arc::new( - serde::deserialize_with_magic::( - patch_root.join(PATCH_INFO_FILE_NAME), - PATCH_INFO_MAGIC, - ) - .context("Failed to resolve patch metadata")?, - ); - let resolver: &dyn PatchResolverImpl = match patch_info.kind { - PatchType::UserPatch => &UpatchResolverImpl, - PatchType::KernelPatch => &KpatchResolverImpl, - }; - - let mut patch_list = Vec::with_capacity(patch_info.entities.len()); - for patch_entity in &patch_info.entities { - let patch = resolver.resolve_patch(patch_root, patch_info.clone(), patch_entity)?; - patch_list.push(patch); - } - - Ok(patch_list) - } -} diff --git a/syscared/src/patch/resolver/upatch.rs b/syscared/src/patch/resolver/upatch.rs deleted file mode 100644 index a5e4ad0c..00000000 --- a/syscared/src/patch/resolver/upatch.rs +++ /dev/null @@ -1,182 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ffi::{CStr, OsString}, - path::Path, - sync::Arc, -}; - -use anyhow::{anyhow, Context, Result}; -use object::{NativeFile, Object, ObjectSection}; - -use syscare_abi::{PatchEntity, PatchInfo}; -use syscare_common::{concat_os, ffi::CStrExt, fs}; - -use super::PatchResolverImpl; -use crate::patch::entity::{Patch, UserPatch, UserPatchFunction}; - -mod ffi { - use std::os::raw::{c_char, c_ulong}; - - use object::{ - read::elf::{ElfSectionRelocationIterator, FileHeader}, - Pod, Relocation, - }; - - #[repr(C)] - #[derive(Debug, Clone, Copy)] - /// Corresponds to `struct upatch_path_func` defined in `upatch-patch.h` - pub struct UpatchFunction { - pub new_addr: c_ulong, - pub new_size: c_ulong, - pub old_addr: c_ulong, - pub old_size: c_ulong, - pub sympos: c_ulong, - pub name: *const c_char, - } - - /* - * SAFETY: This struct is - * - #[repr(C)] - * - have no invalid byte values - * - have no padding - */ - unsafe impl Pod for UpatchFunction {} - - pub const UPATCH_FUNCTION_SIZE: usize = std::mem::size_of::(); - pub const UPATCH_FUNCTION_OFFSET: usize = 40; - - pub struct UpatchRelocation { - pub _addr: (u64, Relocation), - pub name: (u64, Relocation), - } - - pub struct UpatchRelocationIterator<'data, 'file, Elf: FileHeader>( - ElfSectionRelocationIterator<'data, 'file, Elf, &'data [u8]>, - ); - - impl<'data, 'file, Elf: FileHeader> UpatchRelocationIterator<'data, 'file, Elf> { - pub fn new(relocations: ElfSectionRelocationIterator<'data, 'file, Elf>) -> Self { - Self(relocations) - } - } - - impl Iterator for UpatchRelocationIterator<'_, '_, Elf> { - type Item = UpatchRelocation; - - fn next(&mut self) -> Option { - if let (Some(addr), Some(name)) = (self.0.next(), self.0.next()) { - return Some(UpatchRelocation { _addr: addr, name }); - } - None - } - } -} - -const UPATCH_FUNCS_SECTION: &str = ".upatch.funcs"; -const UPATCH_STRINGS_SECTION: &str = ".upatch.strings"; - -pub struct UpatchResolverImpl; - -impl UpatchResolverImpl { - #[inline] - fn resolve_patch_elf(patch: &mut UserPatch) -> Result<()> { - let patch_file = fs::mmap(&patch.patch_file) - .with_context(|| format!("Failed to mmap file {}", patch.patch_file.display()))?; - let patch_elf = NativeFile::parse(patch_file.as_ref()).context("Invalid patch format")?; - - // Read sections - let function_section = patch_elf - .section_by_name(UPATCH_FUNCS_SECTION) - .with_context(|| format!("Cannot find section '{}'", UPATCH_FUNCS_SECTION))?; - let string_section = patch_elf - .section_by_name(UPATCH_STRINGS_SECTION) - .with_context(|| format!("Cannot find section '{}'", UPATCH_STRINGS_SECTION))?; - let function_data = function_section - .data() - .with_context(|| format!("Failed to read section '{}'", UPATCH_FUNCS_SECTION))?; - let string_data = string_section - .data() - .with_context(|| format!("Failed to read section '{}'", UPATCH_FUNCS_SECTION))?; - - // Resolve patch functions - let patch_functions = &mut patch.functions; - let upatch_function_slice = object::slice_from_bytes::( - function_data, - function_data.len() / ffi::UPATCH_FUNCTION_SIZE, - ) - .map(|(f, _)| f) - .map_err(|_| anyhow!("Invalid data format")) - .context("Failed to resolve patch functions")?; - - for function in upatch_function_slice { - patch_functions.push(UserPatchFunction { - name: OsString::new(), - old_addr: function.old_addr, - old_size: function.old_size, - new_addr: function.new_addr, - new_size: function.new_size, - }); - } - - // Relocate patch functions - for relocation in ffi::UpatchRelocationIterator::new(function_section.relocations()) { - let (name_reloc_offset, name_reloc) = relocation.name; - - let name_index = (name_reloc_offset as usize - ffi::UPATCH_FUNCTION_OFFSET) - / ffi::UPATCH_FUNCTION_SIZE; - let name_function = patch_functions - .get_mut(name_index) - .context("Failed to find patch function")?; - let name_offset = name_reloc.addend() as usize; - let name_string = CStr::from_bytes_with_next_nul(&string_data[name_offset..]) - .context("Failed to parse patch function name")? - .to_os_string(); - - name_function.name = name_string; - } - - Ok(()) - } -} - -impl PatchResolverImpl for UpatchResolverImpl { - fn resolve_patch( - &self, - patch_root: &Path, - patch_info: Arc, - patch_entity: &PatchEntity, - ) -> Result { - let mut patch = UserPatch { - uuid: patch_entity.uuid, - name: concat_os!( - patch_info.target.short_name(), - "/", - patch_info.name(), - "/", - fs::file_name(&patch_entity.patch_target) - ), - info: patch_info.clone(), - pkg_name: patch_info.target.full_name(), - patch_file: patch_root.join(&patch_entity.patch_name), - target_elf: patch_entity.patch_target.clone(), - functions: Vec::new(), - checksum: patch_entity.checksum.clone(), - }; - Self::resolve_patch_elf(&mut patch).context("Failed to resolve patch")?; - - Ok(Patch::UserPatch(patch)) - } -} -- Gitee From a3cac86e9613863240efc4039c92e2f8355d53e5 Mon Sep 17 00:00:00 2001 From: renoseven Date: Wed, 28 May 2025 18:29:34 +0800 Subject: [PATCH 10/13] project: update Cargo.lock Signed-off-by: renoseven --- Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b4798dec..38324d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1146,10 +1146,11 @@ name = "syscare-common" version = "1.2.2" dependencies = [ "anyhow", - "lazy_static", "log", "memmap2", "nix", + "num_cpus", + "object", "regex", "serde", "serde_cbor", -- Gitee From e26c5d33f963cb7cd1e9953a653383d5675ad534 Mon Sep 17 00:00:00 2001 From: renoseven Date: Wed, 11 Jun 2025 13:31:46 +0800 Subject: [PATCH 11/13] project: update CMakeLists.txt Signed-off-by: renoseven --- CMakeLists.txt | 80 ++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95a917f1..843abc67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,29 +37,29 @@ if(GIT_FOUND) ERROR_QUIET WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) - set(PROJECT_BUILD_VERSION "${BUILD_VERSION}-g${GIT_VERSION}") + set(BUILD_VERSION "${BUILD_VERSION}-g${GIT_VERSION}") else() - set(PROJECT_BUILD_VERSION "${BUILD_VERSION}") + set(BUILD_VERSION "${BUILD_VERSION}") endif() # Build configurations if(ENABLE_ASAN) - set(PROJECT_BUILD_VERSION "${PROJECT_BUILD_VERSION}-asan") - list(APPEND PROJECT_C_BUILD_FLAGS -fsanitize=address -fno-omit-frame-pointer) - list(APPEND PROJECT_C_LIBRARIES asan) + set(BUILD_VERSION "${BUILD_VERSION}-asan") + list(APPEND BUILD_FLAGS_C -fsanitize=address -fno-omit-frame-pointer) + list(APPEND LINK_LIBRARIES_C asan) endif() if(ENABLE_GCOV) - set(PROJECT_BUILD_VERSION "${PROJECT_BUILD_VERSION}-gcov") - list(APPEND PROJECT_C_BUILD_FLAGS -ftest-coverage -fprofile-arcs) - list(APPEND PROJECT_RUST_FLAGS -C instrument-coverage) - list(APPEND PROJECT_C_LIBRARIES gcov) + set(BUILD_VERSION "${BUILD_VERSION}-gcov") + list(APPEND BUILD_FLAGS_C -ftest-coverage -fprofile-arcs) + list(APPEND BUILD_FLAGS_RUST -C instrument-coverage) + list(APPEND LINK_LIBRARIES_C gcov) endif() # Build flags -list(APPEND PROJECT_C_BUILD_FLAGS - -std=gnu99 -Wall -O2 -Werror -Wextra - -DBUILD_VERSION="${PROJECT_BUILD_VERSION}" -D_FORTIFY_SOURCE=2 +list(APPEND BUILD_FLAGS_C + -std=gnu99 -O2 -Wall -Wextra -Werror + -DBUILD_VERSION="${BUILD_VERSION}" -D_FORTIFY_SOURCE=2 -Wtrampolines -Wformat=2 -Wstrict-prototypes -Wdate-time -Wstack-usage=8192 -Wfloat-equal -Wswitch-default -Wshadow -Wconversion -Wcast-qual -Wunused -Wundef @@ -70,16 +70,16 @@ list(APPEND PROJECT_C_BUILD_FLAGS # The -Werror=cast-align compiler flag causes issues on riscv64 GCC, # while the same operations do not error on aarch64. This appears to be -# a compiler-specific problem. Temporarily disable this option as a +# a compiler-specific problem. Temporarily disable this option as a # workaround since applying fixes would require intrusive code changes # across multiple files. if(NOT ARCH STREQUAL "riscv64") - list(APPEND PROJECT_C_BUILD_FLAGS + list(APPEND BUILD_FLAGS_C -Wcast-align ) endif() -list(APPEND PROJECT_RUST_FLAGS +list(APPEND BUILD_FLAGS_RUST --cfg unsound_local_offset -D warnings -C link-arg=-s @@ -91,7 +91,7 @@ list(APPEND PROJECT_RUST_FLAGS ) # Link flags -list(APPEND PROJECT_C_LINK_FLAGS +list(APPEND LINK_FLAGS_C -pie -Wl,-z,relro,-z,now -Wl,-z,noexecstack -rdynamic @@ -100,9 +100,9 @@ list(APPEND PROJECT_C_LINK_FLAGS ) if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - list(APPEND PROJECT_C_BUILD_FLAGS -g) + list(APPEND BUILD_FLAGS_C -g) elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - list(APPEND PROJECT_C_LINK_FLAGS -s) + list(APPEND LINK_FLAGS_C -s) endif() # Install directories @@ -120,44 +120,42 @@ message("███████║ ██║ ███████║╚█ message("╚══════╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝") message("---------------------------------------------------------") message("-- Build type: ${CMAKE_BUILD_TYPE}") -message("-- Build version: ${PROJECT_BUILD_VERSION}") -message("-- Rust flags: ${PROJECT_RUST_FLAGS}") -message("-- Build flags: ${PROJECT_C_BUILD_FLAGS}") -message("-- Link flags: ${PROJECT_C_LINK_FLAGS}") -message("-- Link libraries: ${PROJECT_C_LIBRARIES}") +message("-- Build version: ${BUILD_VERSION}") +message("-- Rust flags: ${BUILD_FLAGS_RUST}") +message("-- Build flags: ${BUILD_FLAGS_C}") +message("-- Link flags: ${LINK_FLAGS_C}") +message("-- Link libraries: ${LINK_LIBRARIES_C}") message("-- Binary directory: ${SYSCARE_BINARY_DIR}") message("-- Libexec directory: ${SYSCARE_LIBEXEC_DIR}") message("-- Service directory: ${SYSCARE_SERVICE_DIR}") message("---------------------------------------------------------") # Apply all flags -add_compile_options(${PROJECT_C_BUILD_FLAGS}) -add_link_options(${PROJECT_C_LINK_FLAGS}) -link_libraries(${PROJECT_C_LIBRARIES}) +add_compile_options(${BUILD_FLAGS_C}) +add_link_options(${LINK_FLAGS_C}) +link_libraries(${LINK_LIBRARIES_C}) # Build rust executables -set(RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/rust") +set(RUST_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/target") set(RUST_OUTPUT_DIR "${RUST_TARGET_DIR}/release") -foreach(FLAG IN LISTS PROJECT_RUST_FLAGS) - set(RUST_FLAGS "${RUST_FLAGS} ${FLAG}") +foreach(CURR_FLAG IN LISTS BUILD_FLAGS_RUST) + set(RUST_FLAGS "${RUST_FLAGS} ${CURR_FLAG}") endforeach() -add_custom_target(rust-build ALL - COMMENT "Building rust executables..." - COMMAND ${CMAKE_COMMAND} -E env - "BUILD_VERSION=${PROJECT_BUILD_VERSION}" - "RUSTFLAGS=${RUST_FLAGS}" - cargo build --release --target-dir "${RUST_TARGET_DIR}" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +add_custom_target(syscare ALL + COMMAND cargo build --release --target-dir ${RUST_TARGET_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ) -# Generate upatch helpers -add_custom_target(generate-upatch-helpers ALL - COMMENT "Generating upatch helpers..." +set_target_properties(syscare PROPERTIES + ENVIRONMENT "RUSTFLAGS=${RUST_FLAGS};BUILD_VERSION=${BUILD_VERSION}" + ADDITIONAL_CLEAN_FILES "${RUST_TARGET_DIR}" +) + +add_custom_target(upatch-helpers ALL COMMAND ln -sf upatch-helper upatch-cc COMMAND ln -sf upatch-helper upatch-c++ - DEPENDS - rust-build + DEPENDS syscare WORKING_DIRECTORY ${RUST_OUTPUT_DIR} ) -- Gitee From f8895912c9d3b687ae983c639a8a79a24f945720 Mon Sep 17 00:00:00 2001 From: renoseven Date: Tue, 10 Jun 2025 15:03:43 +0800 Subject: [PATCH 12/13] syscared: switch user patch impl from ptrace to uprobe Signed-off-by: renoseven --- syscared/src/config.rs | 2 +- syscared/src/patch/driver/upatch/mod.rs | 361 ++++++-------------- syscared/src/patch/driver/upatch/monitor.rs | 186 ---------- syscared/src/patch/driver/upatch/sys.rs | 185 +++++++--- syscared/src/patch/driver/upatch/target.rs | 43 +-- 5 files changed, 239 insertions(+), 538 deletions(-) delete mode 100644 syscared/src/patch/driver/upatch/monitor.rs diff --git a/syscared/src/config.rs b/syscared/src/config.rs index 5e0d7361..932669fd 100644 --- a/syscared/src/config.rs +++ b/syscared/src/config.rs @@ -64,7 +64,7 @@ pub struct KernelPatchConfig { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct UserPatchConfig { - pub skipped: Vec, + pub blocked: Vec, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] diff --git a/syscared/src/patch/driver/upatch/mod.rs b/syscared/src/patch/driver/upatch/mod.rs index cc00f531..6cc04bae 100644 --- a/syscared/src/patch/driver/upatch/mod.rs +++ b/syscared/src/patch/driver/upatch/mod.rs @@ -14,76 +14,99 @@ use std::{ collections::{HashMap, HashSet}, - ffi::OsStr, fmt::Write, + fs::File, iter::FromIterator, - os::linux::fs::MetadataExt, - path::{Path, PathBuf}, - sync::Arc, + os::unix::io::{AsRawFd, RawFd}, + path::PathBuf, }; -use anyhow::{bail, ensure, Result}; -use log::{debug, warn}; -use parking_lot::RwLock; -use uuid::Uuid; +use anyhow::{anyhow, ensure, Result}; +use log::debug; use syscare_abi::PatchStatus; -use syscare_common::{fs, util::digest}; +use syscare_common::{ + os::{kernel, selinux}, + util::digest, +}; use crate::{config::UserPatchConfig, patch::entity::UserPatch}; -mod monitor; mod sys; mod target; -use monitor::UserPatchMonitor; use target::PatchTarget; pub struct UserPatchDriver { - status_map: HashMap, - target_map: Arc>>, - skipped_files: Arc>, - monitor: UserPatchMonitor, + ioctl_dev: File, + target_map: HashMap, // target elf -> target + blocked_files: HashSet, + _guard: kernel::ModuleGuard, } impl UserPatchDriver { pub fn new(config: &UserPatchConfig) -> Result { - let target_map = Arc::new(RwLock::new(HashMap::new())); - let skipped_files = Arc::new(HashSet::from_iter(config.skipped.iter().cloned())); - - Ok(Self { - status_map: HashMap::new(), - target_map: target_map.clone(), - skipped_files: skipped_files.clone(), - monitor: UserPatchMonitor::new(move |target_elfs| { - for target_elf in target_elfs { - Self::patch_new_process(&target_map, &skipped_files, target_elf); - } + const UPATCH_KMOD_FILE: &str = "/usr/libexec/syscare/upatch_manage.ko"; + const UPATCH_DEV_FILE: &str = "/dev/upatch_manage"; + + if selinux::get_status() == selinux::Status::Enforcing { + kernel::relable_module_file(UPATCH_KMOD_FILE).map_err(|e| { + anyhow!( + "Failed to relable upatch kernel module, {}", + e.to_string().to_lowercase() + ) + })?; + } + let guard = kernel::insert_module_guarded(UPATCH_KMOD_FILE).map_err(|e| { + anyhow!( + "Failed to insert upatch kernel module, {}", + e.to_string().to_lowercase() + ) + })?; + + let driver = Self { + ioctl_dev: File::open(UPATCH_DEV_FILE).map_err(|e| { + anyhow!( + "Failed to open device '{}', {}", + UPATCH_DEV_FILE, + e.to_string().to_lowercase() + ) })?, - }) + target_map: HashMap::new(), + blocked_files: HashSet::from_iter(config.blocked.iter().cloned()), + _guard: guard, + }; + Ok(driver) } } impl UserPatchDriver { - #[inline] - fn read_patch_status(&self, uuid: &Uuid) -> PatchStatus { - self.status_map - .get(uuid) - .copied() - .unwrap_or(PatchStatus::NotApplied) + fn register_patch(&mut self, patch: &UserPatch) { + self.target_map + .entry(patch.target_elf.clone()) + .or_default() + .add_patch(patch); } - #[inline] - fn write_patch_status(&mut self, uuid: &Uuid, value: PatchStatus) { - *self.status_map.entry(*uuid).or_default() = value; - } + fn unregister_patch(&mut self, patch: &UserPatch) { + let target = match self.target_map.get_mut(&patch.target_elf) { + Some(target) => target, + None => return, + }; + target.remove_patch(patch); - fn remove_patch_status(&mut self, uuid: &Uuid) { - self.status_map.remove(uuid); + if !target.is_patched() { + self.target_map.remove(&patch.target_elf); + } } } impl UserPatchDriver { + #[inline] + fn ioctl_fd(&self) -> RawFd { + self.ioctl_dev.as_raw_fd() + } + fn check_consistency(patch: &UserPatch) -> Result<()> { let real_checksum = digest::file(&patch.patch_file)?; debug!("Target checksum: '{}'", patch.checksum); @@ -97,7 +120,7 @@ impl UserPatchDriver { } pub fn check_conflicted_patches(&self, patch: &UserPatch) -> Result<()> { - let conflicted = match self.target_map.read().get(&patch.target_elf) { + let conflicted = match self.target_map.get(&patch.target_elf) { Some(target) => target.get_conflicted_patches(patch).collect(), None => HashSet::new(), }; @@ -105,7 +128,7 @@ impl UserPatchDriver { ensure!(conflicted.is_empty(), { let mut msg = String::new(); writeln!(msg, "Upatch: Patch is conflicted with")?; - for uuid in conflicted.into_iter() { + for uuid in conflicted { writeln!(msg, "* Patch '{}'", uuid)?; } msg.pop(); @@ -115,7 +138,7 @@ impl UserPatchDriver { } pub fn check_overridden_patches(&self, patch: &UserPatch) -> Result<()> { - let overridden = match self.target_map.read().get(&patch.target_elf) { + let overridden = match self.target_map.get(&patch.target_elf) { Some(target) => target.get_overridden_patches(patch).collect(), None => HashSet::new(), }; @@ -123,7 +146,7 @@ impl UserPatchDriver { ensure!(overridden.is_empty(), { let mut msg = String::new(); writeln!(msg, "Upatch: Patch is overridden by")?; - for uuid in overridden.into_iter() { + for uuid in overridden { writeln!(msg, "* Patch '{}'", uuid)?; } msg.pop(); @@ -133,108 +156,6 @@ impl UserPatchDriver { } } -impl UserPatchDriver { - #[inline] - fn parse_process_id(proc_path: &Path) -> Option { - proc_path - .file_name() - .and_then(OsStr::to_str) - .map(str::parse) - .and_then(Result::ok) - } - - fn find_target_process>( - skipped_files: &HashSet, - target_elf: P, - ) -> Result> { - let mut target_pids = HashSet::new(); - let target_path = target_elf.as_ref(); - let target_inode = target_path.metadata()?.st_ino(); - - for proc_path in fs::list_dirs("/proc", fs::TraverseOptions { recursive: false })? { - let pid = match Self::parse_process_id(&proc_path) { - Some(pid) => pid, - None => continue, - }; - let exec_path = match fs::read_link(format!("/proc/{}/exe", pid)) { - Ok(file_path) => file_path, - Err(_) => continue, - }; - if skipped_files.contains(&exec_path) { - continue; - } - // Try to match binary path - if exec_path == target_path { - target_pids.insert(pid); - continue; - } - // Try to match mapped files - let map_files = fs::list_symlinks( - format!("/proc/{}/map_files", pid), - fs::TraverseOptions { recursive: false }, - )?; - for mapped_file in map_files { - if let Ok(mapped_inode) = mapped_file - .read_link() - .and_then(|file_path| Ok(file_path.metadata()?.st_ino())) - { - if mapped_inode == target_inode { - target_pids.insert(pid); - break; - } - }; - } - } - - Ok(target_pids) - } - - fn patch_new_process( - target_map: &RwLock>, - skipped_files: &HashSet, - target_elf: &Path, - ) { - let process_list = match Self::find_target_process(skipped_files, target_elf) { - Ok(pids) => pids, - Err(_) => return, - }; - - let mut target_map = target_map.write(); - let patch_target = match target_map.get_mut(target_elf) { - Some(target) => target, - None => return, - }; - patch_target.clean_dead_process(&process_list); - - let all_patches = patch_target.all_patches().collect::>(); - let need_actived = patch_target.need_actived(&process_list); - - for (uuid, patch_file) in all_patches { - if !need_actived.is_empty() { - debug!( - "Upatch: Activating patch '{}' ({}) for process {:?}", - uuid, - target_elf.display(), - need_actived, - ); - } - for &pid in &need_actived { - match sys::active_patch(&uuid, pid, target_elf, &patch_file) { - Ok(_) => patch_target.add_process(pid), - Err(e) => { - warn!( - "Upatch: Failed to active patch '{}' for process {}, {}", - uuid, - pid, - e.to_string().to_lowercase(), - ); - } - } - } - } - } -} - impl UserPatchDriver { pub fn check_patch(&self, patch: &UserPatch) -> Result<()> { Self::check_consistency(patch)?; @@ -242,134 +163,58 @@ impl UserPatchDriver { } pub fn get_patch_status(&self, patch: &UserPatch) -> Result { - Ok(self.read_patch_status(&patch.uuid)) + sys::get_patch_status(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + anyhow!( + "Kpatch: Failed to get patch status, {}", + e.to_string().to_lowercase() + ) + }) } pub fn load_patch(&mut self, patch: &UserPatch) -> Result<()> { - self.write_patch_status(&patch.uuid, PatchStatus::Deactived); - Ok(()) + ensure!( + !self.blocked_files.contains(&patch.target_elf), + "Upatch: Patch target '{}' is blocked", + patch.target_elf.display(), + ); + sys::load_patch(self.ioctl_fd(), &patch.patch_file, &patch.target_elf).map_err(|e| { + anyhow!( + "Upatch: Failed to load patch, {}", + e.to_string().to_lowercase() + ) + }) } pub fn remove_patch(&mut self, patch: &UserPatch) -> Result<()> { - self.remove_patch_status(&patch.uuid); - Ok(()) + sys::remove_patch(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + anyhow!( + "Upatch: Failed to remove patch, {}", + e.to_string().to_lowercase() + ) + }) } pub fn active_patch(&mut self, patch: &UserPatch) -> Result<()> { - let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; - - let mut target_map = self.target_map.write(); - let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); - patch_target.clean_dead_process(&process_list); - - // If target is not patched before, start watching it - let start_watch = !patch_target.is_patched(); - - // Active patch - let need_actived = patch_target.need_actived(&process_list); - - let mut results = Vec::new(); - for pid in need_actived { - let result = sys::active_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); - results.push((pid, result)); - } - - // Return error if all process fails - if !results.is_empty() && results.iter().all(|(_, result)| result.is_err()) { - let mut msg = String::new(); - writeln!(msg, "Upatch: Failed to active patch")?; - for (pid, result) in &results { - if let Err(e) = result { - writeln!(msg, "* Process {}: {}", pid, e)?; - } - } - msg.pop(); - bail!(msg); - } - - // Process results - for (pid, result) in results { - match result { - Ok(_) => patch_target.add_process(pid), - Err(e) => { - warn!( - "Upatch: Failed to active patch '{}' for process {}, {}", - patch.uuid, - pid, - e.to_string().to_lowercase(), - ); - } - } - } - patch_target.add_patch(patch); + sys::active_patch(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + anyhow!( + "Upatch: Failed to active patch, {}", + e.to_string().to_lowercase() + ) + })?; + self.register_patch(patch); - // Drop the lock - drop(target_map); - - if start_watch { - self.monitor.watch_file(&patch.target_elf)?; - } - - self.write_patch_status(&patch.uuid, PatchStatus::Actived); Ok(()) } pub fn deactive_patch(&mut self, patch: &UserPatch) -> Result<()> { - let process_list = Self::find_target_process(&self.skipped_files, &patch.target_elf)?; - - let mut target_map = self.target_map.write(); - let patch_target = target_map.entry(patch.target_elf.clone()).or_default(); - patch_target.clean_dead_process(&process_list); - - // Deactive patch - let need_deactive = patch_target.need_deactived(&process_list); - - let mut results = Vec::new(); - for pid in need_deactive { - let result = - sys::deactive_patch(&patch.uuid, pid, &patch.target_elf, &patch.patch_file); - results.push((pid, result)); - } - - // Return error if all process fails - if !results.is_empty() && results.iter().any(|(_, result)| result.is_err()) { - let mut msg = String::new(); - writeln!(msg, "Upatch: Failed to deactive patch")?; - for (pid, result) in &results { - if let Err(e) = result { - writeln!(msg, "* Process {}: {}", pid, e)?; - } - } - msg.pop(); - bail!(msg); - } - - // Process results - for (pid, result) in results { - match result { - Ok(_) => patch_target.remove_process(pid), - Err(e) => { - warn!( - "Upatch: Failed to deactive patch '{}' for process {}, {}", - patch.uuid, - pid, - e.to_string().to_lowercase(), - ); - } - } - } - patch_target.remove_patch(patch); - - // If target is no longer has patch, stop watching it - let stop_watch = !patch_target.is_patched(); - - drop(target_map); - - if stop_watch { - self.monitor.ignore_file(&patch.target_elf)?; - } + sys::deactive_patch(self.ioctl_fd(), &patch.patch_file).map_err(|e| { + anyhow!( + "Upatch: Failed to deactive patch, {}", + e.to_string().to_lowercase() + ) + })?; + self.unregister_patch(patch); - self.write_patch_status(&patch.uuid, PatchStatus::Deactived); Ok(()) } } diff --git a/syscared/src/patch/driver/upatch/monitor.rs b/syscared/src/patch/driver/upatch/monitor.rs deleted file mode 100644 index 150d1a35..00000000 --- a/syscared/src/patch/driver/upatch/monitor.rs +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: Mulan PSL v2 -/* - * Copyright (c) 2024 Huawei Technologies Co., Ltd. - * syscared is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, - * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, - * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -use std::{ - ops::DerefMut, - path::{Path, PathBuf}, - sync::Arc, - thread, - time::Duration, -}; - -use anyhow::{anyhow, bail, Context, Result}; -use indexmap::IndexMap; -use inotify::{Inotify, WatchDescriptor, WatchMask}; -use log::info; -use parking_lot::{Mutex, RwLock}; -use syscare_common::ffi::OsStrExt; - -const MONITOR_THREAD_NAME: &str = "upatch_monitor"; -const MONITOR_CHECK_PERIOD: u64 = 100; -const MONITOR_EVENT_BUFFER_CAPACITY: usize = 16 * 64; // inotify event size: 16 - -pub(super) struct UserPatchMonitor { - inotify: Arc>>, - watch_wd_map: Arc>>, - watch_file_map: Arc>>, - monitor_thread: Option>, -} - -impl UserPatchMonitor { - pub fn new(callback: F) -> Result - where - F: Fn(Vec<&Path>) + Send + Sync + 'static, - { - let inotify = Arc::new(Mutex::new(Some( - Inotify::init().context("Failed to initialize inotify")?, - ))); - let watch_wd_map = Arc::new(Mutex::new(IndexMap::new())); - let watch_file_map = Arc::new(RwLock::new(IndexMap::new())); - let monitor_thread = MonitorThread { - inotify: inotify.clone(), - watch_file_map: watch_file_map.clone(), - callback, - } - .run()?; - - Ok(Self { - inotify, - watch_wd_map, - watch_file_map, - monitor_thread: Some(monitor_thread), - }) - } -} - -impl UserPatchMonitor { - pub fn watch_file>(&self, file_path: P) -> Result<()> { - let watch_file = file_path.as_ref(); - if self.watch_wd_map.lock().contains_key(watch_file) { - return Ok(()); - } - - match self.inotify.lock().as_mut() { - Some(inotify) => { - let wd = inotify - .add_watch(watch_file, WatchMask::OPEN) - .map_err(|e| { - anyhow!( - "Failed to watch file '{}', {}", - watch_file.display(), - e.to_string().to_lowercase() - ) - })?; - - self.watch_file_map - .write() - .insert(wd.clone(), watch_file.to_owned()); - self.watch_wd_map.lock().insert(watch_file.to_owned(), wd); - info!("Start watching file '{}'", watch_file.display()); - } - None => bail!("Inotify does not exist"), - } - - Ok(()) - } - - pub fn ignore_file>(&self, file_path: P) -> Result<()> { - let ignore_file = file_path.as_ref(); - - if let Some(wd) = self.watch_wd_map.lock().remove(ignore_file) { - match self.inotify.lock().as_mut() { - Some(inotify) => { - self.watch_file_map.write().remove(&wd); - - inotify.rm_watch(wd).map_err(|e| { - anyhow!( - "Failed to stop watch file '{}', {}", - ignore_file.display(), - e.to_string().to_lowercase() - ) - })?; - info!("Stop watching file '{}'", ignore_file.display()); - } - None => bail!("Inotify does not exist"), - } - } - - Ok(()) - } -} - -struct MonitorThread { - inotify: Arc>>, - watch_file_map: Arc>>, - callback: F, -} - -impl MonitorThread -where - F: Fn(Vec<&Path>) + Send + Sync + 'static, -{ - fn run(self) -> Result> { - thread::Builder::new() - .name(MONITOR_THREAD_NAME.to_string()) - .spawn(move || self.thread_main()) - .map_err(|e| { - anyhow!( - "Failed to create thread '{}', {}", - MONITOR_THREAD_NAME, - e.to_string().to_lowercase() - ) - }) - } - - #[inline] - fn filter_blacklist_path(path: &Path) -> bool { - const BLACKLIST_KEYWORDS: [&str; 2] = ["syscare", "upatch"]; - - for keyword in BLACKLIST_KEYWORDS { - if path.contains(keyword) { - return false; - } - } - true - } - - fn thread_main(self) { - while let Some(inotify) = self.inotify.lock().as_mut() { - let mut buffer = [0; MONITOR_EVENT_BUFFER_CAPACITY]; - - if let Ok(events) = inotify.read_events(&mut buffer) { - let watch_file_map = self.watch_file_map.read(); - let path_list = events - .filter_map(|event| watch_file_map.get(&event.wd)) - .filter(|path| Self::filter_blacklist_path(path)) - .map(|path| path.as_ref()) - .collect::>(); - (self.callback)(path_list) - } - - thread::park_timeout(Duration::from_millis(MONITOR_CHECK_PERIOD)) - } - } -} - -impl Drop for UserPatchMonitor { - fn drop(&mut self) { - if let Some(inotify) = self.inotify.lock().deref_mut().take() { - inotify.close().ok(); - } - if let Some(thread) = self.monitor_thread.take() { - thread.join().ok(); - } - } -} diff --git a/syscared/src/patch/driver/upatch/sys.rs b/syscared/src/patch/driver/upatch/sys.rs index 85188401..20d1086b 100644 --- a/syscared/src/patch/driver/upatch/sys.rs +++ b/syscared/src/patch/driver/upatch/sys.rs @@ -1,77 +1,160 @@ -use std::path::Path; +use std::{ + ffi::CString, + os::unix::{ + ffi::OsStrExt, + io::{AsRawFd, RawFd}, + }, + path::Path, +}; -use anyhow::{bail, Result}; -use log::{debug, Level}; -use uuid::Uuid; +use log::debug; +use nix::errno::Errno; -use syscare_common::process::Command; +use syscare_abi::PatchStatus; -const UPATCH_MANAGE_BIN: &str = "upatch-manage"; +mod ffi { + use nix::ioctl_write_ptr; + use std::ffi::c_char; -pub fn active_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> + const UPATCH_MAGIC: u8 = 0xE5; + + const UPATCH_LOAD: u8 = 0x01; + const UPATCH_ACTIVE: u8 = 0x02; + const UPATCH_DEACTIVE: u8 = 0x03; + const UPATCH_REMOVE: u8 = 0x04; + const UPATCH_STATUS: u8 = 0x05; + + pub const UPATCH_STATUS_NOT_APPLIED: i32 = 1; + pub const UPATCH_STATUS_DEACTIVED: i32 = 2; + pub const UPATCH_STATUS_ACTIVED: i32 = 3; + + #[repr(C)] + pub struct PatchLoadRequest { + pub patch_file: *const c_char, + pub target_elf: *const c_char, + } + + ioctl_write_ptr!( + ioctl_load_patch, + UPATCH_MAGIC, + UPATCH_LOAD, + PatchLoadRequest + ); + ioctl_write_ptr!(ioctl_active_patch, UPATCH_MAGIC, UPATCH_ACTIVE, c_char); + ioctl_write_ptr!(ioctl_deactive_patch, UPATCH_MAGIC, UPATCH_DEACTIVE, c_char); + ioctl_write_ptr!(ioctl_remove_patch, UPATCH_MAGIC, UPATCH_REMOVE, c_char); + ioctl_write_ptr!(ioctl_get_patch_status, UPATCH_MAGIC, UPATCH_STATUS, c_char); +} + +pub fn get_patch_status

(fd: RawFd, patch_file: P) -> std::io::Result where P: AsRef, - Q: AsRef, { - let target_elf = target_elf.as_ref(); let patch_file = patch_file.as_ref(); + debug!( + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", + fd, + stringify!(UPATCH_STATUS), + patch_file.display(), + ); + let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + let status_code = unsafe { ffi::ioctl_get_patch_status(fd, patch_cstr.as_ptr())? }; + let status = match status_code { + ffi::UPATCH_STATUS_NOT_APPLIED => PatchStatus::NotApplied, + ffi::UPATCH_STATUS_DEACTIVED => PatchStatus::Deactived, + ffi::UPATCH_STATUS_ACTIVED => PatchStatus::Actived, + _ => return Err(std::io::Error::from(Errno::EINVAL)), + }; + + Ok(status) +} + +pub fn load_patch(ioctl_dev: RawFd, patch_file: Q, target_elf: P) -> std::io::Result<()> +where + P: AsRef, + Q: AsRef, +{ + let ioctl_fd = ioctl_dev.as_raw_fd(); + let patch_file = patch_file.as_ref(); + let target_elf = target_elf.as_ref(); debug!( - "Upatch: Patching '{}' to '{}' (pid: {})...", + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {{ {}, {} }} }}", + ioctl_fd, + stringify!(UPATCH_LOAD), patch_file.display(), target_elf.display(), - pid, ); - let exit_code = Command::new(UPATCH_MANAGE_BIN) - .arg("patch") - .arg("--uuid") - .arg(uuid.to_string()) - .arg("--pid") - .arg(pid.to_string()) - .arg("--binary") - .arg(target_elf) - .arg("--upatch") - .arg(patch_file) - .stdout(Level::Error) - .run_with_output()? - .exit_code(); - - match exit_code { - 0 => Ok(()), - _ => bail!(std::io::Error::from_raw_os_error(exit_code)), + + let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + let target_cstr = CString::new(target_elf.as_os_str().as_bytes())?; + let request = ffi::PatchLoadRequest { + patch_file: patch_cstr.as_ptr(), + target_elf: target_cstr.as_ptr(), + }; + unsafe { + ffi::ioctl_load_patch(ioctl_fd, &request)?; } + + Ok(()) } -pub fn deactive_patch(uuid: &Uuid, pid: i32, target_elf: P, patch_file: Q) -> Result<()> +pub fn remove_patch

(fd: RawFd, patch_file: P) -> std::io::Result<()> where P: AsRef, - Q: AsRef, { - let target_elf = target_elf.as_ref(); let patch_file = patch_file.as_ref(); + debug!( + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", + fd, + stringify!(UPATCH_REMOVE), + patch_file.display(), + ); + + let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + unsafe { + ffi::ioctl_remove_patch(fd, patch_cstr.as_ptr())?; + } + + Ok(()) +} +pub fn active_patch

(fd: RawFd, patch_file: P) -> std::io::Result<()> +where + P: AsRef, +{ + let patch_file = patch_file.as_ref(); debug!( - "Upatch: Unpatching '{}' from '{}' (pid: {})...", + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", + fd, + stringify!(UPATCH_ACTIVE), + patch_file.display(), + ); + + let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + unsafe { + ffi::ioctl_active_patch(fd, patch_cstr.as_ptr())?; + } + + Ok(()) +} + +pub fn deactive_patch

(fd: RawFd, patch_file: P) -> std::io::Result<()> +where + P: AsRef, +{ + let patch_file = patch_file.as_ref(); + debug!( + "Upatch: Ioctl {{ fd: {}, cmd: {}, data: {} }}", + fd, + stringify!(UPATCH_DEACTIVE), patch_file.display(), - target_elf.display(), - pid, ); - let exit_code = Command::new(UPATCH_MANAGE_BIN) - .arg("unpatch") - .arg("--uuid") - .arg(uuid.to_string()) - .arg("--pid") - .arg(pid.to_string()) - .arg("--binary") - .arg(target_elf) - .arg("--upatch") - .arg(patch_file) - .stdout(Level::Error) - .run_with_output()? - .exit_code(); - - match exit_code { - 0 => Ok(()), - _ => bail!(std::io::Error::from_raw_os_error(exit_code)), + + let patch_cstr = CString::new(patch_file.as_os_str().as_bytes())?; + unsafe { + ffi::ioctl_deactive_patch(fd, patch_cstr.as_ptr())?; } + + Ok(()) } diff --git a/syscared/src/patch/driver/upatch/target.rs b/syscared/src/patch/driver/upatch/target.rs index e0e9f882..11d3b693 100644 --- a/syscared/src/patch/driver/upatch/target.rs +++ b/syscared/src/patch/driver/upatch/target.rs @@ -12,10 +12,7 @@ * See the Mulan PSL v2 for more details. */ -use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, - path::PathBuf, -}; +use std::collections::{hash_map::Entry, HashMap}; use indexmap::IndexSet; use uuid::Uuid; @@ -24,39 +21,9 @@ use crate::patch::entity::UserPatch; #[derive(Debug, Default)] pub struct PatchTarget { - process_list: HashSet, - patch_map: HashMap, // uuid -> patch file collision_map: HashMap>, // function old addr -> patch collision list } -impl PatchTarget { - pub fn add_process(&mut self, pid: i32) { - self.process_list.insert(pid); - } - - pub fn remove_process(&mut self, pid: i32) { - self.process_list.remove(&pid); - } - - pub fn clean_dead_process(&mut self, process_list: &HashSet) { - self.process_list.retain(|pid| process_list.contains(pid)); - } - - pub fn need_actived(&self, process_list: &HashSet) -> HashSet { - process_list - .difference(&self.process_list) - .copied() - .collect() - } - - pub fn need_deactived(&self, process_list: &HashSet) -> HashSet { - process_list - .intersection(&self.process_list) - .copied() - .collect() - } -} - impl PatchTarget { pub fn add_patch(&mut self, patch: &UserPatch) { for function in &patch.functions { @@ -65,7 +32,6 @@ impl PatchTarget { .or_default() .insert(patch.uuid); } - self.patch_map.insert(patch.uuid, patch.patch_file.clone()); } pub fn remove_patch(&mut self, patch: &UserPatch) { @@ -79,19 +45,12 @@ impl PatchTarget { } } } - self.patch_map.remove(&patch.uuid); } pub fn is_patched(&self) -> bool { !self.collision_map.is_empty() } - pub fn all_patches(&self) -> impl Iterator + '_ { - self.patch_map - .iter() - .map(|(uuid, path)| (*uuid, path.to_path_buf())) - } - pub fn get_conflicted_patches<'a>( &'a self, patch: &'a UserPatch, -- Gitee From 5e28a77e48a37dcc99717cad896f9a93df6a4b01 Mon Sep 17 00:00:00 2001 From: renoseven Date: Wed, 11 Jun 2025 21:43:41 +0800 Subject: [PATCH 13/13] upatch-manage: switch to uprobe impl Signed-off-by: renoseven --- upatch-manage/CMakeLists.txt | 37 +- upatch-manage/LICENSE | 339 ------ upatch-manage/Makefile | 35 + upatch-manage/arch/aarch64/insn.c | 130 --- upatch-manage/arch/aarch64/insn.h | 71 -- upatch-manage/arch/aarch64/ptrace.c | 221 ---- upatch-manage/arch/aarch64/relocation.c | 338 ------ upatch-manage/arch/aarch64/resolve.c | 164 --- upatch-manage/arch/arm/patch_load.c | 470 ++++++++ upatch-manage/arch/arm64/insn.c | 113 ++ upatch-manage/arch/arm64/insn.h | 16 + upatch-manage/arch/arm64/patch_load.c | 599 ++++++++++ upatch-manage/arch/patch_load.h | 69 ++ upatch-manage/arch/riscv64/insn.h | 123 -- upatch-manage/arch/riscv64/process.h | 28 - upatch-manage/arch/riscv64/ptrace.c | 219 ---- upatch-manage/arch/riscv64/relocation.c | 246 ---- upatch-manage/arch/riscv64/resolve.c | 154 --- upatch-manage/arch/x86/patch_load.c | 320 +++++ upatch-manage/arch/x86_64/ptrace.c | 199 ---- upatch-manage/arch/x86_64/relocation.c | 145 --- upatch-manage/arch/x86_64/resolve.c | 140 --- upatch-manage/ioctl_dev.c | 265 +++++ upatch-manage/ioctl_dev.h | 40 + upatch-manage/kernel_compat.c | 200 ++++ .../{upatch-patch.h => kernel_compat.h} | 44 +- upatch-manage/list.h | 568 --------- upatch-manage/log.h | 73 -- upatch-manage/main.c | 85 ++ upatch-manage/patch_entity.c | 304 +++++ upatch-manage/patch_entity.h | 120 ++ upatch-manage/patch_load.c | 638 ++++++++++ upatch-manage/patch_load.h | 87 ++ upatch-manage/patch_manage.c | 515 ++++++++ .../{upatch-relocation.h => patch_manage.h} | 31 +- upatch-manage/process_entity.c | 129 ++ .../{upatch-relocation.c => process_entity.h} | 98 +- upatch-manage/symbol_resolve.c | 662 +++++++++++ .../{upatch-resolve.h => symbol_resolve.h} | 48 +- upatch-manage/target_entity.c | 454 +++++++ upatch-manage/target_entity.h | 149 +++ upatch-manage/upatch-common.c | 28 - upatch-manage/upatch-elf.c | 232 ---- upatch-manage/upatch-elf.h | 163 --- upatch-manage/upatch-manage.c | 233 ---- upatch-manage/upatch-patch.c | 1039 ----------------- upatch-manage/upatch-process.c | 918 --------------- upatch-manage/upatch-process.h | 151 --- upatch-manage/upatch-ptrace.c | 298 ----- upatch-manage/upatch-ptrace.h | 92 -- upatch-manage/upatch-resolve.c | 332 ------ upatch-manage/upatch-stack-check.c | 129 -- upatch-manage/upatch-stack-check.h | 17 - upatch-manage/util.c | 132 +++ upatch-manage/{upatch-common.h => util.h} | 96 +- 55 files changed, 5606 insertions(+), 6940 deletions(-) delete mode 100644 upatch-manage/LICENSE create mode 100644 upatch-manage/Makefile delete mode 100644 upatch-manage/arch/aarch64/insn.c delete mode 100644 upatch-manage/arch/aarch64/insn.h delete mode 100644 upatch-manage/arch/aarch64/ptrace.c delete mode 100644 upatch-manage/arch/aarch64/relocation.c delete mode 100644 upatch-manage/arch/aarch64/resolve.c create mode 100644 upatch-manage/arch/arm/patch_load.c create mode 100644 upatch-manage/arch/arm64/insn.c create mode 100644 upatch-manage/arch/arm64/insn.h create mode 100644 upatch-manage/arch/arm64/patch_load.c create mode 100644 upatch-manage/arch/patch_load.h delete mode 100644 upatch-manage/arch/riscv64/insn.h delete mode 100644 upatch-manage/arch/riscv64/process.h delete mode 100644 upatch-manage/arch/riscv64/ptrace.c delete mode 100644 upatch-manage/arch/riscv64/relocation.c delete mode 100644 upatch-manage/arch/riscv64/resolve.c create mode 100644 upatch-manage/arch/x86/patch_load.c delete mode 100644 upatch-manage/arch/x86_64/ptrace.c delete mode 100644 upatch-manage/arch/x86_64/relocation.c delete mode 100644 upatch-manage/arch/x86_64/resolve.c create mode 100644 upatch-manage/ioctl_dev.c create mode 100644 upatch-manage/ioctl_dev.h create mode 100644 upatch-manage/kernel_compat.c rename upatch-manage/{upatch-patch.h => kernel_compat.h} (44%) delete mode 100644 upatch-manage/list.h delete mode 100644 upatch-manage/log.h create mode 100644 upatch-manage/main.c create mode 100644 upatch-manage/patch_entity.c create mode 100644 upatch-manage/patch_entity.h create mode 100644 upatch-manage/patch_load.c create mode 100644 upatch-manage/patch_load.h create mode 100644 upatch-manage/patch_manage.c rename upatch-manage/{upatch-relocation.h => patch_manage.h} (60%) create mode 100644 upatch-manage/process_entity.c rename upatch-manage/{upatch-relocation.c => process_entity.h} (32%) create mode 100644 upatch-manage/symbol_resolve.c rename upatch-manage/{upatch-resolve.h => symbol_resolve.h} (45%) create mode 100644 upatch-manage/target_entity.c create mode 100644 upatch-manage/target_entity.h delete mode 100644 upatch-manage/upatch-common.c delete mode 100644 upatch-manage/upatch-elf.c delete mode 100644 upatch-manage/upatch-elf.h delete mode 100644 upatch-manage/upatch-manage.c delete mode 100644 upatch-manage/upatch-patch.c delete mode 100644 upatch-manage/upatch-process.c delete mode 100644 upatch-manage/upatch-process.h delete mode 100644 upatch-manage/upatch-ptrace.c delete mode 100644 upatch-manage/upatch-ptrace.h delete mode 100644 upatch-manage/upatch-resolve.c delete mode 100644 upatch-manage/upatch-stack-check.c delete mode 100644 upatch-manage/upatch-stack-check.h create mode 100644 upatch-manage/util.c rename upatch-manage/{upatch-common.h => util.h} (36%) diff --git a/upatch-manage/CMakeLists.txt b/upatch-manage/CMakeLists.txt index 1f7ecd04..996b4970 100644 --- a/upatch-manage/CMakeLists.txt +++ b/upatch-manage/CMakeLists.txt @@ -1,28 +1,33 @@ # SPDX-License-Identifier: GPL-2.0 -set(UPATCH_MANAGE "upatch-manage") +set(UPATCH_MANAGE_KMOD "upatch_manage.ko") -set(ARCH_PATH arch/${ARCH}) +if (DEFINED ENABLE_GCOV) + set(ENABLE_GCOV "ENABLE_GCOV=1") +endif() -include_directories( - arch/${ARCH}/ - ./ -) +if (DEFINED KERNEL_VERSION) + set(KERNEL_BUILD_DIR /lib/modules/${KERNEL_VERSION}/build) + set(KMOD_CLEAN_CMD ${CMAKE_MAKE_PROGRAM} clean KERNEL_SRC=${KERNEL_BUILD_DIR} V=1) + set(KMOD_BUILD_CMD ${CMAKE_MAKE_PROGRAM} KERNEL_SRC=${KERNEL_BUILD_DIR} MODULE_VERSION=${BUILD_VERSION} ENABLE_GCOV=${ENABLE_GCOV} V=1) +else() + set(KMOD_CLEAN_CMD ${CMAKE_MAKE_PROGRAM} clean V=1) + set(KMOD_BUILD_CMD ${CMAKE_MAKE_PROGRAM} MODULE_VERSION=${BUILD_VERSION} ENABLE_GCOV=${ENABLE_GCOV} V=1) +endif() -file(GLOB HOST_SRC_FILES - arch/${ARCH}/*.c - *.c +add_custom_target(upatch-manage ALL + COMMAND ${KMOD_CLEAN_CMD} + COMMAND ${KMOD_BUILD_CMD} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) -add_executable(${UPATCH_MANAGE} ${HOST_SRC_FILES}) -target_link_libraries(${UPATCH_MANAGE} elf) - install( - TARGETS - ${UPATCH_MANAGE} + FILES + ${UPATCH_MANAGE_KMOD} PERMISSIONS - OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE + OWNER_WRITE OWNER_READ + GROUP_READ + WORLD_READ DESTINATION ${SYSCARE_LIBEXEC_DIR} ) diff --git a/upatch-manage/LICENSE b/upatch-manage/LICENSE deleted file mode 100644 index d159169d..00000000 --- a/upatch-manage/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/upatch-manage/Makefile b/upatch-manage/Makefile new file mode 100644 index 00000000..69d0c701 --- /dev/null +++ b/upatch-manage/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 + +MODULE_NAME = upatch_manage +ifndef MODULE_VERSION + MODULE_VERSION := dev +endif + +ifeq ($(ENABLE_GCOV), 1) + GCOV_PROFILE := y +endif + +ifndef KERNEL_SRC + KERNEL_SRC := /lib/modules/6.6.0-95.0.0.99.oe2403sp1.x86_64/build +endif +MODULE_SRC := $(shell pwd) + +ccflags-y += -D__KERNEL__ -DMODULE -DMODNAME=\"${MODULE_NAME}\" -DMODVER=\"${MODULE_VERSION}\" +ccflags-y += -D_FORTIFY_SOURCE=2 -DSECUREC_SUPPORT_FORMAT_WARNING=1 +ccflags-y += -O2 -Wall -pipe + +obj-m += ${MODULE_NAME}.o +${MODULE_NAME}-objs := main.o ioctl_dev.o +${MODULE_NAME}-objs += patch_entity.o target_entity.o process_entity.o +${MODULE_NAME}-objs += patch_load.o arch/$(ARCH)/patch_load.o symbol_resolve.o +${MODULE_NAME}-objs += patch_manage.o kernel_compat.o util.o + +ifeq ($(ARCH), arm64) + ${MODULE_NAME}-objs += arch/$(ARCH)/insn.o +endif + +all: + make -C $(KERNEL_SRC) M=$(MODULE_SRC) modules -j + +clean: + make -C $(KERNEL_SRC) M=$(MODULE_SRC) clean diff --git a/upatch-manage/arch/aarch64/insn.c b/upatch-manage/arch/aarch64/insn.c deleted file mode 100644 index 9a775a5a..00000000 --- a/upatch-manage/arch/aarch64/insn.c +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2013 Huawei Ltd. - * Author: Jiang Liu - * - * Copyright (C) 2014-2016 Zi Shen Lim - */ - -#include - -#include "insn.h" - -static int aarch64_get_imm_shift_mask(enum aarch64_insn_imm_type type, - u32 *maskp, int *shiftp) -{ - u32 mask; - int shift; - - switch (type) { - case AARCH64_INSN_IMM_26: - mask = BIT(26) - 1; - shift = 0; - break; - case AARCH64_INSN_IMM_19: - mask = BIT(19) - 1; - shift = 5; - break; - case AARCH64_INSN_IMM_16: - mask = BIT(16) - 1; - shift = 5; - break; - case AARCH64_INSN_IMM_14: - mask = BIT(14) - 1; - shift = 5; - break; - case AARCH64_INSN_IMM_12: - mask = BIT(12) - 1; - shift = 10; - break; - case AARCH64_INSN_IMM_9: - mask = BIT(9) - 1; - shift = 12; - break; - case AARCH64_INSN_IMM_7: - mask = BIT(7) - 1; - shift = 15; - break; - case AARCH64_INSN_IMM_6: - case AARCH64_INSN_IMM_S: - mask = BIT(6) - 1; - shift = 10; - break; - case AARCH64_INSN_IMM_R: - mask = BIT(6) - 1; - shift = 16; - break; - case AARCH64_INSN_IMM_N: - mask = 1; - shift = 22; - break; - default: - return -EINVAL; - } - - *maskp = mask; - *shiftp = shift; - - return 0; -} - -u32 aarch64_insn_encode_immediate(enum aarch64_insn_imm_type type, - u32 insn, u64 imm) -{ - u32 immlo; - u32 immhi; - u32 mask; - - int shift; - - if (insn == AARCH64_BREAK_FAULT) { - return AARCH64_BREAK_FAULT; - } - - switch (type) { - case AARCH64_INSN_IMM_ADR: - shift = 0; - immlo = (imm & ADR_IMM_LOMASK) << ADR_IMM_LOSHIFT; - imm >>= ADR_IMM_HILOSPLIT; - immhi = (imm & ADR_IMM_HIMASK) << ADR_IMM_HISHIFT; - imm = immlo | immhi; - mask = ((ADR_IMM_LOMASK << ADR_IMM_LOSHIFT) | - (ADR_IMM_HIMASK << ADR_IMM_HISHIFT)); - break; - default: - if (aarch64_get_imm_shift_mask(type, &mask, &shift) < 0) { - log_error("upatch: unknown immediate encoding %d\n", type); - return AARCH64_BREAK_FAULT; - } - } - - /* Update the immediate field. */ - insn &= ~(mask << shift); - insn |= (u32)(imm & mask) << shift; - - return insn; -} - -s64 extract_insn_imm(s64 sval, int len, int lsb) -{ - s64 imm; - s64 imm_mask; - - imm = sval >> lsb; - imm_mask = (s64)((BIT(lsb + len) - 1) >> lsb); - imm = imm & imm_mask; - - log_debug("imm: insn=0x%lx, insn[%d:%d]=0x%lx\n", - sval, (len + lsb - 1), lsb, imm); - return imm; -} - -s32 insert_insn_imm(enum aarch64_insn_imm_type imm_type, void *place, u64 imm) -{ - u32 insn = le32_to_cpu(*(__le32 *)place); - u32 new_insn = aarch64_insn_encode_immediate(imm_type, insn, imm); - - log_debug("insn: insn=0x%x, imm_type=%d, imm=0x%lx, new_insn=0x%x\n", - insn, imm_type, imm, new_insn); - return (s32)new_insn; -} diff --git a/upatch-manage/arch/aarch64/insn.h b/upatch-manage/arch/aarch64/insn.h deleted file mode 100644 index 8efc81b1..00000000 --- a/upatch-manage/arch/aarch64/insn.h +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2013 Huawei Ltd. - * Author: Jiang Liu - * - * Copyright (C) 2014-2016 Zi Shen Lim - */ - -#ifndef _ARCH_AARCH64_INSN_H -#define _ARCH_AARCH64_INSN_H - -#include -#include - -#include "upatch-relocation.h" - -enum aarch64_insn_imm_type { - AARCH64_INSN_IMM_ADR, - AARCH64_INSN_IMM_26, - AARCH64_INSN_IMM_19, - AARCH64_INSN_IMM_16, - AARCH64_INSN_IMM_14, - AARCH64_INSN_IMM_12, - AARCH64_INSN_IMM_9, - AARCH64_INSN_IMM_7, - AARCH64_INSN_IMM_6, - AARCH64_INSN_IMM_S, - AARCH64_INSN_IMM_R, - AARCH64_INSN_IMM_N, - AARCH64_INSN_IMM_MAX -}; - -#define SZ_2M 0x00200000 -#define ADR_IMM_HILOSPLIT 2 -#define ADR_IMM_SIZE SZ_2M -#define ADR_IMM_LOMASK ((1 << ADR_IMM_HILOSPLIT) - 1) -#define ADR_IMM_HIMASK ((ADR_IMM_SIZE >> ADR_IMM_HILOSPLIT) - 1) -#define ADR_IMM_LOSHIFT 29 -#define ADR_IMM_HISHIFT 5 - -#define FAULT_BRK_IMM 0x100 - -/* - * BRK instruction encoding - * The #imm16 value should be placed at bits[20:5] within BRK ins - */ -#define AARCH64_BREAK_MON 0xd4200000 - -/* - * BRK instruction for provoking a fault on purpose - * Unlike kgdb, #imm16 value with unallocated handler is used for faulting. - */ -#define AARCH64_BREAK_FAULT (AARCH64_BREAK_MON | (FAULT_BRK_IMM << 5)) - -#if BYTE_ORDER == LITTLE_ENDIAN -#define le32_to_cpu(val) (val) -#define cpu_to_le32(val) (val) -#endif -#if BYTE_ORDER == BIG_ENDIAN -#define le32_to_cpu(val) bswap_32(val) -#define cpu_to_le32(val) bswap_32(val) -#endif - -u32 aarch64_insn_encode_immediate(enum aarch64_insn_imm_type type, - u32 insn, u64 imm); - -s64 extract_insn_imm(s64 sval, int len, int lsb); - -s32 insert_insn_imm(enum aarch64_insn_imm_type imm_type, void *place, u64 imm); - -#endif /* _ARCH_AARCH64_INSN_H */ diff --git a/upatch-manage/arch/aarch64/ptrace.c b/upatch-manage/arch/aarch64/ptrace.c deleted file mode 100644 index 131e0dd3..00000000 --- a/upatch-manage/arch/aarch64/ptrace.c +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include - -#include "insn.h" -#include "upatch-ptrace.h" - -#define ORIGIN_INSN_LEN 16 - -#define UPATCH_INSN_LEN 8 -#define UPATCH_ADDR_LEN 8 - -int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc) -{ - struct iovec regs_iov; - struct user_regs_struct regs; - - regs_iov.iov_base = ®s; - regs_iov.iov_len = sizeof(regs); - - if (ptrace(PTRACE_GETREGSET, pid, - (void *)NT_PRSTATUS, (void *)®s_iov) < 0) { - log_error("Cannot get regs from %d\n", pid); - return -1; - } - *sp = (unsigned long)regs.sp; - *pc = (unsigned long)regs.pc; - - return 0; -} -long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *pctx, int nr, - unsigned long arg1, unsigned long arg2, - unsigned long arg3, unsigned long arg4, - unsigned long arg5, unsigned long arg6, - unsigned long *res) -{ - struct user_regs_struct regs; - unsigned char syscall[] = { - 0x01, 0x00, 0x00, 0xd4, // 0xd4000001 svc #0 = syscall - 0xa0, 0x00, 0x20, 0xd4, // 0xd42000a0 brk #5 = int3 - }; - long ret; - - log_debug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); - regs.regs[8] = (unsigned long long)nr; - regs.regs[0] = arg1; - regs.regs[1] = arg2; - regs.regs[2] = arg3; - regs.regs[3] = arg4; - regs.regs[4] = arg5; - regs.regs[5] = arg6; - - ret = upatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); - if (ret == 0) { - *res = regs.regs[0]; - } - - return ret; -} - -long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, - const unsigned char *code, size_t codelen, - struct user_regs_struct *pregs, - int (*func)(struct upatch_ptrace_ctx *pctx, const void *data), - const void *data) -{ - long ret; - - struct user_regs_struct orig_regs; - struct user_regs_struct regs; - - struct iovec orig_regs_iov; - struct iovec regs_iov; - - struct upatch_process *proc = pctx->proc; - unsigned long libc_base = proc->libc_base; - - unsigned char *orig_code = (unsigned char *)malloc( - sizeof(*orig_code) * codelen); - if (orig_code == NULL) { - log_error("Malloc orig_code failed\n"); - return -1; - } - - orig_regs_iov.iov_base = &orig_regs; - orig_regs_iov.iov_len = sizeof(orig_regs); - regs_iov.iov_base = ®s; - regs_iov.iov_len = sizeof(regs); - - ret = ptrace(PTRACE_GETREGSET, pctx->pid, (void *)NT_PRSTATUS, - (void *)&orig_regs_iov); - if (ret < 0) { - log_error("can't get regs - %d\n", pctx->pid); - free(orig_code); - return -1; - } - ret = upatch_process_mem_read(proc, libc_base, - (unsigned long *)orig_code, codelen); - if (ret < 0) { - log_error("can't peek original code - %d\n", pctx->pid); - free(orig_code); - return -1; - } - ret = upatch_process_mem_write(proc, code, - libc_base, codelen); - if (ret < 0) { - log_error("can't poke syscall code - %d\n", pctx->pid); - goto poke_back; - } - - regs = orig_regs; - regs.pc = libc_base; - copy_regs(®s, pregs); - - ret = ptrace(PTRACE_SETREGSET, pctx->pid, (void *)NT_PRSTATUS, - (void *)®s_iov); - if (ret < 0) { - log_error("can't set regs - %d\n", pctx->pid); - goto poke_back; - } - - ret = func(pctx, data); - if (ret < 0) { - log_error("failed call to func\n"); - goto poke_back; - } - - ret = ptrace(PTRACE_GETREGSET, pctx->pid, (void *)NT_PRSTATUS, - (void *)®s_iov); - if (ret < 0) { - log_error("can't get updated regs - %d\n", pctx->pid); - goto poke_back; - } - - ret = ptrace(PTRACE_SETREGSET, pctx->pid, (void *)NT_PRSTATUS, - (void *)&orig_regs_iov); - if (ret < 0) { - log_error("can't restore regs - %d\n", pctx->pid); - goto poke_back; - } - - *pregs = regs; - -poke_back: - upatch_process_mem_write(proc, (unsigned long *)orig_code, - libc_base, codelen); - - free(orig_code); - return ret; -} - -void copy_regs(struct user_regs_struct *dst, struct user_regs_struct *src) -{ -#define COPY_REG(x) dst->x = src->x - COPY_REG(regs[0]); - COPY_REG(regs[1]); - COPY_REG(regs[2]); - COPY_REG(regs[3]); - COPY_REG(regs[4]); - COPY_REG(regs[5]); - COPY_REG(regs[8]); - COPY_REG(regs[29]); - - COPY_REG(regs[9]); - COPY_REG(regs[10]); - COPY_REG(regs[11]); - COPY_REG(regs[12]); - COPY_REG(regs[13]); - COPY_REG(regs[14]); - COPY_REG(regs[15]); - COPY_REG(regs[16]); - COPY_REG(regs[17]); - COPY_REG(regs[18]); - COPY_REG(regs[19]); - COPY_REG(regs[20]); -#undef COPY_REG -} - -size_t get_origin_insn_len(void) -{ - return ORIGIN_INSN_LEN; -} - -size_t get_upatch_insn_len(void) -{ - return UPATCH_INSN_LEN; -} - -size_t get_upatch_addr_len(void) -{ - return UPATCH_ADDR_LEN; -} - -// for long jumper -unsigned long get_new_insn(void) -{ - unsigned int insn0 = 0x58000051; // ldr x17, #8 - unsigned int insn4 = 0xd61f0220; // br x17 - return (unsigned long)(insn0 | ((unsigned long)insn4 << 32)); -} diff --git a/upatch-manage/arch/aarch64/relocation.c b/upatch-manage/arch/aarch64/relocation.c deleted file mode 100644 index 7dd82ebd..00000000 --- a/upatch-manage/arch/aarch64/relocation.c +++ /dev/null @@ -1,338 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - -#include "insn.h" -#include "upatch-relocation.h" -#include "upatch-resolve.h" - -#define TCB_SIZE (2 * sizeof(void *)) - -enum aarch64_reloc_op { - RELOC_OP_NONE, - RELOC_OP_ABS, - RELOC_OP_PREL, - RELOC_OP_PAGE, -}; - -static inline s64 calc_reloc(enum aarch64_reloc_op op, void *place, u64 val) -{ - s64 sval; - switch (op) { - case RELOC_OP_ABS: - // S + A - sval = (s64)val; - break; - case RELOC_OP_PREL: - // S + A - P - sval = (s64)(val - (u64)place); - break; - case RELOC_OP_PAGE: - // Page(S + A) - Page(P) - sval = (s64)((val & ~(u64)0xfff) - ((u64)place & ~(u64)0xfff)); - break; - default: - log_error("upatch: unknown relocation operation %d\n", op); - break; - } - - log_debug("reloc: S+A=0x%lx, P=0x%lx, X=0x%lx\n", val, (u64)place, sval); - return sval; -} - -int apply_relocate_add(struct upatch_elf *uelf, unsigned int symindex, - unsigned int relsec) -{ - unsigned int i; - GElf_Sym *sym; - char const *sym_name; - void *loc; - void *uloc; - u64 val; - u64 got; - s64 result = 0; - GElf_Shdr *shdrs = (void *)uelf->info.shdrs; - GElf_Rela *rel = (void *)shdrs[relsec].sh_addr; - - for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { - /* loc corresponds to P in the kernel space */ - loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addr + rel[i].r_offset; - - /* uloc corresponds P in user space */ - uloc = (void *)shdrs[shdrs[relsec].sh_info].sh_addralign + - rel[i].r_offset; - - /* sym is the ELF symbol we're referring to */ - sym = (GElf_Sym *)shdrs[symindex].sh_addr + GELF_R_SYM(rel[i].r_info); - if (GELF_ST_TYPE(sym[i].st_info) == STT_SECTION && - sym->st_shndx < uelf->info.hdr->e_shnum) { - sym_name = uelf->info.shstrtab + shdrs[sym->st_shndx].sh_name; - } else { - sym_name = uelf->strtab + sym->st_name; - } - - /* val corresponds to (S + A) */ - val = (unsigned long)sym->st_value + (unsigned long)rel[i].r_addend; - log_debug( - "\nsymbol='%s', k_addr=0x%lx, u_addr=0x%lx, " - "r_offset=0x%lx, st_value=0x%lx, r_addend=0x%lx\n", - sym_name, shdrs[shdrs[relsec].sh_info].sh_addr, - shdrs[shdrs[relsec].sh_info].sh_addralign, - rel[i].r_offset, sym->st_value, rel[i].r_addend); - - /* Perform the static relocation. */ - switch (GELF_R_TYPE(rel[i].r_info)) { - case R_AARCH64_NONE: - break; - /* Data relocations. */ - case R_AARCH64_ABS64: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - *(s64 *)loc = result; - break; - case R_AARCH64_ABS32: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - if (result < -(s64)BIT(31) || result >= (s64)BIT(32)) { - goto overflow; - } - *(s32 *)loc = (s32)result; - break; - case R_AARCH64_ABS16: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - if (result < -(s64)BIT(15) || result >= (s64)BIT(16)) { - goto overflow; - } - *(s16 *)loc = (s16)result; - break; - case R_AARCH64_PREL64: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - *(s64 *)loc = result; - break; - case R_AARCH64_PREL32: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - if (result < -(s64)BIT(31) || result >= (s64)BIT(32)) { - goto overflow; - } - *(s32 *)loc = (s32)result; - break; - case R_AARCH64_PREL16: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - if (result < -(s64)BIT(15) || result >= (s64)BIT(16)) { - goto overflow; - } - *(s16 *)loc = (s16)result; - break; - /* Immediate instruction relocations. */ - case R_AARCH64_LD_PREL_LO19: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - if (result < -(s64)BIT(20) || result >= (s64)BIT(20)) { - goto overflow; - } - result = extract_insn_imm(result, 19, 2); - result = insert_insn_imm(AARCH64_INSN_IMM_19, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_ADR_PREL_LO21: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - if (result < -(s64)BIT(20) || result >= (s64)BIT(20)) { - goto overflow; - } - result = extract_insn_imm(result, 21, 0); - result = insert_insn_imm(AARCH64_INSN_IMM_ADR, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_ADR_PREL_PG_HI21: - result = calc_reloc(RELOC_OP_PAGE, uloc, val); - if (result < -(s64)BIT(32) || result >= (s64)BIT(32)) { - goto overflow; - } - result = extract_insn_imm(result, 21, 12); - result = insert_insn_imm(AARCH64_INSN_IMM_ADR, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_ADR_PREL_PG_HI21_NC: - result = calc_reloc(RELOC_OP_PAGE, uloc, val); - result = extract_insn_imm(result, 21, 12); - result = insert_insn_imm(AARCH64_INSN_IMM_ADR, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_ADD_ABS_LO12_NC: - case R_AARCH64_LDST8_ABS_LO12_NC: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - result = extract_insn_imm(result, 12, 0); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_LDST16_ABS_LO12_NC: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - result = extract_insn_imm(result, 11, 1); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_LDST32_ABS_LO12_NC: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - result = extract_insn_imm(result, 10, 2); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_LDST64_ABS_LO12_NC: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - result = extract_insn_imm(result, 9, 3); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_LDST128_ABS_LO12_NC: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - result = extract_insn_imm(result, 8, 4); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TSTBR14: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - if (result < -(s64)BIT(15) || result >= (s64)BIT(15)) { - goto overflow; - } - result = extract_insn_imm(result, 14, 2); - result = insert_insn_imm(AARCH64_INSN_IMM_14, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_CONDBR19: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - result = extract_insn_imm(result, 19, 2); - result = insert_insn_imm(AARCH64_INSN_IMM_19, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_JUMP26: - case R_AARCH64_CALL26: - result = calc_reloc(RELOC_OP_PREL, uloc, val); - if (result < -(s64)BIT(27) || result >= (s64)BIT(27)) { - log_debug( - "R_AARCH64_CALL26 overflow: " - "result = 0x%lx, uloc = 0x%lx, val = 0x%lx\n", - result, (unsigned long)uloc, val); - val = search_insert_plt_table(uelf, val, (u64)&val); - log_debug("R_AARCH64_CALL26 overflow: " - "plt.addr = 0x%lx\n", val); - if (!val) { - goto overflow; - } - result = calc_reloc(RELOC_OP_PREL, uloc, val); - } - result = extract_insn_imm(result, 26, 2); - result = insert_insn_imm(AARCH64_INSN_IMM_26, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_ADR_GOT_PAGE: - got = (u64)setup_got_table(uelf, val, 0); - if (got == 0) { - goto overflow; - } - result = calc_reloc(RELOC_OP_PAGE, uloc, got); - if (result < -(s64)BIT(32) || result >= (s64)BIT(32)) { - goto overflow; - } - result = extract_insn_imm(result, 21, 12); - result = insert_insn_imm(AARCH64_INSN_IMM_ADR, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_LD64_GOT_LO12_NC: - got = (u64)setup_got_table(uelf, val, 0); - if (got == 0) { - goto overflow; - } - result = calc_reloc(RELOC_OP_ABS, uloc, got); - // don't check result & 7 == 0. - // sometimes, result & 7 != 0, it works fine. - result = extract_insn_imm(result, 9, 3); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TLSLE_ADD_TPREL_HI12: - result = (long)(ALIGN(TCB_SIZE, uelf->relf->tls_align) + val); - if (result < 0 || result >= (s64)BIT(24)) { - goto overflow; - } - result = extract_insn_imm(result, 12, 12); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: - result = (long)(ALIGN(TCB_SIZE, uelf->relf->tls_align) + val); - result = extract_insn_imm(result, 12, 0); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TLSDESC_ADR_PAGE21: - result = calc_reloc(RELOC_OP_PAGE, uloc, val); - if (result < -(s64)BIT(32) || result >= (s64)BIT(32)) { - goto overflow; - } - result = extract_insn_imm(result, 21, 12); - result = insert_insn_imm(AARCH64_INSN_IMM_ADR, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TLSDESC_LD64_LO12: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - // don't check result & 7 == 0. - result = extract_insn_imm(result, 9, 3); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TLSDESC_ADD_LO12: - result = calc_reloc(RELOC_OP_ABS, uloc, val); - result = extract_insn_imm(result, 12, 0); - result = insert_insn_imm(AARCH64_INSN_IMM_12, loc, - (unsigned long)result); - *(__le32 *)loc = cpu_to_le32((__le32)result); - break; - case R_AARCH64_TLSDESC_CALL: - // this is a blr instruction, don't need to modify - break; - default: - log_error("upatch: unsupported RELA relocation: %lu\n", - GELF_R_TYPE(rel[i].r_info)); - return -ENOEXEC; - } - } - return 0; - -overflow: - log_error("upatch: overflow in relocation type %d val %lx reloc %lx\n", - (int)GELF_R_TYPE(rel[i].r_info), val, result); - return -ENOEXEC; -} diff --git a/upatch-manage/arch/aarch64/resolve.c b/upatch-manage/arch/aarch64/resolve.c deleted file mode 100644 index 63ebb0fa..00000000 --- a/upatch-manage/arch/aarch64/resolve.c +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - -#include "log.h" -#include "upatch-ptrace.h" -#include "upatch-resolve.h" - -/* - * ldr x16, #24 - * ldr x17, #12 - * br x17 - * undefined - */ -#define AARCH64_JUMP_TABLE_JMP1 0x58000071580000d0 -#define AARCH64_JUMP_TABLE_JMP2 0xffffffffd61f0220 - -struct upatch_jmp_table_entry { - unsigned long inst[2]; - unsigned long addr[2]; -}; - -unsigned int get_jmp_table_entry() -{ - return sizeof(struct upatch_jmp_table_entry); -} - -static unsigned long setup_jmp_table(struct upatch_elf *uelf, - unsigned long jmp_addr, unsigned long origin_addr) -{ - struct upatch_jmp_table_entry *table = uelf->core_layout.kbase + - uelf->jmp_offs; - - unsigned int index = uelf->jmp_cur_entry; - if (index >= uelf->jmp_max_entry) { - log_error("jmp table overflow\n"); - return 0; - } - - table[index].inst[0] = AARCH64_JUMP_TABLE_JMP1; - table[index].inst[1] = AARCH64_JUMP_TABLE_JMP2; - table[index].addr[0] = jmp_addr; - table[index].addr[1] = origin_addr; - uelf->jmp_cur_entry++; - - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - index * sizeof(struct upatch_jmp_table_entry)); -} - -unsigned long setup_got_table(struct upatch_elf *uelf, - unsigned long jmp_addr, unsigned long tls_addr) -{ - struct upatch_jmp_table_entry *table = uelf->core_layout.kbase + - uelf->jmp_offs; - - unsigned int index = uelf->jmp_cur_entry; - if (index >= uelf->jmp_max_entry) { - log_error("got table overflow\n"); - return 0; - } - - table[index].inst[0] = jmp_addr; - table[index].inst[1] = tls_addr; - table[index].addr[0] = 0xffffffff; - table[index].addr[1] = 0xffffffff; - uelf->jmp_cur_entry++; - - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - index * sizeof(struct upatch_jmp_table_entry)); -} - -unsigned long insert_plt_table(struct upatch_elf *uelf, struct object_file *obj, - unsigned long r_type, unsigned long addr) -{ - unsigned long jmp_addr = 0xffffffff; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, sizeof(jmp_addr))) { - log_error("copy address failed\n"); - goto out; - } - - if (r_type == R_AARCH64_TLSDESC && - upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), - &tls_addr, sizeof(tls_addr))) { - log_error("copy address failed\n"); - goto out; - } - - if (r_type == R_AARCH64_TLSDESC) { - elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); - } else { - elf_addr = setup_jmp_table(uelf, jmp_addr, (unsigned long)addr); - } - log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", - elf_addr, jmp_addr, tls_addr); -out: - return elf_addr; -} - -unsigned long insert_got_table(struct upatch_elf *uelf, struct object_file *obj, - unsigned long r_type, unsigned long addr) -{ - unsigned long jmp_addr = 0xffffffff; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, sizeof(jmp_addr))) { - log_error("copy address failed\n"); - goto out; - } - if (r_type == R_AARCH64_TLSDESC && - upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), - &tls_addr, sizeof(tls_addr))) { - log_error("copy address failed\n"); - goto out; - } - - elf_addr = jmp_addr; - if (r_type != R_AARCH64_GLOB_DAT) { - elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); - } - log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, - jmp_addr, tls_addr); - -out: - return elf_addr; -} - -unsigned long search_insert_plt_table(struct upatch_elf *uelf, - unsigned long jmp_addr, unsigned long origin_addr) -{ - struct upatch_jmp_table_entry *table = uelf->core_layout.kbase + - uelf->jmp_offs; - - for (unsigned int i = 0; i < uelf->jmp_cur_entry; ++i) { - if (table[i].addr[0] != jmp_addr) { - continue; - } - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - i * sizeof(struct upatch_jmp_table_entry)); - } - - return setup_jmp_table(uelf, jmp_addr, origin_addr); -} diff --git a/upatch-manage/arch/arm/patch_load.c b/upatch-manage/arch/arm/patch_load.c new file mode 100644 index 00000000..a7fe4484 --- /dev/null +++ b/upatch-manage/arch/arm/patch_load.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * setup jmp table and do relocation in arm + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef __arm__ + +#include +#include + +#include "../../util.h" +#include "../../patch_load.h" +#include "../patch_load.h" + +#ifndef R_ARM_TLS_DESC +#define R_ARM_TLS_DESC 13 +#endif + +#ifndef R_ARM_GLOB_DAT +#define R_ARM_GLOB_DAT 21 +#endif + +#ifndef R_ARM_JUMP_SLOT +#define R_ARM_JUMP_SLOT 22 +#endif + +#ifndef R_ARM_GOTOFF32 +#define R_ARM_GOTOFF32 24 +#endif +#ifndef R_ARM_GOTPC +#define R_ARM_GOTPC 25 +#endif +#ifndef R_ARM_GOT32 +#define R_ARM_GOT32 26 +#endif +#ifndef R_ARM_TLS_GOTDESC +#define R_ARM_TLS_GOTDESC 90 +#endif +#ifndef R_ARM_GOT_ABS +#define R_ARM_GOT_ABS 95 +#endif +#ifndef R_ARM_GOT_PREL +#define R_ARM_GOT_PREL 96 +#endif +#ifndef R_ARM_GOT_BREL12 +#define R_ARM_GOT_BREL12 97 +#endif +#ifndef R_ARM_GOTOFF12 +#define R_ARM_GOTOFF12 98 +#endif +#ifndef R_ARM_GOTRELAX +#define R_ARM_GOTRELAX 99 +#endif +#ifndef R_ARM_TLS_GD32 +#define R_ARM_TLS_GD32 104 +#endif +#ifndef R_ARM_TLS_LDM32 +#define R_ARM_TLS_LDM32 105 +#endif +#ifndef R_ARM_TLS_IE32 +#define R_ARM_TLS_IE32 107 +#endif +#ifndef R_ARM_TLS_IE12GP +#define R_ARM_TLS_IE12GP 111 +#endif +#ifndef R_ARM_THM_GOT_BREL12 +#define R_ARM_THM_GOT_BREL12 131 +#endif + +/* arm32 PC = crr_addr+8 + * r12 is temp data storage register, no need to restore + * 0: ldr r12, [pc] + * 4: mov pc, r12 //bx r12 + * 8: +*/ +#define ARM_JUMP_TABLE_JMP_1 0xE59FC000 +#define ARM_JUMP_TABLE_JMP_2 0xE1A0F00C + +/* For IFUNC(indirect function), the symbol value is point to the resolve function + * We should call resolve func and get the real func address in x0 + * For armv7 and sparc64, we need to pass hwcap as first arg to resolve function + * 0x00 push {r0, r1, r2, lr} save arg for IFUNC, like memcpy + * 0x04 ldr r12, [pc, #0x14] load resolve func addr + 0x08 ldr r0, [pc, #0x14] load hwcap + * 0xoc mov lr, pc save return addr 0x14 + * 0x10 mov pc, r12 jmp to resolve func + * 0x14 mov r12, r0 get the real addr of IFUNC + * 0x18 pop {r0, r1, r2, lr} restore arg for IFUNC + * 0x1c mov pc, r12 jmp to real IFUNC + * 0x20 addr[0] + * 0x24 hwcap + */ + +#define ARM_CALL_IFUNC_1 0xE92D4007 +#define ARM_CALL_IFUNC_2 0xE59FC014 +#define ARM_CALL_IFUNC_3 0xE59F0014 +#define ARM_CALL_IFUNC_4 0xE1A0E00F +#define ARM_CALL_IFUNC_5 0xE1A0F00C +#define ARM_CALL_IFUNC_6 0xE1A0C000 +#define ARM_CALL_IFUNC_7 0xE8BD4007 +#define ARM_CALL_IFUNC_8 0xE1A0F00C + +enum arm_reloc_op { + RELOC_OP_NONE, + RELOC_OP_ABS, + RELOC_OP_PREL, +}; + +unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + int entry_num = is_ifunc ? IFUNC_JMP_ENTRY_NUM : NORMAL_JMP_ENTRY_NUM; + if (table->cur + entry_num > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, entry_num); + return 0; + } + + if (is_ifunc) { + jmp[index] = ARM_CALL_IFUNC_1; + jmp[index + 1] = ARM_CALL_IFUNC_2; + jmp[index + 2] = ARM_CALL_IFUNC_3; + jmp[index + 3] = ARM_CALL_IFUNC_4; + jmp[index + 4] = ARM_CALL_IFUNC_5; + jmp[index + 5] = ARM_CALL_IFUNC_6; + jmp[index + 6] = ARM_CALL_IFUNC_7; + jmp[index + 7] = ARM_CALL_IFUNC_8; + jmp[index + 8] = jmp_addr; + jmp[index + 9] = ELF_HWCAP; + } else { + jmp[index] = ARM_JUMP_TABLE_JMP_1; + jmp[index + 1] = ARM_JUMP_TABLE_JMP_2; + jmp[index + 2] = jmp_addr; + } + table->cur += entry_num; + + return info->layout.base + table->off + index * JMP_ENTRY_SIZE; +} + +unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + unsigned long entry_addr = info->layout.base + table->off + index * JMP_ENTRY_SIZE; + int entry_num = NORMAL_JMP_ENTRY_NUM; + if (table->cur + entry_num > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, entry_num); + return 0; + } + + jmp[index] = jmp_addr; + jmp[index + 1] = tls_addr; + table->cur += entry_num; + + log_debug("\tsetup got table 0x%lx -> 0x%lx, tls_addr=0x%lx\n", entry_addr, jmp_addr, tls_addr); + + return entry_addr; +} + +unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +{ + unsigned long jmp_addr; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + if (r_type == R_ARM_TLS_DESC && + copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + if (r_type == R_ARM_TLS_DESC) { + elf_addr = setup_got_table(info, jmp_addr, tls_addr); + } else { + elf_addr = setup_jmp_table(info, jmp_addr, 0); + } + + log_debug("JMP: 0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, jmp_addr, tls_addr); + +out: + return elf_addr; +} + +unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +{ + unsigned long jmp_addr; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + if (r_type == R_ARM_TLS_DESC && + copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + elf_addr = setup_got_table(info, jmp_addr, tls_addr); + +out: + return elf_addr; +} + +static u32 calc_reloc(enum arm_reloc_op op, u32 *place, u32 S) +{ + s32 sval = 0; + switch (op) { + case RELOC_OP_ABS: + // S + A + sval = S; + break; + case RELOC_OP_PREL: + // S + A - P + sval = S - (u32)place; + break; + default: + log_err("\tunknown relocation operation %d\n", op); + break; + } + + log_debug("\tS + A = 0x%x, P = 0x%x, X = 0x%x\n", S, (u32)place, sval); + return sval; +} + +int apply_relocate_add(struct upatch_info *info, unsigned int relsec) +{ + Elf_Shdr *sechdrs = info->sechdrs; + const char *strtab = info->strtab; + unsigned int symindex = info->index.sym; + unsigned int i; + Elf_Sym *sym; + char const *sym_name; + u32 *reloc_place; + u32 *ureloc_place; + u32 sym_addr, got, tmp; + s32 result; + Elf_Rel *rel = (void *)sechdrs[relsec].sh_addr; + u32 got_vaddr = 0; + struct jmp_table *table; + unsigned int reloced_sec = sechdrs[relsec].sh_info; + + // sh_addr = kdest, is the section start in hot patch kalloc memory + // sh_addralign = dest, is the section start in VMA pole + u32 sec_kaddr = sechdrs[reloced_sec].sh_addr; + u32 sec_vaddr = sechdrs[reloced_sec].sh_addralign; + + log_debug("sec_kaddr = 0x%x sec_vaddr = 0x%x\n", sec_kaddr, sec_vaddr); + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + + /* relocP corresponds to P in the kernel space */ + reloc_place = (void *)sec_kaddr + rel[i].r_offset; + /* urelocP corresponds to P in user spcace */ + ureloc_place = (void *)sec_vaddr + rel[i].r_offset; + /* sym is the ELF symbol we're referring to */ + sym = (Elf_Sym *)sechdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info); + sym_name = strtab + sym->st_name; + + sym_addr = sym->st_value; + log_debug("'%s'\t type %d r_offset=0x%x, st_value=0x%x\n", + sym_name, (int)ELF_R_TYPE(rel[i].r_info), + rel[i].r_offset, sym->st_value); + log_debug("\t(S + A) = 0x%x \tP(kernel) = 0x%x \tP(user) = 0x%x\n", + sym_addr, (u32)reloc_place, (u32)ureloc_place); + log_debug("\t(before) *reloc_place = 0x%x\n", *reloc_place); + + table = &info->layout.table; + got_vaddr = info->layout.base + table->off; + + switch (ELF_R_TYPE(rel[i].r_info)) { + case R_ARM_NONE: + break; + case R_ARM_ABS32: + case R_ARM_TARGET1: // (S + A) | T + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + *reloc_place += result; + break; + + case R_ARM_PC24: + case R_ARM_CALL: + case R_ARM_JUMP24: + if (sym_addr & 3) { + log_err("unsupported interworking call (ARM -> Thumb)\n"); + return -ENOEXEC; + } + result = __mem_to_opcode_arm(*reloc_place); + result = (result & 0x00ffffff) << 2; + if (result & 0x02000000) + result -= 0x04000000; + + // L = S + A - P (A = 0) + result += sym_addr - (u32)ureloc_place; + /* + * Route through a PLT entry if 'offset' exceeds the + * supported range. Note that 'offset + loc + 8' + * contains the absolute jump target, i.e., + * @sym + addend, corrected for the +8 PC bias. + */ + if (IS_ENABLED(CONFIG_ARM_MODULE_PLTS) && + (result <= (s32)0xfe000000 || result >= (s32)0x02000000)) { + result = setup_jmp_table(info, result + (u32)ureloc_place + 8, false) + - (u32)ureloc_place - 8; + if (!result) { + goto overflow; + } + log_warn("setup jmp table for PLT in arm! result = 0x%x\n", result); + } + + // check if plt addr still outside of 32MB range + if (result <= (s32)0xfe000000 || result >= (s32)0x02000000) { + log_err("setup jmp table outside of 32MB range for result = 0x%x\n", result); + goto overflow; + } + + result >>= 2; + result &= 0x00ffffff; + + *reloc_place &= __opcode_to_mem_arm(0xff000000); + *reloc_place |= __opcode_to_mem_arm(result); + break; + + case R_ARM_V4BX: + /* Preserve Rm and the condition code. Alter + * other bits to re-code instruction as + * MOV PC,Rm. + */ + *reloc_place &= __opcode_to_mem_arm(0xf000000f); + *reloc_place |= __opcode_to_mem_arm(0x01a0f000); + break; + + case R_ARM_PREL31: /* sign extend */ + result = (*(s32 *)reloc_place << 1) >> 1; + result += calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + if (result >= 0x40000000 || result < -0x40000000) { + goto overflow; + } + *reloc_place &= 0x80000000; + *reloc_place |= (result & 0x7fffffff); + break; + + case R_ARM_REL32: // ((S + A) | T) - P, T is 0 because we don't have thumb mode + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + *reloc_place += result; + break; + + case R_ARM_MOVW_ABS_NC: // (S + A) | T + case R_ARM_MOVT_ABS: // S + A + case R_ARM_MOVW_PREL_NC: // ((S + A) | T) - P + case R_ARM_MOVT_PREL: // (S + A) - P + result = tmp = __mem_to_opcode_arm(*reloc_place); + result = ((result & 0xf0000) >> 4) | (result & 0xfff); + result = (result ^ 0x8000) - 0x8000; + + result += sym_addr; // S + if (ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVT_PREL || + ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVW_PREL_NC) + result -= (u32)ureloc_place; // - P + if (ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVT_ABS || + ELF_R_TYPE(rel[i].r_info) == R_ARM_MOVT_PREL) + result >>= 16; + + tmp &= 0xfff0f000; + tmp |= ((result & 0xf000) << 4) | (result & 0x0fff); + + *reloc_place = __opcode_to_mem_arm(tmp); + break; + + // The relocation above is implement based on Linux kernel arch/arm/kernel/module.c + // The relocation below is implement based on LLVM + case R_ARM_GLOB_DAT: + case R_ARM_JUMP_SLOT: // (S + A) | T + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + *reloc_place += result; + break; + + case R_ARM_GOTPC: // R_ARM_BASE_PREL B(S) + A - P + // B(S) is the start address of .got section, which is got_vaddr + log_debug("\t(GOT) got_vaddr = 0x%x\n", got_vaddr); + result = calc_reloc(RELOC_OP_PREL, ureloc_place, got_vaddr); + *reloc_place += result; + break; + + case R_ARM_GOT32: // R_ARM_GOT_BREL GOT(S) + A - GOT_ORG + // GOT_ORG is the start address of got table, which is got_vaddr + log_debug("\t(GOT) got_vaddr = 0x%x\n", got_vaddr); + got = get_or_setup_got_entry(info, sym); + if (got == 0) { + goto overflow; + } + result = calc_reloc(RELOC_OP_PREL, (u32 *)got_vaddr, got); + *reloc_place += result; + break; + + case R_ARM_GOT_PREL: // GOT(S) + A -P + log_debug("\t(GOT) got_vaddr = 0x%x\n", got_vaddr); + got = get_or_setup_got_entry(info, sym); + if (got == 0) { + goto overflow; + } + result = calc_reloc(RELOC_OP_PREL, ureloc_place, got); + *reloc_place += result; + break; + + default: + log_debug("\tunsupported REL relocation: %u\n", ELF_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + log_debug("\t(after) *reloc_place = 0x%x, result = 0x%x\n", *reloc_place, result); + } + return 0; + +overflow: + log_err("\toverflow in relocation type %d val %x reloc 0x%x\n", + (int)ELF_R_TYPE(rel[i].r_info), sym_addr, result); + return -ENOEXEC; +} + +bool is_got_rela_type(int type) +{ + switch (type) { + case R_ARM_GOTOFF32: + case R_ARM_GOT32: + case R_ARM_TLS_GOTDESC: + case R_ARM_GOT_ABS: + case R_ARM_GOT_PREL: + case R_ARM_GOT_BREL12: + case R_ARM_GOTOFF12: + case R_ARM_GOTRELAX: + case R_ARM_TLS_GD32: + case R_ARM_TLS_LDM32: + case R_ARM_TLS_IE32: + case R_ARM_TLS_IE12GP: + case R_ARM_THM_GOT_BREL12: + return true; + break; + default: + return false; + break; + } + return false; +} + +#endif /* __arm__ */ diff --git a/upatch-manage/arch/arm64/insn.c b/upatch-manage/arch/arm64/insn.c new file mode 100644 index 00000000..eaf92fad --- /dev/null +++ b/upatch-manage/arch/arm64/insn.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Huawei Ltd. + * Author: Jiang Liu + * + * Copyright (C) 2014-2016 Zi Shen Lim + */ + +#ifdef __aarch64__ + +#include + +#include "insn.h" +#include "../../util.h" + +#define ADR_IMM_HILOSPLIT 2 +#define ADR_IMM_SIZE SZ_2M +#define ADR_IMM_LOMASK ((1 << ADR_IMM_HILOSPLIT) - 1) +#define ADR_IMM_HIMASK ((ADR_IMM_SIZE >> ADR_IMM_HILOSPLIT) - 1) +#define ADR_IMM_LOSHIFT 29 +#define ADR_IMM_HISHIFT 5 + +static int aarch64_get_imm_shift_mask(enum aarch64_insn_imm_type type, u32 *maskp, int *shiftp) +{ + u32 mask; + int shift; + + switch (type) { + case AARCH64_INSN_IMM_26: + mask = BIT(26) - 1; + shift = 0; + break; + case AARCH64_INSN_IMM_19: + mask = BIT(19) - 1; + shift = 5; + break; + case AARCH64_INSN_IMM_16: + mask = BIT(16) - 1; + shift = 5; + break; + case AARCH64_INSN_IMM_14: + mask = BIT(14) - 1; + shift = 5; + break; + case AARCH64_INSN_IMM_12: + mask = BIT(12) - 1; + shift = 10; + break; + case AARCH64_INSN_IMM_9: + mask = BIT(9) - 1; + shift = 12; + break; + case AARCH64_INSN_IMM_7: + mask = BIT(7) - 1; + shift = 15; + break; + case AARCH64_INSN_IMM_6: + case AARCH64_INSN_IMM_S: + mask = BIT(6) - 1; + shift = 10; + break; + case AARCH64_INSN_IMM_R: + mask = BIT(6) - 1; + shift = 16; + break; + case AARCH64_INSN_IMM_N: + mask = 1; + shift = 22; + break; + default: + return -EINVAL; + } + + *maskp = mask; + *shiftp = shift; + + return 0; +} + +u32 aarch64_insn_encode_immediate(enum aarch64_insn_imm_type type, u32 insn, u64 imm) +{ + u32 immlo, immhi, mask; + int shift; + + if (insn == AARCH64_BREAK_FAULT) { + return AARCH64_BREAK_FAULT; + } + + switch (type) { + case AARCH64_INSN_IMM_ADR: + shift = 0; + immlo = (imm & ADR_IMM_LOMASK) << ADR_IMM_LOSHIFT; + imm >>= ADR_IMM_HILOSPLIT; + immhi = (imm & ADR_IMM_HIMASK) << ADR_IMM_HISHIFT; + imm = immlo | immhi; + mask = ((ADR_IMM_LOMASK << ADR_IMM_LOSHIFT) | + (ADR_IMM_HIMASK << ADR_IMM_HISHIFT)); + break; + default: + if (aarch64_get_imm_shift_mask(type, &mask, &shift) < 0) { + log_err("unknown immediate encoding %d\n", type); + return AARCH64_BREAK_FAULT; + } + } + + /* Update the immediate field. */ + insn &= ~(mask << shift); + insn |= (imm & mask) << shift; + + return insn; +} + +#endif /* __aarch64__ */ diff --git a/upatch-manage/arch/arm64/insn.h b/upatch-manage/arch/arm64/insn.h new file mode 100644 index 00000000..6e8c165f --- /dev/null +++ b/upatch-manage/arch/arm64/insn.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Huawei Ltd. + * Author: Jiang Liu + * + * Copyright (C) 2014-2016 Zi Shen Lim + */ + +#ifndef _ARCH_AARCH64_INSN_H +#define _ARCH_AARCH64_INSN_H + +#include + +u32 aarch64_insn_encode_immediate(enum aarch64_insn_imm_type type, u32 insn, u64 imm); + +#endif /* _ARCH_AARCH64_INSN_H */ diff --git a/upatch-manage/arch/arm64/patch_load.c b/upatch-manage/arch/arm64/patch_load.c new file mode 100644 index 00000000..1227326a --- /dev/null +++ b/upatch-manage/arch/arm64/patch_load.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * setup jmp table and do relocation in arm64 + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef __aarch64__ + +#include + +#include "../../util.h" +#include "../patch_load.h" +#include "insn.h" + +/* For UND function, we need to find its real addr in VMA, after that, we need to + * create plt.got table to store instruction to jmp to it real addr + * 0: ldr x17, #8 + * 4: br x17 + * 8: + * 12: + */ +#define AARCH64_JUMP_TABLE_JMP 0xd61f022058000051 + +/* For IFUNC(indirect function), the symbol value is point to the resolve function + * We should call resolve func and get the real func address in x0 + * We save x0, x1, x2, x30, because IFUNC will only use 3 args, and x30 is the LR store the return addr + * 0: stp x0, x1, [sp, #-32]! + * 4: stp x2, x30, [sp, #16] + * 8: ldr x17, #0x18 + * C: blr x17 + * 10: mov x17, x0 + * 14: ldp x2, x30, [sp, #16] + * 18: ldp x0, x1, [sp], #32 + * 1C: br x17 + * 20: + * 24: + */ + +#define AARCH64_CALL_IFUNC_1 0xA9017BE2A9BE07E0 +#define AARCH64_CALL_IFUNC_2 0xD63F0220580000D1 +#define AARCH64_CALL_IFUNC_3 0xA9417BE2AA0003F1 +#define AARCH64_CALL_IFUNC_4 0xD61F0220A8C207E0 + + +/* + * 0: ldr x16, #24 load mem from PC+24 into x16, x16 = *(PC+24) = addr[1] + * 4: ldr x17, #12 load mem from PC+12 into x17, x17 = *(PC+16) = addr[0] + * 8: br x17 jump to addr in x17, jmp tp addr[0] + * 12: undefined we only need 3 instruction, but table entry is 64bit size + * 16: addr[0] jump destination + * 20: addr[0] + * 24: addr[1] plt entry address + * 28: addr[1] + */ +#define AARCH64_JUMP_TABLE_JMP1 0x58000071580000d0 +#define AARCH64_JUMP_TABLE_JMP2 0x00000000d61f0220 + +#ifndef R_AARCH64_MOVW_GOTOFF_GO +#define R_AARCH64_MOVW_GOTOFF_GO 300 +#endif + +#ifndef R_AARCH64_LD64_GOTPAGE_LO15 +#define R_AARCH64_LD64_GOTPAGE_LO15 313 +#endif + + +#ifndef R_AARCH64_ADR_GOT_PAGE +#define R_AARCH64_ADR_GOT_PAGE 311 +#endif + +#ifndef R_AARCH64_LD64_GOT_LO12_NC +#define R_AARCH64_LD64_GOT_LO12_NC 312 +#endif + +#ifndef R_AARCH64_LD64_GOTPAGE_LO15 +#define R_AARCH64_LD64_GOTPAGE_LO15 313 +#endif + +#ifndef R_AARCH64_TLSLE_ADD_TPREL_HI12 +#define R_AARCH64_TLSLE_ADD_TPREL_HI12 549 +#endif + +#ifndef R_AARCH64_TLSLE_ADD_TPREL_LO12_NC +#define R_AARCH64_TLSLE_ADD_TPREL_LO12_NC 551 +#endif + +#ifndef R_AARCH64_TLSDESC_ADR_PAGE21 +#define R_AARCH64_TLSDESC_ADR_PAGE21 562 +#endif + +#ifndef R_AARCH64_TLSDESC_LD64_LO12 +#define R_AARCH64_TLSDESC_LD64_LO12 563 +#endif + +#ifndef R_AARCH64_TLSDESC_ADD_LO12 +#define R_AARCH64_TLSDESC_ADD_LO12 564 +#endif + +#ifndef R_AARCH64_TLSDESC_CALL +#define R_AARCH64_TLSDESC_CALL 569 +#endif + +#ifndef R_AARCH64_TLSDESC +#define R_AARCH64_TLSDESC 1031 +#endif + +#define TCB_SIZE 2 * sizeof(void *) + +enum aarch64_reloc_op { + RELOC_OP_NONE, + RELOC_OP_ABS, + RELOC_OP_PREL, + RELOC_OP_PAGE, +}; + +unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + int entry_num = is_ifunc ? IFUNC_JMP_ENTRY_NUM : NORMAL_JMP_ENTRY_NUM; + if (table->cur + entry_num > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, entry_num); + return 0; + } + + if (is_ifunc) { + jmp[index] = AARCH64_CALL_IFUNC_1; + jmp[index + 1] = AARCH64_CALL_IFUNC_2; + jmp[index + 2] = AARCH64_CALL_IFUNC_3; + jmp[index + 3] = AARCH64_CALL_IFUNC_4; + jmp[index + 4] = jmp_addr; + } else { + jmp[index] = AARCH64_JUMP_TABLE_JMP; + jmp[index + 1] = jmp_addr; + } + table->cur += entry_num; + + return info->layout.base + table->off + index * JMP_ENTRY_SIZE; +} + +static unsigned long setup_jmp_table_with_plt(struct upatch_info *info, + unsigned long jmp_addr, unsigned long plt_addr) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + int entry_num = PLT_JMP_ENTRY_NUM; + if (table->cur + entry_num > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, entry_num); + return 0; + } + + jmp[index] = AARCH64_JUMP_TABLE_JMP1; + jmp[index + 1] = AARCH64_JUMP_TABLE_JMP2; + jmp[index + 2] = jmp_addr; + jmp[index + 3] = plt_addr; + table->cur += entry_num; + + return info->layout.base + table->off + index * JMP_ENTRY_SIZE; +} + +unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + unsigned long entry_addr = info->layout.base + table->off + index * JMP_ENTRY_SIZE; + int entry_num = NORMAL_JMP_ENTRY_NUM; + if (table->cur + entry_num > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, entry_num); + return 0; + } + + jmp[index] = jmp_addr; + jmp[index + 1] = tls_addr; + table->cur += entry_num; + + log_debug("\tsetup got table 0x%lx -> 0x%lx, tls_addr=0x%lx\n", + entry_addr, jmp_addr, tls_addr); + + return entry_addr; +} + +unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +{ + unsigned long jmp_addr; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + if (r_type == R_AARCH64_TLSDESC && + copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + if (r_type == R_AARCH64_TLSDESC) + elf_addr = setup_got_table(info, jmp_addr, tls_addr); + else + elf_addr = setup_jmp_table_with_plt(info, jmp_addr, (unsigned long)(uintptr_t)addr); + + log_debug("JMP: 0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", + elf_addr, jmp_addr, tls_addr); + +out: + return elf_addr; +} + +static unsigned long search_insert_plt_table(struct upatch_info *info, + unsigned long jmp_addr, unsigned long plt_addr) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int i = 0; + + for (i = 0; i < table->max; ++i) { + if (jmp[i] != jmp_addr) { + continue; + } + return info->layout.base + table->off + i * JMP_ENTRY_SIZE; + } + + return setup_jmp_table_with_plt(info, jmp_addr, plt_addr); +} + +unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +{ + unsigned long jmp_addr; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + if (r_type == R_AARCH64_TLSDESC && + copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + elf_addr = setup_got_table(info, jmp_addr, tls_addr); + +out: + return elf_addr; +} + +static s64 calc_reloc(enum aarch64_reloc_op op, void *place, u64 S) +{ + s64 sval = 0; + switch (op) { + case RELOC_OP_ABS: + // S + A + sval = S; + break; + case RELOC_OP_PREL: + // S + A - P + sval = S - (u64)place; + break; + case RELOC_OP_PAGE: + // Page(S + A) - Page(P) + sval = (S & ~0xfff) - ((u64)place & ~0xfff); + break; + default: + log_err("\tunknown relocation operation %d\n", op); + break; + } + + log_debug("\tS + A = 0x%llx, P = 0x%llx, X = 0x%llx\n", S, (u64)place, sval); + return sval; +} + +static inline u64 extract_insn_imm(s64 sval, int len, int lsb) +{ + u64 imm, imm_mask; + + imm = sval >> lsb; + imm_mask = (BIT(lsb + len) - 1) >> lsb; + imm = imm & imm_mask; + + log_debug("\textract imm, X=0x%llx, X[%d:%d]=0x%llx\n", sval, (len + lsb - 1), lsb, imm); + return imm; +} + +static inline u32 insert_insn_imm(enum aarch64_insn_imm_type imm_type, void *place, u64 imm) +{ + u32 insn, new_insn; + + insn = le32_to_cpu(*(__le32 *)place); + new_insn = aarch64_insn_encode_immediate(imm_type, insn, imm); + + log_debug("\tinsert imm, P=0x%llx, insn=0x%x, imm_type=%d, imm=0x%llx, new_insn=0x%x\n", + (u64)place, insn, imm_type, imm, new_insn); + return new_insn; +} + +int apply_relocate_add(struct upatch_info *info, unsigned int relsec) +{ + Elf_Shdr *sechdrs = info->sechdrs; + const char *strtab = info->strtab; + unsigned int symindex = info->index.sym; + unsigned int i; + Elf_Sym *sym; + char const *sym_name; + void *reloc_place; + void *ureloc_place; + u64 sym_addr, got; + s64 result; + u64 got_start = info->layout.base + info->layout.table.off; + Elf_Rela *rel = (void *)sechdrs[relsec].sh_addr; + + unsigned int reloced_sec = sechdrs[relsec].sh_info; + + // sh_addr = kdest, is the section start in hot patch kalloc memory + // sh_addralign = dest, is the section start in VMA pole + void *sec_kaddr = (void *)sechdrs[reloced_sec].sh_addr; + void *sec_vaddr = (void *)sechdrs[reloced_sec].sh_addralign; + log_debug("sec_kaddr = 0x%llx sec_vaddr = 0x%llx\n", (u64)sec_kaddr, (u64)sec_vaddr); + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + /* corresponds to P in the kernel space */ + reloc_place = (void *)sec_kaddr + rel[i].r_offset; + + /* corresponds to P in user space */ + ureloc_place = (void *)sec_vaddr + rel[i].r_offset; + + /* sym is the ELF symbol we're referring to */ + sym = (Elf_Sym *)sechdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info); + sym_name = strtab + sym->st_name; + + /* src corresponds to (S + A) */ + sym_addr = (s64)(sym->st_value + rel[i].r_addend); + log_debug("'%s'\t type %d r_offset=0x%llx, st_value=0x%llx, r_addend=0x%llx\n", + sym_name, (int)ELF_R_TYPE(rel[i].r_info), + rel[i].r_offset, sym->st_value, rel[i].r_addend); + log_debug("\t(S + A) = 0x%llx \tP(kernel) = 0x%Lx \tP(user) = 0x%Lx\n", + sym_addr, (u64)reloc_place, (u64)ureloc_place); + log_debug("\t(before) *reloc_place = 0x%llx\n", *(u64*)reloc_place); + + /* Perform the static relocation. */ + switch (ELF_R_TYPE(rel[i].r_info)) { + /* Null relocations. */ + case R_ARM_NONE: + case R_AARCH64_NONE: + break; + /* Data relocations. */ + case R_AARCH64_ABS64: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + *(s64 *)reloc_place = result; + break; + case R_AARCH64_ABS32: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + if (result < -(s64)BIT(31) || result >= (s64)BIT(32)) + goto overflow; + *(s32 *)reloc_place = result; + break; + case R_AARCH64_ABS16: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + if (result < -(s64)BIT(15) || result >= (s64)BIT(16)) + goto overflow; + *(s16 *)reloc_place = result; + break; + case R_AARCH64_PREL64: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + *(s64 *)reloc_place = result; + break; + case R_AARCH64_PREL32: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + if (result < -(s64)BIT(31) || result >= (s64)BIT(32)) + goto overflow; + *(s32 *)reloc_place = result; + break; + case R_AARCH64_PREL16: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + if (result < -(s64)BIT(15) || result >= (s64)BIT(16)) + goto overflow; + *(s16 *)reloc_place = result; + break; + /* Immediate instruction relocations. */ + case R_AARCH64_LD_PREL_LO19: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + if (result < -(s64)BIT(20) || result >= (s64)BIT(20)) + goto overflow; + result = extract_insn_imm(result, 19, 2); + result = insert_insn_imm(AARCH64_INSN_IMM_19, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_ADR_PREL_LO21: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + if (result < -(s64)BIT(20) || result >= (s64)BIT(20)) + goto overflow; + result = extract_insn_imm(result, 21, 0); + result = insert_insn_imm(AARCH64_INSN_IMM_ADR, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_ADR_PREL_PG_HI21: + result = calc_reloc(RELOC_OP_PAGE, ureloc_place, sym_addr); + if (result < -(s64)BIT(32) || result >= (s64)BIT(32)) + goto overflow; + result = extract_insn_imm(result, 21, 12); + result = insert_insn_imm(AARCH64_INSN_IMM_ADR, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_ADR_PREL_PG_HI21_NC: + result = calc_reloc(RELOC_OP_PAGE, ureloc_place, sym_addr); + result = extract_insn_imm(result, 21, 12); + result = insert_insn_imm(AARCH64_INSN_IMM_ADR, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_ADD_ABS_LO12_NC: + case R_AARCH64_LDST8_ABS_LO12_NC: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + result = extract_insn_imm(result, 12, 0); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_LDST16_ABS_LO12_NC: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + result = extract_insn_imm(result, 11, 1); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_LDST32_ABS_LO12_NC: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + result = extract_insn_imm(result, 10, 2); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_LDST64_ABS_LO12_NC: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + result = extract_insn_imm(result, 9, 3); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_LDST128_ABS_LO12_NC: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + result = extract_insn_imm(result, 8, 4); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TSTBR14: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + if (result < -(s64)BIT(15) || result >= (s64)BIT(15)) + goto overflow; + result = extract_insn_imm(result, 14, 2); + result = insert_insn_imm(AARCH64_INSN_IMM_14, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_CONDBR19: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + result = extract_insn_imm(result, 19, 2); + result = insert_insn_imm(AARCH64_INSN_IMM_19, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_JUMP26: + case R_AARCH64_CALL26: + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + // branch addressable span is +/-128MB + if (result < -(s64)BIT(27) || result >= (s64)BIT(27)) { + log_warn("\tR_AARCH64_CALL26 overflow: result = 0x%llx, uloc = 0x%lx, val = 0x%llx\n", + result, (unsigned long)(uintptr_t)ureloc_place, sym_addr); + sym_addr = search_insert_plt_table(info, sym_addr, (u64)&sym_addr); + log_warn("\tR_AARCH64_CALL26 overflow: plt.addr = 0x%llx\n", sym_addr); + if (!sym_addr) { + goto overflow; + } + result = calc_reloc(RELOC_OP_PREL, ureloc_place, sym_addr); + } + result = extract_insn_imm(result, 26, 2); + result = insert_insn_imm(AARCH64_INSN_IMM_26, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_ADR_GOT_PAGE: + got = get_or_setup_got_entry(info, sym); + if (got == 0) { + goto overflow; + } + result = calc_reloc(RELOC_OP_PAGE, ureloc_place, got); + if (result < -(s64)BIT(32) || result >= (s64)BIT(32)) { + goto overflow; + } + result = extract_insn_imm(result, 21, 12); + result = insert_insn_imm(AARCH64_INSN_IMM_ADR, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_LD64_GOT_LO12_NC: + got = get_or_setup_got_entry(info, sym); + if (got == 0) { + goto overflow; + } + result = calc_reloc(RELOC_OP_ABS, ureloc_place, got); + // don't check result & 7 == 0. + // sometimes, result & 7 != 0, it works fine. + result = extract_insn_imm(result, 9, 3); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_LD64_GOTPAGE_LO15: + got = get_or_setup_got_entry(info, sym); + if (got == 0) { + goto overflow; + } + // G(GDAT(S)) - Page(GOT) + result = calc_reloc(RELOC_OP_PREL, (void *)(got_start & ~0xfff), got); + if (result < 0 || result >= (s64)BIT(14)) { + log_err("got=%llx got_start=%llx\n", got, got_start); + goto overflow; + } + result = extract_insn_imm(result, 12, 3); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TLSLE_ADD_TPREL_HI12: + result = ALIGN(TCB_SIZE, info->running_elf.meta->tls_align) + sym_addr; + if (result < 0 || result >= BIT(24)) { + goto overflow; + } + result = extract_insn_imm(result, 12, 12); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: + result = ALIGN(TCB_SIZE, info->running_elf.meta->tls_align) + sym_addr; + result = extract_insn_imm(result, 12, 0); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TLSDESC_ADR_PAGE21: + result = calc_reloc(RELOC_OP_PAGE, ureloc_place, sym_addr); + if (result < -(s64)BIT(32) || result >= (s64)BIT(32)) { + goto overflow; + } + result = extract_insn_imm(result, 21, 12); + result = insert_insn_imm(AARCH64_INSN_IMM_ADR, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TLSDESC_LD64_LO12: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + // don't check result & 7 == 0. + result = extract_insn_imm(result, 9, 3); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TLSDESC_ADD_LO12: + result = calc_reloc(RELOC_OP_ABS, ureloc_place, sym_addr); + result = extract_insn_imm(result, 12, 0); + result = insert_insn_imm(AARCH64_INSN_IMM_12, reloc_place, result); + *(__le32 *)reloc_place = cpu_to_le32(result); + break; + case R_AARCH64_TLSDESC_CALL: + // this is a blr instruction, don't need to modify + break; + default: + log_err("\tunsupported RELA relocation: %llu\n", + ELF_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + log_debug("\t(after) *reloc_place = 0x%llx, result = 0x%llx\n", *(u64*)reloc_place, result); + } + return 0; + +overflow: + log_err("\toverflow in relocation type %d val 0x%Lx reloc 0x%llx\n", + (int)ELF_R_TYPE(rel[i].r_info), sym_addr, result); + return -ENOEXEC; +} + +bool is_got_rela_type(int type) +{ + if (type >= R_AARCH64_MOVW_GOTOFF_GO && type <= R_AARCH64_LD64_GOTPAGE_LO15) { + return true; + } + return false; +} + +#endif /* __aarch64__ */ diff --git a/upatch-manage/arch/patch_load.h b/upatch-manage/arch/patch_load.h new file mode 100644 index 00000000..1441392b --- /dev/null +++ b/upatch-manage/arch/patch_load.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 HUAWEI, Inc. + * + * Authors: + * renoseven + * + */ + +#ifndef _ARCH_PATCH_LOAD_H +#define _ARCH_PATCH_LOAD_H + +#include "../patch_load.h" +#include "../target_entity.h" +#include "../util.h" + +/* jmp table, solve limit for the jmp instruction, Used for both PLT/GOT */ +#if defined(__aarch64__) +struct upatch_jmp_table_entry { + unsigned long inst[2]; + unsigned long addr[2]; +}; +#else +struct upatch_jmp_table_entry { + unsigned long inst; + unsigned long addr; +}; +#endif + +#define JMP_ENTRY_SIZE (sizeof(unsigned long)) +#if defined(__arm__) +#define NORMAL_JMP_ENTRY_NUM 3 +#else +#define NORMAL_JMP_ENTRY_NUM 2 +#endif + +#define JMP_TABLE_GOT_ENTRY_SIZE (JMP_ENTRY_SIZE * NORMAL_JMP_ENTRY_NUM) + +#if defined(__x86_64__) +#define IFUNC_JMP_ENTRY_NUM 3 +// R_X86_64_PC32 uses a 32-bit signed offset, allowing a +/- 2 GiB range. +#define PATCH_LOAD_RANGE_LIMIT (1UL << 31) // 2 GiB (2^31) + +#elif defined(__aarch64__) +#define IFUNC_JMP_ENTRY_NUM 5 +#define PLT_JMP_ENTRY_NUM 4 +// R_AARCH64_JUMP26/CALL26 uses a 26-bit immediate, shifted left by 2, signed -> +/- 128 MiB range. +#define PATCH_LOAD_RANGE_LIMIT (1UL << 27) // 128 MiB (2^27) + +#elif defined(__arm__) +#define IFUNC_JMP_ENTRY_NUM 10 +// R_ARM_JUMP24/CALL uses a 24-bit immediate, shifted left by 2, signed -> +/- 32 MiB range. +#define PATCH_LOAD_RANGE_LIMIT (1UL << 25) // 32 MiB (2^25) +#endif + +#define JMP_TABLE_ENTRY_MAX_SIZE (JMP_ENTRY_SIZE * IFUNC_JMP_ENTRY_NUM) + +unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr); + +// write jmp addr in jmp table in text section, return the real jmp entry address in VMA +unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc); + +unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr); + +unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr); + +int apply_relocate_add(struct upatch_info *info, unsigned int relsec); + +#endif /* _ARCH_PATCH_LOAD_H */ diff --git a/upatch-manage/arch/riscv64/insn.h b/upatch-manage/arch/riscv64/insn.h deleted file mode 100644 index 8ab4daf7..00000000 --- a/upatch-manage/arch/riscv64/insn.h +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2024 laokz - */ - -#ifndef _ARCH_RISCV64_INSN_H -#define _ARCH_RISCV64_INSN_H - -static inline unsigned set_utype_imm(unsigned ins, unsigned long imm) -{ - /* + imm[11] to counteract lo12 sign extension in next instruction */ - unsigned long temp_imm = imm; - temp_imm += (temp_imm & 0x800) << 1; - return (temp_imm & 0xfffff000) | (ins & 0xfff); -} - -static inline unsigned set_itype_imm(unsigned ins, unsigned long imm) -{ - return ((imm & 0xfff) << 20) | (ins & 0xfffff); -} - -static inline unsigned set_stype_imm(unsigned ins, unsigned long imm) -{ - /* rs2 rs1 func opcode - ins: imm[11-8,7-5] 1,1111, 1111,1 111, imm[4-1,0] 111,1111 - ins mask 0 1 f f f 0 7 f - - imm bit no. 11-----5 4---0 - 1111,111 1,1111 - imm mask fe0 1f - - ==>imm bit no. 31----25 11--7 - */ - return (ins & 0x1fff07f) | - ((imm & 0xfe0) << (31 - 11)) | ((imm & 0x1f) << (11 - 4)); -} - -static inline unsigned set_jtype_imm(unsigned ins, unsigned long imm) -{ - /* - imm bit no. 20 19------12 11 10---------1 - 1, 1111,1111, 1 111,1111,111 0 - mask 100000 ff000 800 7fe - - ==>imm bit no. 31 19------12 20 30---------21 - */ - return (ins & 0xfff) | - ((imm & 0x100000) << (31 - 20)) | (imm & 0xff000) | - ((imm & 0x800) << (20 - 11)) | ((imm & 0x7fe) << (30 - 10)); -} - -static inline unsigned set_btype_imm(unsigned ins, unsigned long imm) -{ - /* rs2 rs1 func opcode - ins: imm[12 10-8,7-5] 1,1111, 1111,1 111, imm[4-1,11] 111,1111 - ins mask 0 1 f f f 0 7 f - - imm bit no. 12 11 10----5 4--1 - 1, 1 111,111 1,111 0 - imm mask 1000 800 7e0 1e - - ==>imm bit no. 31 7 30---25 11-8 - */ - return (ins & 0x01fff07f) | - ((imm & 0x1000) << (31 - 12)) | ((imm & 0x800) >> (11 - 7)) | - ((imm & 0x7e0) << (30 - 10)) | ((imm & 0x1e) << (11 - 4)); -} - -static inline unsigned short set_cjtype_imm(unsigned short ins, unsigned long imm) -{ - /* funct3 imm opcode - ins: 111 offset[11,4 9 8 10, 6 7 3 2, 1 5] 11 - ins mask e 0 0 3 - - imm bit no. 11 10 9-8 7 6 5 4 3-1 - 1 1 11, 1 1 1 1, 111 0 - imm mask 800 400 300 80 40 20 10 e - - ==>imm bit no. 12 8 10-9 6 7 2 11 5-3 - */ - return (ins & 0xe003) | - ((imm & 0x800) << (12 - 11)) | ((imm & 0x400) >> (10 - 8)) | - ((imm & 0x300) << (10 - 9)) | ((imm & 0x80) >> (7 - 6)) | - ((imm & 0x40) << (7 - 6)) | ((imm & 0x20) >> (5 - 2)) | - ((imm & 0x10) << (11 - 4)) | ((imm & 0xe) << (5 - 3)); -} - -/* only support C.LUI */ -static inline unsigned short set_citype_imm(unsigned short ins, unsigned long imm) -{ - /* funct3 imm[17] rd imm[16:12] opcode - ins: 111 imm[17], 1111,1 imm[16-14,13-12] 11 - ins mask e f 8 3 - - imm bit no. 17 16--12 - 1 1,1111 - imm mask 20000 1f000 - - ==>imm bit no. 12 6---2 - */ - return (ins & 0xef83) | - ((imm & 0x20000) >> (17 - 12)) | ((imm & 0x1f000) >> (16 - 6)); -} - -/* only support C.BEQZ C.BNEZ */ -static inline unsigned short set_cbtype_imm(unsigned short ins, unsigned long imm) -{ - /* funct3 imm[8 4 3] rs imm[7 6 2 1 5] opcode - ins: 111 0,0 0 11,1 0 0 0,0 0 11 - ins mask e 3 8 3 - - imm bit no. 8 , 7 6 5 4,3 2 1 0 - imm mask 100 c0 20 18 6 - - ==>imm bit no. 12 6-5 2 11-10 4-3 - */ - return (ins & 0xe383) | - ((imm & 0x100) << (12 - 8)) | ((imm & 0xc0) >> (7 - 6)) | - ((imm & 0x20) >> (5 - 2)) | ((imm & 0x18) << (11 - 4)) | - ((imm & 0x6) << (4 - 2)); -} - -#endif diff --git a/upatch-manage/arch/riscv64/process.h b/upatch-manage/arch/riscv64/process.h deleted file mode 100644 index 91926fa2..00000000 --- a/upatch-manage/arch/riscv64/process.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 ISCAS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef __PROCESS__ -#define __PROCESS__ - -#ifndef MAX_DISTANCE -#define MAX_DISTANCE 0x80000000 -#endif - -#endif diff --git a/upatch-manage/arch/riscv64/ptrace.c b/upatch-manage/arch/riscv64/ptrace.c deleted file mode 100644 index 2ccdc9ef..00000000 --- a/upatch-manage/arch/riscv64/ptrace.c +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 ISCAS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include - -#include "upatch-ptrace.h" - -int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc) -{ - struct iovec regs_iov; - struct user_regs_struct regs; - - regs_iov.iov_base = ®s; - regs_iov.iov_len = sizeof(regs); - - if (ptrace(PTRACE_GETREGSET, pid, - (void *)NT_PRSTATUS, (void *)®s_iov) < 0) { - log_error("Cannot get regs from %d\n", pid); - return -1; - } - *sp = (unsigned long)regs.sp; - *pc = (unsigned long)regs.pc; - return 0; -} - -static long read_gregs(int pid, struct user_regs_struct *regs) -{ - struct iovec data = {regs, sizeof(*regs)}; - if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &data) == -1) { - log_error("ptrace(PTRACE_GETREGSET)"); - return -1; - } - return 0; -} - -static long write_gregs(int pid, struct user_regs_struct *regs) -{ - struct iovec data = {regs, sizeof(*regs)}; - if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &data) == -1) { - log_error("ptrace(PTRACE_SETREGSET)"); - return -1; - } - return 0; -} - -long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *pctx, int nr, - unsigned long arg1, unsigned long arg2, - unsigned long arg3, unsigned long arg4, - unsigned long arg5, unsigned long arg6, - unsigned long *res) -{ - struct user_regs_struct regs; - unsigned char syscall[] = { - 0x73, 0x00, 0x00, 0x00, // ecall - 0x73, 0x00, 0x10, 0x00, // ebreak - }; - long ret; - - log_debug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); - regs.a7 = (unsigned long)nr; - regs.a0 = arg1; - regs.a1 = arg2; - regs.a2 = arg3; - regs.a3 = arg4; - regs.a4 = arg5; - regs.a5 = arg6; - - ret = upatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); - if (ret == 0) - *res = regs.a0; - - return ret; -} - -long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, - const unsigned char *code, size_t codelen, - struct user_regs_struct *pregs, - int (*func)(struct upatch_ptrace_ctx *pctx, - const void *data), - const void *data) -{ - struct user_regs_struct orig_regs, regs; - if (!codelen) { - log_error("Invalid codelen\n"); - return -1; - } - unsigned char *orig_code = (unsigned char *)malloc(sizeof(*orig_code) * codelen); - if (orig_code == NULL) { - log_error("Malloc orig_code failed\n"); - return -1; - } - long ret; - struct upatch_process *proc = pctx->proc; - unsigned long libc_base = proc->libc_base; - - ret = read_gregs(pctx->pid, &orig_regs); - if (ret < 0) { - free(orig_code); - return -1; - } - ret = upatch_process_mem_read(proc, libc_base, - (unsigned long *)orig_code, codelen); - if (ret < 0) { - log_error("can't peek original code - %d\n", pctx->pid); - free(orig_code); - return -1; - } - ret = upatch_process_mem_write(proc, (const unsigned long *)code, libc_base, - codelen); - if (ret < 0) { - log_error("can't poke syscall code - %d\n", pctx->pid); - goto poke_back; - } - - regs = orig_regs; - regs.pc = libc_base; - - copy_regs(®s, pregs); - - ret = write_gregs(pctx->pid, ®s); - if (ret < 0) { - goto poke_back; - } - - ret = func(pctx, data); - if (ret < 0) { - log_error("failed call to func\n"); - goto poke_back; - } - - ret = read_gregs(pctx->pid, ®s); - if (ret < 0) { - goto poke_back; - } - - ret = write_gregs(pctx->pid, &orig_regs); - if (ret < 0) { - goto poke_back; - } - - *pregs = regs; - -poke_back: - upatch_process_mem_write(proc, (unsigned long *)orig_code, libc_base, - codelen); - free(orig_code); - return ret; -} - -void copy_regs(struct user_regs_struct *dst, struct user_regs_struct *src) -{ -#define COPY_REG(x) dst->x = src->x - COPY_REG(a0); - COPY_REG(a1); - COPY_REG(a2); - COPY_REG(a3); - COPY_REG(a4); - COPY_REG(a5); - COPY_REG(a6); - COPY_REG(a7); -#undef COPY_REG -} - -#define UPATCH_INSN_LEN 8 -#define UPATCH_ADDR_LEN 8 -#define ORIGIN_INSN_LEN (UPATCH_INSN_LEN + UPATCH_ADDR_LEN) - -size_t get_origin_insn_len() -{ - return ORIGIN_INSN_LEN; -} - -size_t get_upatch_insn_len() -{ - return UPATCH_INSN_LEN; -} - -size_t get_upatch_addr_len() -{ - return UPATCH_ADDR_LEN; -} - -/* - * On RISC-V, there must be 3 instructors(12 bytes) to jump to - * arbitrary address. The core upatch-manage limit jump instructor - * to one long(8 bytes), for us is +-2G range. - */ -unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr) -{ - unsigned long offset; - unsigned int insn0, insn4; - - offset = new_addr - old_addr; - offset += (offset & 0x800) << 1; - insn0 = 0xf97 | (offset & 0xfffff000); // auipc t6, off[20] - insn4 = 0xf8067 | ((offset & 0xfff) << 20); // jalr zero, off[12](t6) - return (unsigned long)(insn0 | ((unsigned long)insn4 << 32)); -} diff --git a/upatch-manage/arch/riscv64/relocation.c b/upatch-manage/arch/riscv64/relocation.c deleted file mode 100644 index ba825992..00000000 --- a/upatch-manage/arch/riscv64/relocation.c +++ /dev/null @@ -1,246 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 ISCAS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - -#include "insn.h" -#include "upatch-relocation.h" -#include "upatch-resolve.h" - -/* - * In PCREL_LO12 relocation entity, its corresponding symbol's value - * points to the ..._HI20 instruction, where the LO12 part of the - * immediate is part of the ..._HI20 symbol value. - */ -static unsigned long find_pcrel_hi_value(GElf_Rela *r, int idx, GElf_Sym *st, unsigned long v) -{ - int i = idx; - r--; - for (; i > 0; i--, r--) { - if ((r->r_offset == v) && - ((GELF_R_TYPE(r->r_info) == R_RISCV_PCREL_HI20) || - (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GOT_HI20) || - (GELF_R_TYPE(r->r_info) == R_RISCV_TLS_GD_HI20) || - (GELF_R_TYPE(r->r_info) == R_RISCV_GOT_HI20))) - return st[GELF_R_SYM(r->r_info)].st_value; - } - - /* should never happen */ - log_error("Not found no. %d rela's corresponding HI20\n", idx); - return 0; -} - -/* - * The patch is a .o file, has only static relocations, all symbols - * have been resolved with our jump table act as got/plt. - */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wsign-conversion" -int apply_relocate_add(struct upatch_elf *uelf, unsigned int symindex, - unsigned int relsec) -{ - unsigned int i; - GElf_Sym *sym, *symtab; - char const *sym_name; - unsigned long uloc_sec; - void *loc; - void *uloc; - u64 val; - GElf_Shdr *shdrs = (void *)uelf->info.shdrs; - GElf_Rela *rel = (void *)shdrs[relsec].sh_addr; - - symtab = (GElf_Sym *)shdrs[symindex].sh_addr; - for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { - /* loc corresponds to P in the kernel space */ - loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addr + - rel[i].r_offset; - - /* uloc corresponds P in user space */ - uloc_sec = shdrs[shdrs[relsec].sh_info].sh_addralign; - uloc = (void *)uloc_sec + rel[i].r_offset; - - /* sym is the ELF symbol we're referring to */ - sym = symtab + GELF_R_SYM(rel[i].r_info); - if (GELF_ST_TYPE(sym->st_info) == STT_SECTION && - sym->st_shndx < uelf->info.hdr->e_shnum) - sym_name = uelf->info.shstrtab + - shdrs[sym->st_shndx].sh_name; - else - sym_name = uelf->strtab + sym->st_name; - - /* val corresponds to (S + A) */ - val = (s64)(sym->st_value + rel[i].r_addend); - log_debug( - "upatch: reloc symbol, name=%s, k_addr=0x%lx, u_addr=0x%lx, " - "r_offset=0x%lx, st_value=0x%lx, r_addend=0x%lx \n", - sym_name, shdrs[shdrs[relsec].sh_info].sh_addr, - uloc_sec, rel[i].r_offset, sym->st_value, rel[i].r_addend); - - /* Perform the static relocation. */ - switch (GELF_R_TYPE(rel[i].r_info)) { - case R_RISCV_NONE: - case R_RISCV_TPREL_ADD: - break; - - case R_RISCV_64: - *(unsigned long *)loc = val; - break; - - /* seems no need to recalculate as it should confined in the same func */ - case R_RISCV_BRANCH: - val -= (unsigned long)uloc; - if ((signed)val >= 4096 || (signed)val < -4096) - goto overflow; - *(unsigned *)loc = set_btype_imm(*(unsigned *)loc, val); - break; - - case R_RISCV_JAL: - val -= (unsigned long)uloc; - if ((signed)val >= (1 << 20) || (signed)val < -(1 << 20)) - goto overflow; - *(unsigned *)loc = set_jtype_imm(*(unsigned *)loc, val); - break; - - case R_RISCV_CALL: - case R_RISCV_CALL_PLT: - // in our jump table, must not overflow - val -= (unsigned long)uloc; - *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); - *(unsigned *)(loc + 4) = set_itype_imm(*(unsigned *)(loc + 4), val); - break; - - case R_RISCV_GOT_HI20: - case R_RISCV_TLS_GOT_HI20: - case R_RISCV_TLS_GD_HI20: - case R_RISCV_PCREL_HI20: - val -= (unsigned long)uloc; // fall through - case R_RISCV_HI20: - case R_RISCV_TPREL_HI20: - if ((long)val != (long)(int)val) - goto overflow; - *(unsigned *)loc = set_utype_imm(*(unsigned *)loc, val); - break; - - case R_RISCV_PCREL_LO12_I: - val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); - if (val == 0) - goto overflow; - val -= sym->st_value; // fall through - case R_RISCV_LO12_I: - case R_RISCV_TPREL_LO12_I: - *(unsigned *)loc = set_itype_imm(*(unsigned *)loc, val); - break; - - case R_RISCV_PCREL_LO12_S: - val = find_pcrel_hi_value(rel + i, i, symtab, sym->st_value - uloc_sec); - if (val == 0) - goto overflow; - val -= sym->st_value; // fall through - case R_RISCV_LO12_S: - case R_RISCV_TPREL_LO12_S: - *(unsigned *)loc = set_stype_imm(*(unsigned *)loc, val); - break; - - /* inner function label calculation, must not overflow */ - case R_RISCV_ADD8: - *(char *)loc += val; - break; - case R_RISCV_ADD16: - *(short *)loc += val; - break; - case R_RISCV_ADD32: - *(int *)loc += val; - break; - case R_RISCV_ADD64: - *(long *)loc += val; - break; - - case R_RISCV_SUB8: - *(char *)loc -= val; - break; - case R_RISCV_SUB16: - *(short *)loc -= val; - break; - case R_RISCV_SUB32: - *(int *)loc -= val; - break; - case R_RISCV_SUB64: - *(long *)loc -= val; - break; - - case R_RISCV_RVC_BRANCH: - val -= (unsigned long)uloc; - if ((signed)val >= 256 || (signed)val < -256) - goto overflow; - *(unsigned short *)loc = set_cbtype_imm(*(unsigned short *)loc, val); - break; - - case R_RISCV_RVC_JUMP: - val -= (unsigned long)uloc; - if ((signed)val >= 2048 || (signed)val < -2048) - goto overflow; - *(unsigned short *)loc = set_cjtype_imm(*(unsigned short *)loc, val); - break; - - case R_RISCV_RVC_LUI: - if ((signed)val >= (1 << 17) || (signed)val < -(1 << 17) || (val & 0x3f000) == 0) - goto overflow; - *(unsigned short *)loc = set_citype_imm(*(unsigned short *)loc, val); - break; - - case R_RISCV_SET8: - *(char *)loc = val; - break; - case R_RISCV_SET16: - *(short *)loc = val; - break; - case R_RISCV_32_PCREL: - case R_RISCV_PLT32: - val -= (unsigned long)uloc; // fall through - case R_RISCV_32: - case R_RISCV_SET32: - if ((long)val != (long)(int)val) - goto overflow; - *(int *)loc = val; - break; - - case R_RISCV_SUB6: - char w6 = (*(char *)loc - (char)val) & 0x3f; - *(char *)loc = (*(char *)loc & 0xc0) | w6; - break; - case R_RISCV_SET6: - *(char *)loc = (*(char *)loc & 0xc0) | (val & 0x3f); - break; - - default: - log_error("upatch: unsupported RELA relocation: %lu\n", - GELF_R_TYPE(rel[i].r_info)); - return -ENOEXEC; - } - } - return 0; - -overflow: - log_error("upatch: overflow in relocation type %d val %lx\n", - (int)GELF_R_TYPE(rel[i].r_info), val); - return -ENOEXEC; -} -#pragma GCC diagnostic pop diff --git a/upatch-manage/arch/riscv64/resolve.c b/upatch-manage/arch/riscv64/resolve.c deleted file mode 100644 index 0b4aa8ba..00000000 --- a/upatch-manage/arch/riscv64/resolve.c +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 ISCAS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include - -#include "log.h" -#include "upatch-ptrace.h" -#include "upatch-resolve.h" - -/* - * auipc t6,0x0 - * ld t6,16(t6) # addr - * jr t6 - * undefined - */ -#define RISCV64_JMP_TABLE_JUMP0 0x010fbf8300000f97 -#define RISCV64_JMP_TABLE_JUMP1 0x000f8067 - -struct upatch_jmp_table_entry { - unsigned long inst[2]; - unsigned long addr[2]; -}; - -unsigned int get_jmp_table_entry() -{ - return sizeof(struct upatch_jmp_table_entry); -} - -static unsigned long setup_jmp_table(struct upatch_elf *uelf, - unsigned long jmp_addr, - unsigned long origin_addr) -{ - struct upatch_jmp_table_entry *table = - uelf->core_layout.kbase + uelf->jmp_offs; - unsigned int index = uelf->jmp_cur_entry; - if (index >= uelf->jmp_max_entry) { - log_error("jmp table overflow\n"); - return 0; - } - - table[index].inst[0] = RISCV64_JMP_TABLE_JUMP0; - table[index].inst[1] = RISCV64_JMP_TABLE_JUMP1; - table[index].addr[0] = jmp_addr; - table[index].addr[1] = origin_addr; - uelf->jmp_cur_entry++; - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - index * sizeof(struct upatch_jmp_table_entry)); -} - -unsigned long setup_got_table(struct upatch_elf *uelf, - unsigned long jmp_addr, - unsigned long tls_addr) -{ - struct upatch_jmp_table_entry *table = - uelf->core_layout.kbase + uelf->jmp_offs; - unsigned int index = uelf->jmp_cur_entry; - - if (index >= uelf->jmp_max_entry) { - log_error("got table overflow\n"); - return 0; - } - - table[index].inst[0] = jmp_addr; - table[index].inst[1] = tls_addr; - table[index].addr[0] = 0xffffffff; - table[index].addr[1] = 0xffffffff; - uelf->jmp_cur_entry++; - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - index * sizeof(struct upatch_jmp_table_entry)); -} - -unsigned long insert_plt_table(struct upatch_elf *uelf, struct object_file *obj, - unsigned long r_type __attribute__((unused)), unsigned long addr) -{ - unsigned long jmp_addr = 0xffffffff; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, - sizeof(jmp_addr))) { - log_error("copy address failed\n"); - goto out; - } - - elf_addr = setup_jmp_table(uelf, jmp_addr, (unsigned long)addr); - - log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, - jmp_addr, tls_addr); - -out: - return elf_addr; -} - -unsigned long insert_got_table(struct upatch_elf *uelf, struct object_file *obj, - unsigned long r_type, unsigned long addr) -{ - unsigned long jmp_addr = 0xffffffff; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, - sizeof(jmp_addr))) { - log_error("copy address failed\n"); - goto out; - } - - /* - * Addr with this type means the symbol is a dynamic TLS variable. - * Addr points to a GOT entry(16 bytes) having type - * - * typedef struct { - * unsigned long int ti_module; - * unsigned long int ti_offset; - * } tls_index; - * - * We also need copy ti_offset to our jump table. - * - * The corresponding symbol will associate with TLS_GD_HI20 - * relocation type, using this tls_index as argument to call - * `void *__tls_get_addr (tls_index *ti)` to resolve the real address. - */ - if (r_type == R_RISCV_TLS_DTPMOD64 && - upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), - &tls_addr, sizeof(tls_addr))) { - log_error("copy address failed\n"); - goto out; - } - - elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); - - log_debug("0x%lx: jmp_addr=0x%lx, tls_addr=0x%lx\n", elf_addr, - jmp_addr, tls_addr); - -out: - return elf_addr; -} diff --git a/upatch-manage/arch/x86/patch_load.c b/upatch-manage/arch/x86/patch_load.c new file mode 100644 index 00000000..b3bcdc79 --- /dev/null +++ b/upatch-manage/arch/x86/patch_load.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * setup jmp table and do relocation in x86 + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef __x86_64__ + +#include + +#include "../patch_load.h" + +#ifndef R_X86_64_DTPMOD64 +#define R_X86_64_DTPMOD64 16 +#endif + +#ifndef R_X86_64_TLSGD +#define R_X86_64_TLSGD 19 +#endif + +#ifndef R_X86_64_GOTTPOFF +#define R_X86_64_GOTTPOFF 22 +#endif + +#ifndef R_X86_64_TPOFF32 +#define R_X86_64_TPOFF32 23 +#endif + +#ifndef R_X86_64_GOTPCRELX +#define R_X86_64_GOTPCRELX 41 +#endif + +#ifndef R_X86_64_REX_GOTPCRELX +#define R_X86_64_REX_GOTPCRELX 42 +#endif + +#define X86_64_JUMP_TO_FUNC 0x90900000000225ff // jmp [rip+2]; nop; nop + +/* For IFUNC(indirect function), the symbol value is point to the resolve function + * We should call resolve func and get the real func address in rax + * In x86_64 function calling convention, we should save all reg that is already saved args. + * For all IFUNC type func in glibc, it will use at most 3 args, so we only save rdi, rsi, rdx + * 0: push rdi + * 1: push rsi + * 2: push rdx + * 3: call QWORD PTR [rip+0x7] + * 9: pop rdx + * a: pop rsi + * b: pop rdi + * c: jmp rax + * e: nop + * f: nop + * 10-18:

+ */ + +#define X86_64_CALL_IFUNC_1 0x00000715FF525657 +#define X86_64_CALL_IFUNC_2 0x9090E0FF5F5E5A00 + +unsigned long setup_jmp_table(struct upatch_info *info, unsigned long jmp_addr, bool is_ifunc) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + int entry_num = is_ifunc ? IFUNC_JMP_ENTRY_NUM : NORMAL_JMP_ENTRY_NUM; + if (table->cur + entry_num > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, entry_num); + return 0; + } + + if (is_ifunc) { + jmp[index] = X86_64_CALL_IFUNC_1; + jmp[index + 1] = X86_64_CALL_IFUNC_2; + jmp[index + 2] = jmp_addr; + } else { + jmp[index] = X86_64_JUMP_TO_FUNC; + jmp[index + 1] = jmp_addr; + } + table->cur += entry_num; + + return info->layout.base + table->off + index * JMP_ENTRY_SIZE; +} + +/* + * Jmp table records address and used call instruction to execute it. + * So, we need 'Inst' and 'addr' + * GOT only need record address and resolve it by [got_addr]. + * To simplify design, use same table for both jmp table and GOT. + */ +unsigned long setup_got_table(struct upatch_info *info, unsigned long jmp_addr, unsigned long tls_addr) +{ + struct jmp_table *table = &info->layout.table; + unsigned long *jmp = info->layout.kbase + table->off; + unsigned int index = table->cur; + unsigned long entry_addr = info->layout.base + table->off + index * JMP_ENTRY_SIZE; + if (table->cur + NORMAL_JMP_ENTRY_NUM > table->max) { + log_err("jmp table overflow, cur = %d, max = %d, num = %d\n", + table->cur, table->max, NORMAL_JMP_ENTRY_NUM); + return 0; + } + + jmp[index] = jmp_addr; + jmp[index + 1] = tls_addr; + table->cur += NORMAL_JMP_ENTRY_NUM; + + log_debug("\tsetup got table at 0x%lx -> 0x%lx, tls_addr = 0x%lx\n", + entry_addr, jmp_addr, tls_addr); + + return entry_addr; +} + +unsigned long insert_plt_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +{ + unsigned long jmp_addr; + unsigned long elf_addr = 0; + + if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + elf_addr = setup_jmp_table(info, jmp_addr, false); + + log_debug("PLT: 0x%lx -> 0x%lx\n", elf_addr, jmp_addr); + +out: + return elf_addr; +} + + +unsigned long insert_got_table(struct upatch_info *info, unsigned long r_type, void __user *addr) +{ + unsigned long jmp_addr; + unsigned long tls_addr = 0xffffffff; + unsigned long elf_addr = 0; + + if (copy_from_user((void *)&jmp_addr, addr, sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + /* + * R_X86_64_TLSGD: allocate two contiguous entries in the GOT to hold a tls_index structure + * tls_index has two unsigned long, the first one is R_X86_64_DTPMOD64. + */ + if (r_type == R_X86_64_DTPMOD64 && + copy_from_user((void *)&tls_addr, addr + sizeof(unsigned long), sizeof(unsigned long))) { + log_err("copy address failed\n"); + goto out; + } + + elf_addr = setup_got_table(info, jmp_addr, tls_addr); + +out: + return elf_addr; +} + +int apply_relocate_add(struct upatch_info *info, unsigned int relsec) +{ + Elf_Shdr *sechdrs = info->sechdrs; + const char *strtab = info->strtab; + unsigned int symindex = info->index.sym; + unsigned int i; + Elf_Rela *rel = (void *)sechdrs[relsec].sh_addr; + Elf_Sym *sym; + void *reloc_place, *ureloc_place; + u64 sym_addr, got; + const char *name; + Elf_Addr tls_size; + + unsigned int reloced_sec = sechdrs[relsec].sh_info; + + // sh_addr = kdest, is the section start in hot patch kalloc memory + // sh_addralign = dest, is the section start in VMA pole + void *sec_kaddr = (void *)sechdrs[reloced_sec].sh_addr; + void *sec_vaddr = (void *)sechdrs[reloced_sec].sh_addralign; + + log_debug("Applying relocate section %u to %u\n", relsec, reloced_sec); + log_debug("section %d: kernel address = 0x%llx, virtual address = 0x%llx\n", + reloced_sec, (u64)sec_kaddr, (u64)sec_vaddr); + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + /* This is where to make the change, calculate it from kernel address. */ + + /* corresponds P in the kernel space */ + reloc_place = sec_kaddr + rel[i].r_offset; + + /* corresponds P in user space */ + ureloc_place = sec_vaddr + rel[i].r_offset; + + /* This is the symbol it is referring to. Note that all + undefined symbols have been resolved. */ + sym = (Elf_Sym *)sechdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info); + name = strtab + sym->st_name; + + /* src corresponds to (S + A) */ + sym_addr = sym->st_value + rel[i].r_addend; + + log_debug("'%s'\t type %d st_value 0x%llx r_addend %ld r_offset 0x%llx\n", + name, (int)ELF_R_TYPE(rel[i].r_info), sym->st_value, (long int)rel[i].r_addend, + rel[i].r_offset); + log_debug("\t(S + A) = 0x%llx \tP(kernel) = 0x%Lx \tP(user) = 0x%Lx\n", + sym_addr, (u64)reloc_place, (u64)ureloc_place); + log_debug("\t(before) *reloc_place = 0x%llx\n", *(u64*)reloc_place); + switch (ELF_R_TYPE(rel[i].r_info)) { + case R_X86_64_NONE: + break; + case R_X86_64_64: + if (*(u64 *)reloc_place != 0) { + goto invalid_relocation; + } + memcpy(reloc_place, &sym_addr, sizeof(u64)); + break; + case R_X86_64_32: + if (*(u32 *)reloc_place != 0) { + goto invalid_relocation; + } + memcpy(reloc_place, &sym_addr, sizeof(u32)); + if (sym_addr != *(u32 *)reloc_place + && (ELF_ST_TYPE(sym->st_info) != STT_SECTION)) { + goto overflow; + } + break; + case R_X86_64_32S: + if (*(s32 *)reloc_place != 0) { + goto invalid_relocation; + } + memcpy(reloc_place, &sym_addr, sizeof(u32)); + if ((s64)sym_addr != *(s32 *)reloc_place + && (ELF_ST_TYPE(sym->st_info) != STT_SECTION)) { + goto overflow; + } + break; + case R_X86_64_TLSGD: + case R_X86_64_GOTTPOFF: + case R_X86_64_GOTPCRELX: + case R_X86_64_REX_GOTPCRELX: + /* get GOT address */ + got = get_or_setup_got_entry(info, sym); + if (got == 0) { + goto overflow; + } + // G + GOT + A + sym_addr = got + rel[i].r_addend; + // G + GOT + A - P + fallthrough; + case R_X86_64_PC32: + case R_X86_64_PLT32: + if (*(u32 *)reloc_place != 0) { + goto invalid_relocation; + } + sym_addr -= (u64)ureloc_place; + memcpy(reloc_place, &sym_addr, sizeof(u32)); + break; + case R_X86_64_PC64: + if (*(u64 *)reloc_place != 0) { + goto invalid_relocation; + } + sym_addr -= (u64)ureloc_place; + memcpy(reloc_place, &sym_addr, sizeof(u64)); + break; + case R_X86_64_TPOFF32: + tls_size = ALIGN(info->running_elf.meta->tls_size, + info->running_elf.meta->tls_align); + // %fs + val - tls_size + if (sym_addr >= tls_size) { + goto overflow; + } + sym_addr -= (u64)tls_size; + memcpy(reloc_place, &sym_addr, sizeof(u32)); + break; + default: + log_err("\tUnknown rela relocation: %llu\n", ELF_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + log_debug("\t(after) *reloc_place = 0x%llx\n", *(u64*)reloc_place); + } + return 0; + +invalid_relocation: + log_err("\tSkipping invalid relocation target, \ + existing value is nonzero for type %d, loc %p, name %s\n", + (int)ELF_R_TYPE(rel[i].r_info), reloc_place, name); + return -ENOEXEC; + +overflow: + log_err("\toverflow in relocation type %d name %s\n", + (int)ELF_R_TYPE(rel[i].r_info), name); + return -ENOEXEC; +} + +bool is_got_rela_type(int type) +{ + switch (type) { + case R_X86_64_TLSGD: + case R_X86_64_GOTTPOFF: + case R_X86_64_GOTPCRELX: + case R_X86_64_REX_GOTPCRELX: + return true; + default: + break; + } + return false; +} + +#endif /* __x86_64__ */ diff --git a/upatch-manage/arch/x86_64/ptrace.c b/upatch-manage/arch/x86_64/ptrace.c deleted file mode 100644 index 2a84a2f4..00000000 --- a/upatch-manage/arch/x86_64/ptrace.c +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include - -#include -#include -#include - -#include "upatch-ptrace.h" - -#define UPATCH_INSN_LEN 6 -#define UPATCH_ADDR_LEN 8 -#define ORIGIN_INSN_LEN (UPATCH_INSN_LEN + UPATCH_ADDR_LEN) - -int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc) -{ - struct user_regs_struct regs; - - if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) < 0) { - log_error("Cannot get regs from %d\n", pid); - return -1; - } - *sp = (unsigned long)regs.rsp; - *pc = (unsigned long)regs.rip; - return 0; -} - -long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *pctx, int nr, - unsigned long arg1, unsigned long arg2, - unsigned long arg3, unsigned long arg4, - unsigned long arg5, unsigned long arg6, - unsigned long *res) -{ - struct user_regs_struct regs; - - unsigned char syscall[] = { - 0x0f, 0x05, /* syscall */ - 0xcc, /* int3 */ - }; - long ret; - - memset(®s, 0, sizeof(struct user_regs_struct)); - log_debug("Executing syscall %d (pid %d)...\n", nr, pctx->pid); - regs.rax = (unsigned long long)nr; - regs.rdi = arg1; - regs.rsi = arg2; - regs.rdx = arg3; - regs.r10 = arg4; - regs.r8 = arg5; - regs.r9 = arg6; - - ret = upatch_execute_remote(pctx, syscall, sizeof(syscall), ®s); - if (ret == 0) { - *res = regs.rax; - } - - return ret; -} - -long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, - const unsigned char *code, size_t codelen, - struct user_regs_struct *pregs, - int (*func)(struct upatch_ptrace_ctx *pctx, const void *data), - const void *data) -{ - long ret; - - struct user_regs_struct orig_regs; - struct user_regs_struct regs; - - struct upatch_process *proc = pctx->proc; - unsigned long libc_base = proc->libc_base; - - unsigned char *orig_code = (unsigned char *)malloc( - sizeof(*orig_code) * codelen); - if (orig_code == NULL) { - log_error("Malloc orig_code failed\n"); - return -1; - } - - ret = ptrace(PTRACE_GETREGS, pctx->pid, NULL, &orig_regs); - if (ret < 0) { - log_error("can't get regs - %d\n", pctx->pid); - free(orig_code); - return -1; - } - - ret = upatch_process_mem_read(proc, libc_base, - (unsigned long *)orig_code, codelen); - if (ret < 0) { - log_error("can't peek original code - %d\n", pctx->pid); - free(orig_code); - return -1; - } - - ret = upatch_process_mem_write(proc, code, libc_base, codelen); - if (ret < 0) { - log_error("can't poke syscall code - %d\n", pctx->pid); - goto poke_back; - } - - regs = orig_regs; - regs.rip = libc_base; - copy_regs(®s, pregs); - - ret = ptrace(PTRACE_SETREGS, pctx->pid, NULL, ®s); - if (ret < 0) { - log_error("can't set regs - %d\n", pctx->pid); - goto poke_back; - } - - ret = func(pctx, data); - if (ret < 0) { - log_error("failed call to func\n"); - goto poke_back; - } - - ret = ptrace(PTRACE_GETREGS, pctx->pid, NULL, ®s); - if (ret < 0) { - log_error("can't get updated regs - %d\n", pctx->pid); - goto poke_back; - } - - ret = ptrace(PTRACE_SETREGS, pctx->pid, NULL, &orig_regs); - if (ret < 0) { - log_error("can't restore regs - %d\n", pctx->pid); - goto poke_back; - } - - *pregs = regs; - -poke_back: - upatch_process_mem_write(proc, (unsigned long *)orig_code, - libc_base, codelen); - free(orig_code); - - return ret; -} - -void copy_regs(struct user_regs_struct *dst, struct user_regs_struct *src) -{ -#define COPY_REG(x) dst->x = src->x - COPY_REG(r15); - COPY_REG(r14); - COPY_REG(r13); - COPY_REG(r12); - COPY_REG(rbp); - COPY_REG(rbx); - COPY_REG(r11); - COPY_REG(r10); - COPY_REG(r9); - COPY_REG(r8); - COPY_REG(rax); - COPY_REG(rcx); - COPY_REG(rdx); - COPY_REG(rsi); - COPY_REG(rdi); -#undef COPY_REG -} - -size_t get_origin_insn_len() -{ - return ORIGIN_INSN_LEN; -} - -size_t get_upatch_insn_len() -{ - return UPATCH_INSN_LEN; -} - -size_t get_upatch_addr_len() -{ - return UPATCH_ADDR_LEN; -} - -unsigned long get_new_insn(void) -{ - // ASM: jmp word ptr [di] (FF25 0000 0000 0000) - return 0x25FF; -} diff --git a/upatch-manage/arch/x86_64/relocation.c b/upatch-manage/arch/x86_64/relocation.c deleted file mode 100644 index e492b1e9..00000000 --- a/upatch-manage/arch/x86_64/relocation.c +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include - -#include "upatch-relocation.h" - -int apply_relocate_add(struct upatch_elf *uelf, unsigned int symindex, - unsigned int relsec) -{ - unsigned int i; - GElf_Sym *sym; - void *loc; - void *real_loc; - u64 val; - const char *sym_name; - GElf_Xword tls_size; - GElf_Shdr *shdrs = (void *)uelf->info.shdrs; - GElf_Rela *rel = (void *)shdrs[relsec].sh_addr; - - log_debug("Applying relocate section %u to %u\n", relsec, - shdrs[relsec].sh_info); - - for (i = 0; i < shdrs[relsec].sh_size / sizeof(*rel); i++) { - /* This is where to make the change, calculate it from kernel address */ - loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addr + rel[i].r_offset; - real_loc = (void *)shdrs[shdrs[relsec].sh_info].sh_addralign + - rel[i].r_offset; - - /* This is the symbol it is referring to. Note that all - undefined symbols have been resolved. */ - sym = (GElf_Sym *)shdrs[symindex].sh_addr + GELF_R_SYM(rel[i].r_info); - if (GELF_ST_TYPE(sym[i].st_info) == STT_SECTION && - sym->st_shndx < uelf->info.hdr->e_shnum) { - sym_name = uelf->info.shstrtab + shdrs[sym->st_shndx].sh_name; - } else { - sym_name = uelf->strtab + sym->st_name; - } - - log_debug("type %d st_value %lx r_addend %lx loc %lx\n", - (int)GELF_R_TYPE(rel[i].r_info), sym->st_value, - rel[i].r_addend, (u64)loc); - - val = sym->st_value + (unsigned long)rel[i].r_addend; - switch (GELF_R_TYPE(rel[i].r_info)) { - case R_X86_64_NONE: - break; - case R_X86_64_64: - if (*(u64 *)loc != 0) { - goto invalid_relocation; - } - memcpy(loc, &val, 8); - break; - case R_X86_64_32: - if (*(u32 *)loc != 0) { - goto invalid_relocation; - } - memcpy(loc, &val, 4); - if (val != *(u32 *)loc && - (GELF_ST_TYPE(sym->st_info) != STT_SECTION)) { - goto overflow; - } - break; - case R_X86_64_32S: - if (*(s32 *)loc != 0) { - goto invalid_relocation; - } - memcpy(loc, &val, 4); - if ((s64)val != *(s32 *)loc && - (GELF_ST_TYPE(sym->st_info) != STT_SECTION)) { - goto overflow; - } - break; - case R_X86_64_TLSGD: - case R_X86_64_GOTTPOFF: - case R_X86_64_GOTPCRELX: - case R_X86_64_REX_GOTPCRELX: - if (sym->st_value == 0) { - goto overflow; - } - /* G + GOT + A */ - val = sym->st_value + (unsigned long)rel[i].r_addend; - /* fall through */ - case R_X86_64_PC32: - case R_X86_64_PLT32: - if (*(u32 *)loc != 0) { - goto invalid_relocation; - } - val -= (u64)real_loc; - memcpy(loc, &val, 4); - break; - case R_X86_64_PC64: - if (*(u64 *)loc != 0) { - goto invalid_relocation; - } - val -= (u64)real_loc; - memcpy(loc, &val, 8); - break; - case R_X86_64_TPOFF32: - tls_size = ALIGN(uelf->relf->tls_size, uelf->relf->tls_align); - // %fs + val - tls_size - if (val >= tls_size) { - goto overflow; - } - val -= (u64)tls_size; - memcpy(loc, &val, 4); - break; - default: - log_error("Unknown rela relocation: %lu\n", - GELF_R_TYPE(rel[i].r_info)); - return -ENOEXEC; - } - } - return 0; - -invalid_relocation: - log_error("upatch: Skipping invalid relocation target, \ - existing value is nonzero for type %d, loc %p, name %s\n", - (int)GELF_R_TYPE(rel[i].r_info), loc, sym_name); - return -ENOEXEC; - -overflow: - log_error("upatch: overflow in relocation type %d name %s\n", - (int)GELF_R_TYPE(rel[i].r_info), sym_name); - return -ENOEXEC; -} diff --git a/upatch-manage/arch/x86_64/resolve.c b/upatch-manage/arch/x86_64/resolve.c deleted file mode 100644 index 405c90c8..00000000 --- a/upatch-manage/arch/x86_64/resolve.c +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - -#include "upatch-ptrace.h" -#include "upatch-resolve.h" - -#define X86_64_JUMP_TABLE_JMP 0x90900000000225ff /* jmp [rip+2]; nop; nop */ - -struct upatch_jmp_table_entry { - unsigned long inst; - unsigned long addr; -}; - -unsigned int get_jmp_table_entry(void) -{ - return sizeof(struct upatch_jmp_table_entry); -} - -static unsigned long setup_jmp_table(struct upatch_elf *uelf, - unsigned long jmp_addr) -{ - struct upatch_jmp_table_entry *table = uelf->core_layout.kbase + - uelf->jmp_offs; - - unsigned int index = uelf->jmp_cur_entry; - if (index >= uelf->jmp_max_entry) { - log_error("jmp table overflow\n"); - return 0; - } - - table[index].inst = X86_64_JUMP_TABLE_JMP; - table[index].addr = jmp_addr; - uelf->jmp_cur_entry++; - - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - index * sizeof(struct upatch_jmp_table_entry)); -} - -/* - * Jmp tabale records address and used call instruction to execute it. - * So, we need 'Inst' and 'addr' - * GOT only need record address and resolve it by [got_addr]. - * To simplify design, use same table for both jmp table and GOT. - */ -unsigned long setup_got_table(struct upatch_elf *uelf, - unsigned long jmp_addr, unsigned long tls_addr) -{ - struct upatch_jmp_table_entry *table = uelf->core_layout.kbase + - uelf->jmp_offs; - - unsigned int index = uelf->jmp_cur_entry; - if (index >= uelf->jmp_max_entry) { - log_error("got table overflow\n"); - return 0; - } - - table[index].inst = jmp_addr; - table[index].addr = tls_addr; - uelf->jmp_cur_entry++; - - return (unsigned long)(uelf->core_layout.base + uelf->jmp_offs + - index * sizeof(struct upatch_jmp_table_entry)); -} - -unsigned long insert_plt_table(struct upatch_elf *uelf, struct object_file *obj, - unsigned long r_type, unsigned long addr) -{ - unsigned long jmp_addr; - unsigned long elf_addr = 0; - - (void)r_type; - if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, sizeof(jmp_addr))) { - log_error("copy address failed\n"); - goto out; - } - - elf_addr = setup_jmp_table(uelf, jmp_addr); - log_debug("0x%lx: jmp_addr=0x%lx\n", elf_addr, jmp_addr); - -out: - return elf_addr; -} - -unsigned long insert_got_table(struct upatch_elf *uelf, struct object_file *obj, - unsigned long r_type, unsigned long addr) -{ - unsigned long jmp_addr; - unsigned long tls_addr = 0xffffffff; - unsigned long elf_addr = 0; - - if (upatch_process_mem_read(obj->proc, addr, &jmp_addr, sizeof(jmp_addr))) { - log_error("copy address failed\n"); - goto out; - } - - /* - * R_X86_64_TLSGD: allocate two contiguous entries in the GOT to hold a - * tls_index structure tls_index has two unsigned long, the first one is - * R_X86_64_DTPMOD64. - */ - if (r_type == R_X86_64_DTPMOD64 && - upatch_process_mem_read(obj->proc, addr + sizeof(unsigned long), - &tls_addr, sizeof(tls_addr))) { - log_error("copy address failed\n"); - goto out; - } - - if (uelf->relf->info.is_dyn && !uelf->relf->info.is_pie) { - elf_addr = setup_got_table(uelf, jmp_addr, tls_addr); - log_debug("0x%lx: jmp_addr=0x%lx\n", elf_addr, jmp_addr); - } else { - /* - * For non-dynamic library files, global variables are not placed in the GOT table - */ - elf_addr = jmp_addr; - log_debug("For non-dynamic library: jmp_addr=0x%lx\n", jmp_addr); - } - -out: - return elf_addr; -} diff --git a/upatch-manage/ioctl_dev.c b/upatch-manage/ioctl_dev.c new file mode 100644 index 00000000..c42778c4 --- /dev/null +++ b/upatch-manage/ioctl_dev.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch_manage kernel module + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ioctl_dev.h" +#include "patch_entity.h" +#include "patch_manage.h" +#include "util.h" + +struct patch_load_request { + const char *patch_file; + const char *target_elf; +}; + +long handle_ioctl(struct file *file, unsigned int code, unsigned long arg); + +static const struct file_operations UPATCH_DEV_OPS = { + .owner = THIS_MODULE, + .unlocked_ioctl = handle_ioctl, +}; + +static struct miscdevice upatch_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = UPATCH_DEV_NAME, + .fops = &UPATCH_DEV_OPS, + .mode = UPATCH_DEV_MODE, +}; + +static char *vmalloc_string_from_user(void __user *addr) +{ + size_t len; + char *buf; + int ret; + + if (addr == 0) { + return ERR_PTR(-EINVAL); + } + + len = strnlen_user(addr, MAX_ARG_STRLEN); + if (len > PATH_MAX) { + return ERR_PTR(-EOVERFLOW); + } + + buf = vmalloc(len); + if (!buf) { + log_err("failed to vmalloc string, len=0x%lx\n", len); + return ERR_PTR(-ENOMEM); + } + + ret = copy_from_user(buf, addr, len); + if (ret) { + VFREE_CLEAR(buf); + return ERR_PTR(ret); + } + + return buf; +} + +static int get_load_para_from_user(void __user *user_addr, struct patch_load_request *res) +{ + struct patch_load_request req; + int error; + int ret; + + ret = copy_from_user(&req, user_addr, sizeof(struct patch_load_request)); + if (ret) { + log_err("failed to get target elf path, ret=%d\n", ret); + return -EINVAL; + } + + res->target_elf = vmalloc_string_from_user((void __user *)req.target_elf); + if (IS_ERR(res->target_elf)) { + error = PTR_ERR(res->target_elf); + log_err("failed to get target elf path, ret=%d\n", error); + return error; + } + + res->patch_file = vmalloc_string_from_user((void __user *)req.patch_file); + if (IS_ERR(res->patch_file)) { + error = PTR_ERR(res->patch_file); + log_err("failed to get patch file path, ret=%d\n", error); + vfree(res->patch_file); + return error; + } + + return 0; +} + +static int ioctl_get_patch_status(void __user * user_addr) +{ + int ret; + + char *patch = vmalloc_string_from_user(user_addr); + + if (IS_ERR(patch)) { + log_err("failed to get patch file path\n"); + return PTR_ERR(patch); + } + + ret = upatch_status(patch); + log_debug("patch '%s' is %s\n", patch, patch_status(ret)); + + vfree(patch); + return ret; +} + +static int ioctl_load_patch(void __user * user_addr) +{ + int ret; + struct patch_load_request req; + + if (!try_module_get(THIS_MODULE)) { + log_err("cannot increase '%s' refcnt!", THIS_MODULE->name); + return -ENODEV; + } + + ret = get_load_para_from_user(user_addr, &req); + if (ret) { + log_err("failed to get patch file path\n"); + module_put(THIS_MODULE); + return ret; + } + + ret = upatch_load(req.patch_file, req.target_elf); + if (ret) { + log_err("failed to load '%s' for '%s', ret=%d\n", + req.patch_file, req.target_elf, ret); + module_put(THIS_MODULE); + } + + vfree(req.patch_file); + vfree(req.target_elf); + return ret; +} + +static int ioctl_active_patch(void __user * user_addr) +{ + int ret; + char *patch = vmalloc_string_from_user(user_addr); + + if (IS_ERR(patch)) { + log_err("failed to get patch file path\n"); + return PTR_ERR(patch); + } + + ret = upatch_active(patch); + if (ret) { + log_err("failed to active patch '%s', ret=%d\n", patch, ret); + } + + vfree(patch); + return ret; +} + +static int ioctl_deactive_patch(void __user * user_addr) +{ + int ret; + char *patch = vmalloc_string_from_user(user_addr); + + if (IS_ERR(patch)) { + log_err("failed to get patch file path\n"); + return PTR_ERR(patch); + } + + ret = upatch_deactive(patch); + if (ret) { + log_err("failed to deactive patch '%s', ret=%d\n", patch, ret); + } + + vfree(patch); + return ret; +} + +static int ioctl_remove_patch(void __user * user_addr) +{ + int ret; + char *patch = vmalloc_string_from_user(user_addr); + + if (IS_ERR(patch)) { + log_err("failed to get patch file path\n"); + return PTR_ERR(patch); + } + + ret = upatch_remove(patch); + if (ret) { + log_err("failed to remove patch %s, ret=%d\n", patch, ret); + } else { + module_put(THIS_MODULE); + } + + vfree(patch); + return ret; +} + +long handle_ioctl(struct file *file, unsigned int code, unsigned long arg) +{ + unsigned int type = _IOC_TYPE(code); + unsigned int nr = _IOC_NR(code); + void __user *argp = (void __user *)arg; + + if (type != UPATCH_MAGIC) { + log_err("invalid ioctl type 0x%x\n", type); + return -EINVAL; + } + + switch (nr) { + case UPATCH_STATUS: + return ioctl_get_patch_status(argp); + + case UPATCH_LOAD: + return ioctl_load_patch(argp); + + case UPATCH_ACTIVE: + return ioctl_active_patch(argp); + + case UPATCH_DEACTIVE: + return ioctl_deactive_patch(argp); + + case UPATCH_REMOVE: + return ioctl_remove_patch(argp); + + default: + log_err("invalid ioctl nr 0x%x\n", nr); + return -EINVAL; + } + + return 0; +} + +int __init ioctl_device_init(void) +{ + return misc_register(&upatch_dev); +} + +void __exit ioctl_device_exit(void) +{ + misc_deregister(&upatch_dev); +} diff --git a/upatch-manage/ioctl_dev.h b/upatch-manage/ioctl_dev.h new file mode 100644 index 00000000..ad0b82d1 --- /dev/null +++ b/upatch-manage/ioctl_dev.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 HUAWEI, Inc. + * + * Authors: + * Longjun Luo + * + */ + +#ifndef _UPATCH_MANAGE_IOCTL_DEV_H +#define _UPATCH_MANAGE_IOCTL_DEV_H + +#include + +#define UPATCH_DEV_NAME "upatch_manage" +#define UPATCH_DEV_PATH "/dev/upatch_manage" +#define UPATCH_DEV_MODE 0600 + +#define UPATCH_MAGIC 0xE5 + +enum { + UPATCH_LOAD = 1, + UPATCH_ACTIVE, + UPATCH_DEACTIVE, + UPATCH_REMOVE, + UPATCH_STATUS, +}; + +#define _UPATCH_IOCTL(cmd, type) _IOW(UPATCH_MAGIC, cmd, type) + +#define UPATCH_LOAD_IOCTL _UPATCH_IOCTL(UPATCH_LOAD, const struct load_request *) +#define UPATCH_ACTIVE_IOCTL _UPATCH_IOCTL(UPATCH_ACTIVE, const char *) +#define UPATCH_DEACTIVE_IOCTL _UPATCH_IOCTL(UPATCH_DEACTIVE, const char *) +#define UPATCH_REMOVE_IOCTL _UPATCH_IOCTL(UPATCH_REMOVE, const char *) +#define UPATCH_STATUS_IOCTL _UPATCH_IOCTL(UPATCH_STATUS, const char *) + +int __init ioctl_device_init(void); +void __exit ioctl_device_exit(void); + +#endif // _UPATCH_MANAGE_IOCTL_DEV_H diff --git a/upatch-manage/kernel_compat.c b/upatch-manage/kernel_compat.c new file mode 100644 index 00000000..f0a98022 --- /dev/null +++ b/upatch-manage/kernel_compat.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch_manage kernel module + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0) + #include + #define VMA_USE_MAPLE_TREE +#endif + +#include "kernel_compat.h" +#include "util.h" + +__always_inline void +upatch_vma_iter_init(struct upatch_vma_iter *vmi, struct mm_struct *mm) +{ + if (unlikely(!vmi)) { + return; + } + *vmi = (struct upatch_vma_iter){0}; + + if (unlikely(!mm)) { + return; + } + lockdep_assert_held(&mm->mmap_lock); + +#ifdef VMA_USE_MAPLE_TREE + mas_init(&vmi->mas, &mm->mm_mt, 0); + vmi->limit = mm->task_size; +#else + vmi->curr = mm->mmap; +#endif +} + +__always_inline void +upatch_vma_iter_set(struct upatch_vma_iter *vmi, struct vm_area_struct *vma) +{ + if (unlikely(!vmi)) { + return; + } + *vmi = (struct upatch_vma_iter){0}; + + if (unlikely(!vma || !vma->vm_mm)) { + return; + } + lockdep_assert_held(&vma->vm_mm->mmap_lock); + +#ifdef VMA_USE_MAPLE_TREE + mas_init(&vmi->mas, &vma->vm_mm->mm_mt, 0); + mas_set(&vmi->mas, vma->vm_end); + vmi->limit = vma->vm_mm->task_size; +#else + vmi->curr = vma; +#endif +} + +__always_inline struct vm_area_struct * +upatch_vma_next(struct upatch_vma_iter *vmi) +{ + if (unlikely(!vmi)) { + return NULL; + } +#ifdef VMA_USE_MAPLE_TREE + return mas_next(&vmi->mas, vmi->limit); +#else + if (unlikely(!vmi->curr)) { + return NULL; + } + struct vm_area_struct *vma = vmi->curr; + vmi->curr = vma->vm_next; + return vma; +#endif +} + +__always_inline struct vm_area_struct * +upatch_vma_prev(struct upatch_vma_iter *vmi) +{ + if (unlikely(!vmi)) { + return NULL; + } +#ifdef VMA_USE_MAPLE_TREE + return mas_prev(&vmi->mas, 0); +#else + if (unlikely(!vmi->curr)) { + return NULL; + } + struct vm_area_struct *vma = vmi->curr; + vmi->curr = vma->vm_prev; + return vma; +#endif +} + +typedef long (*do_mprotect_pkey_fn)( + unsigned long start, + size_t len, + unsigned long prot, + int pkey +); + +static const char *mprotect_symbol_names[] = { + "do_mprotect_pkey.constprop.0", + "do_mprotect_pkey", + "do_mprotect", + NULL +}; +static do_mprotect_pkey_fn do_mprotect_pkey = NULL; + +static void *get_kernel_symbol(const char *symbol_name) +{ + struct kprobe kp = { + .symbol_name = symbol_name, + }; + void *addr; + int ret; + + if (!symbol_name) { + return ERR_PTR(-EINVAL); + } + + ret = register_kprobe(&kp); + if (ret < 0) { + + return ERR_PTR(ret); + } + + addr = (void *)kp.addr; + if (!addr) { + log_err("kernel symbol '%s' is NULL\n", symbol_name); + unregister_kprobe(&kp); + return ERR_PTR(-EFAULT); + } + + unregister_kprobe(&kp); + return addr; +} + +int upatch_mprotect(unsigned long addr, size_t len, unsigned long prot) +{ + if (!do_mprotect_pkey || IS_ERR(do_mprotect_pkey)) { + return -ENOSYS; + } + return do_mprotect_pkey(addr, len, prot, -1); +} + +int __init kernel_compat_init(void) +{ + int ret; + const char *symbol_name = NULL; + void *addr = NULL; + + for (const char **name = mprotect_symbol_names; *name; name++) { + addr = get_kernel_symbol(*name); + if (IS_ERR(addr)) { + ret = PTR_ERR(addr); + continue; + } + symbol_name = *name; + do_mprotect_pkey = addr; + log_debug("kernel symbol '%s' is at 0x%lx\n", symbol_name, (unsigned long)addr); + break; + } + + if (!symbol_name) { + ret = PTR_ERR(addr); + log_err("cannot find kernel symbol '%s', ret=%d\n", mprotect_symbol_names[0], ret); + return ret; + } + + return 0; +} + +void __exit kernel_compat_exit(void) +{ + return; +} diff --git a/upatch-manage/upatch-patch.h b/upatch-manage/kernel_compat.h similarity index 44% rename from upatch-manage/upatch-patch.h rename to upatch-manage/kernel_compat.h index d32bec0e..ea034245 100644 --- a/upatch-manage/upatch-patch.h +++ b/upatch-manage/kernel_compat.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * upatch-manage + * when user program hit uprobe trap and go into kernel, load patch into VMA * Copyright (C) 2024 Huawei Technologies Co., Ltd. * * This program is free software; you can redistribute it and/or modify @@ -18,17 +18,41 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __UPATCH_PATCH__ -#define __UPATCH_PATCH__ +#ifndef _UPATCH_MANAGE_KERNEL_COMPAT_H +#define _UPATCH_MANAGE_KERNEL_COMPAT_H -#include "upatch-elf.h" -#include "upatch-process.h" -#include "list.h" +#include +#include +#include -int process_patch(int, struct upatch_elf *, struct running_elf *, const char *uuid, const char *binary_path); +#include +#include -int process_unpatch(int, const char *uuid); - -int process_info(int); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0) + #include + #define VMA_USE_MAPLE_TREE +#endif +struct upatch_vma_iter { +#ifdef VMA_USE_MAPLE_TREE + struct ma_state mas; + unsigned long limit; +#else + struct vm_area_struct *curr; #endif +}; + +void upatch_vma_iter_init(struct upatch_vma_iter *vmi, struct mm_struct *mm); + +void upatch_vma_iter_set(struct upatch_vma_iter *vmi, struct vm_area_struct *vma); + +struct vm_area_struct *upatch_vma_next(struct upatch_vma_iter *vmi); + +struct vm_area_struct *upatch_vma_prev(struct upatch_vma_iter *vmi); + +int upatch_mprotect(unsigned long addr, size_t len, unsigned long prot); + +int __init kernel_compat_init(void); +void __exit kernel_compat_exit(void); + +#endif // _UPATCH_MANAGE_KERNEL_COMPAT_H diff --git a/upatch-manage/list.h b/upatch-manage/list.h deleted file mode 100644 index 9e55a758..00000000 --- a/upatch-manage/list.h +++ /dev/null @@ -1,568 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef _LINUX_LIST_H -#define _LINUX_LIST_H - -#include - -/* - * Simple doubly linked list implementation. - * - * Some of the internal functions ("__xxx") are useful when - * manipulating whole lists rather than single entries, as - * sometimes we already know the next/prev entries and we can - * generate better code by using them directly rather than - * using the generic single-entry routines. - */ - -#define container_of(ptr, type, member) \ - ((type *)(((void *)(ptr)) - offsetof(type, member))) - -struct list_head { - struct list_head *next, *prev; -}; - -#define LIST_HEAD_INIT(name) \ - { \ - &(name), &(name) \ - } - -#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) - -#define INIT_LIST_HEAD(ptr) \ - do { \ - (ptr)->next = (ptr); \ - (ptr)->prev = (ptr); \ - } while (0) - -static inline void list_init(struct list_head *list) -{ - list->next = list; - list->prev = list; -} - -/* - * Insert a new entry between two known consecutive entries. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -static inline void __list_add(struct list_head *new, struct list_head *prev, - struct list_head *next) -{ - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; -} - -/** - * list_add_head - add a new entry - * @new: new entry to be added - * @head: list head to add it before - * - * Insert a new entry before the specified head. - * This is useful for implementing queues. - */ -static inline void list_add_head(struct list_head *new, struct list_head *head) -{ - __list_add(new, head, head->next); -} - -/** - * list_add - add a new entry - * @new: new entry to be added - * @head: list head to add it after - * - * Insert a new entry after the specified head. - * This is good for implementing stacks. - */ -static inline void list_add(struct list_head *new, struct list_head *head) -{ - __list_add(new, head->prev, head); -} - -/* - * Delete a list entry by making the prev/next entries - * point to each other. - * - * This is only for internal list manipulation where we know - * the prev/next entries already! - */ -static inline void __list_del(struct list_head *prev, struct list_head *next) -{ - next->prev = prev; - prev->next = next; -} - -/** - * list_del - deletes entry from list. - * @entry: the element to delete from the list. - * Note: list_empty() on entry does not return true after this, the entry is - * in an undefined state. - */ -static inline void __list_del_entry(struct list_head *entry) -{ - __list_del(entry->prev, entry->next); -} - -static inline void list_del(struct list_head *entry) -{ - __list_del_entry(entry); -} - -/** - * list_replace - replace old entry by new one - * @old: the element to be replaced - * @new: the new element to insert - * - * If @old was empty, it will be overwritten. - */ -static inline void list_replace(struct list_head *old, struct list_head *new) -{ - new->next = old->next; - new->next->prev = new; - new->prev = old->prev; - new->prev->next = new; -} - -/** - * list_move - delete from one list and add as another's head - * @list: the entry to move - * @head: the head that will precede our entry - */ -static inline void list_move(struct list_head *list, struct list_head *head) -{ - __list_del_entry(list); - list_add(list, head); -} - -/** - * list_move_tail - delete from one list and add as another's tail - * @list: the entry to move - * @head: the head that will follow our entry - */ -static inline void list_move_tail(struct list_head *list, - struct list_head *head) -{ - __list_del_entry(list); - list_add(list, head); -} - -/** - * list_is_last - tests whether @list is the last entry in list @head - * @list: the entry to test - * @head: the head of the list - */ -static inline int list_is_last(const struct list_head *list, - const struct list_head *head) -{ - return list->next == head; -} - -/** - * list_empty - tests whether a list is empty - * @head: the list to test. - */ -static inline int list_empty(const struct list_head *head) -{ - return head->next == head; -} - -/** - * list_empty_careful - tests whether a list is empty and not being modified - * @head: the list to test - * - * Description: - * tests whether a list is empty _and_ checks that no other CPU might be - * in the process of modifying either member (next or prev) - * - * NOTE: using list_empty_careful() without synchronization - * can only be safe if the only activity that can happen - * to the list entry is list_del_init(). Eg. it cannot be used - * if another CPU could re-list_add() it. - */ -static inline int list_empty_careful(const struct list_head *head) -{ - struct list_head *next = head->next; - return (next == head) && (next == head->prev); -} - -/** - * list_rotate_left - rotate the list to the left - * @head: the head of the list - */ -static inline void list_rotate_left(struct list_head *head) -{ - struct list_head *first; - - if (!list_empty(head)) { - first = head->next; - list_move_tail(first, head); - } -} - -/** - * list_is_singular - tests whether a list has just one entry. - * @head: the list to test. - */ -static inline int list_is_singular(const struct list_head *head) -{ - return !list_empty(head) && (head->next == head->prev); -} - -static inline void __list_cut_position(struct list_head *list, - struct list_head *head, struct list_head *entry) -{ - struct list_head *new_first = entry->next; - - list->next = head->next; - list->next->prev = list; - list->prev = entry; - entry->next = list; - head->next = new_first; - new_first->prev = head; -} - -static inline void __list_splice(const struct list_head *list, - struct list_head *prev, struct list_head *next) -{ - struct list_head *first = list->next; - struct list_head *last = list->prev; - - first->prev = prev; - prev->next = first; - - last->next = next; - next->prev = last; -} - -/** - * list_splice - join two lists, this is designed for stacks - * @list: the new list to add. - * @head: the place to add it in the first list. - */ -static inline void list_splice(const struct list_head *list, - struct list_head *head) -{ - if (!list_empty(list)) { - __list_splice(list, head, head->next); - } -} - -/** - * list_splice_tail - join two lists, each list being a queue - * @list: the new list to add. - * @head: the place to add it in the first list. - */ -static inline void list_splice_tail(struct list_head *list, - struct list_head *head) -{ - if (!list_empty(list)) { - __list_splice(list, head->prev, head); - } -} - -/** - * list_entry - get the struct for this entry - * @ptr: the &struct list_head pointer. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_head within the struct. - */ -#define list_entry(ptr, type, member) container_of(ptr, type, member) - -/** - * list_first_entry - get the first element from a list - * @ptr: the list head to take the element from. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_head within the struct. - * - * Note, that list is expected to be not empty. - */ -#define list_first_entry(ptr, type, member) \ - list_entry((ptr)->next, type, member) - -/** - * list_last_entry - get the last element from a list - * @ptr: the list head to take the element from. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_head within the struct. - * - * Note, that list is expected to be not empty. - */ -#define list_last_entry(ptr, type, member) list_entry((ptr)->prev, type, member) - -/** - * list_first_entry_or_null - get the first element from a list - * @ptr: the list head to take the element from. - * @type: the type of the struct this is embedded in. - * @member: the name of the list_head within the struct. - * - * Note that if the list is empty, it returns NULL. - */ -#define list_first_entry_or_null(ptr, type, member) \ - ({ \ - struct list_head *head__ = (ptr); \ - struct list_head *pos__ = READ_ONCE(head__->next); \ - pos__ != head__ ? list_entry(pos__, type, member) : NULL; \ - }) - -/** - * list_next_entry - get the next element in list - * @pos: the type * to cursor - * @member: the name of the list_head within the struct. - */ -#define list_next_entry(pos, member) \ - list_entry((pos)->member.next, typeof(*(pos)), member) - -/** - * list_prev_entry - get the prev element in list - * @pos: the type * to cursor - * @member: the name of the list_head within the struct. - */ -#define list_prev_entry(pos, member) \ - list_entry((pos)->member.prev, typeof(*(pos)), member) - -/** - * list_for_each - iterate over a list - * @pos: the &struct list_head to use as a loop cursor. - * @head: the head for your list. - */ -#define list_for_each(pos, head) \ - for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) - -/** - * list_for_each_prev - iterate over a list backwards - * @pos: the &struct list_head to use as a loop cursor. - * @head: the head for your list. - */ -#define list_for_each_prev(pos, head) \ - for ((pos) = (head)->prev; (pos) != (head); (pos) = (pos)->prev) - -/** - * list_for_each_safe - iterate over a list safe against removal of list entry - * @pos: the &struct list_head to use as a loop cursor. - * @n: another &struct list_head to use as temporary storage - * @head: the head for your list. - */ -#define list_for_each_safe(pos, n, head) \ - for ( \ - (pos) = (head)->next, (n) = (pos)->next; \ - (pos) != (head); \ - (pos) = (n), (n) = (pos)->next \ - ) - -/** - * list_for_each_prev_safe - iterate over a list backwards safe against removal - * of list entry - * @pos: the &struct list_head to use as a loop cursor. - * @n: another &struct list_head to use as temporary storage - * @head: the head for your list. - */ -#define list_for_each_prev_safe(pos, n, head) \ - for ( \ - (pos) = (head)->prev, (n) = (pos)->prev; \ - (pos) != (head); \ - (pos) = (n), (n) = (pos)->prev \ - ) - -/** - * list_for_each_entry - iterate over list of given type - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - */ -#define list_for_each_entry(pos, head, member) \ - for ( \ - (pos) = list_first_entry(head, typeof(*(pos)), member); \ - &((pos)->member) != (head); \ - (pos) = list_next_entry(pos, member) \ - ) - -/** - * list_for_each_entry_reverse - iterate backwards over list of given type. - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - */ -#define list_for_each_entry_reverse(pos, head, member) \ - for ( \ - (pos) = list_last_entry(head, typeof(*(pos)), member); \ - &((pos)->member) != (head); \ - (pos) = list_prev_entry(pos, member) \ - ) - -/** - * list_prepare_entry - prepare a pos entry for use in - * list_for_each_entry_continue() - * @pos: the type * to use as a start point - * @head: the head of the list - * @member: the name of the list_head within the struct. - * - * Prepares a pos entry for use as a start point in - * list_for_each_entry_continue(). - */ -#define list_prepare_entry(pos, head, member) \ - ((pos) ?: list_entry(head, typeof(*(pos)), member)) - -/** - * list_for_each_entry_continue - continue iteration over list of given type - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Continue to iterate over list of given type, continuing after - * the current position. - */ -#define list_for_each_entry_continue(pos, head, member) \ - for ( \ - (pos) = list_next_entry(pos, member); \ - &((pos)->member) != (head); \ - (pos) = list_next_entry(pos, member) \ - ) - -/** - * list_for_each_entry_continue_reverse - iterate backwards from the given point - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Start to iterate over list of given type backwards, continuing after - * the current position. - */ -#define list_for_each_entry_continue_reverse(pos, head, member) \ - for ( \ - (pos) = list_prev_entry(pos, member); \ - &((pos)->member) != (head); \ - (pos) = list_prev_entry(pos, member) \ - ) - -/** - * list_for_each_entry_from - iterate over list of given type from the current - * point - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Iterate over list of given type, continuing from current position. - */ -#define list_for_each_entry_from(pos, head, member) \ - for (; &((pos)->member) != (head); (pos) = list_next_entry(pos, member)) - -/** - * list_for_each_entry_from_reverse - iterate backwards over list of given type - * from the current point - * @pos: the type * to use as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Iterate backwards over list of given type, continuing from current position. - */ -#define list_for_each_entry_from_reverse(pos, head, member) \ - for (; &((pos)->member) != (head); (pos) = list_prev_entry(pos, member)) - -/** - * list_for_each_entry_safe - iterate over list of given type safe against - * removal of list entry - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_head within the struct. - */ -#define list_for_each_entry_safe(pos, n, head, member) \ - for ( \ - (pos) = list_first_entry(head, typeof(*(pos)), member), \ - (n) = list_next_entry(pos, member); \ - &((pos)->member) != (head); \ - (pos) = (n), (n) = list_next_entry(n, member) \ - ) - -/** - * list_for_each_entry_safe_continue - continue list iteration safe against - * removal - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Iterate over list of given type, continuing after current point, - * safe against removal of list entry. - */ -#define list_for_each_entry_safe_continue(pos, n, head, member) \ - for ( \ - (pos) = list_next_entry(pos, member), \ - (n) = list_next_entry(pos, member); \ - &((pos)->member) != (head); \ - (pos) = (n), (n) = list_next_entry(n, member) \ - ) - -/** - * list_for_each_entry_safe_from - iterate over list from current point safe - * against removal - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Iterate over list of given type from current point, safe against - * removal of list entry. - */ -#define list_for_each_entry_safe_from(pos, n, head, member) \ - for ( \ - (n) = list_next_entry(pos, member); \ - &((pos)->member) != (head); \ - (pos) = (n), (n) = list_next_entry(n, member) \ - ) - -/** - * list_for_each_entry_safe_reverse - iterate backwards over list safe against - * removal - * @pos: the type * to use as a loop cursor. - * @n: another type * to use as temporary storage - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Iterate backwards over list of given type, safe against removal - * of list entry. - */ -#define list_for_each_entry_safe_reverse(pos, n, head, member) \ - for ( \ - (pos) = list_last_entry(head, typeof(*(pos)), member), \ - (n) = list_prev_entry(pos, member); \ - &((pos)->member) != (head); \ - (pos) = (n), (n) = list_prev_entry(n, member) \ - ) - -/** - * list_safe_reset_next - reset a stale list_for_each_entry_safe loop - * @pos: the loop cursor used in the list_for_each_entry_safe loop - * @n: temporary storage used in list_for_each_entry_safe - * @member: the name of the list_head within the struct. - * - * list_safe_reset_next is not safe to use in general if the list may be - * modified concurrently (eg. the lock is dropped in the loop body). An - * exception to this is if the cursor element (pos) is pinned in the list, - * and list_safe_reset_next is called after re-taking the lock and before - * completing the current iteration of the loop body. - */ -#define list_safe_reset_next(pos, n, member) (n) = list_next_entry(pos, member) - -#endif diff --git a/upatch-manage/log.h b/upatch-manage/log.h deleted file mode 100644 index a109958b..00000000 --- a/upatch-manage/log.h +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * log.h - * - * Copyright (C) 2014 Seth Jennings - * Copyright (C) 2013-2014 Josh Poimboeuf - * Copyright (C) 2022 Longjun Luo - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA, - * 02110-1301, USA. - */ - -#ifndef __UPATCH_LOG_H_ -#define __UPATCH_LOG_H_ - -#include -#include - -/* Files that include log.h must define g_loglevel and g_logprefix */ -extern enum log_level g_loglevel; -extern char *g_logprefix; - -enum exit_status { - EXIT_STATUS_SUCCESS = 0, - EXIT_STATUS_ERROR = 1, -}; - -/* Since upatch-build is an one-shot program, we do not care about failure handler */ -#define ERROR(format, ...) \ - error(EXIT_STATUS_ERROR, 0, "ERROR: %s: %s: %d: " format, \ - g_logprefix, __FUNCTION__, __LINE__, ##__VA_ARGS__) - -/* it is time cost */ -#define log_debug(format, ...) log(DEBUG, format, ##__VA_ARGS__) -#define log_normal(format, ...) log(NORMAL, format, ##__VA_ARGS__) -#define log_warn(format, ...) log(WARN, format, ##__VA_ARGS__) -#define log_error(format, ...) log(ERR, format, ##__VA_ARGS__) - -#define log(level, format, ...) \ - do { \ - if (g_loglevel <= (level)) { \ - printf(format, ##__VA_ARGS__); \ - } \ - } while (0) - -#define REQUIRE(COND, message) \ - do { \ - if (!(COND)) { \ - ERROR(message); \ - } \ - } \ - while (0) - -enum log_level { - DEBUG, - NORMAL, - WARN, - ERR, -}; - -#endif diff --git a/upatch-manage/main.c b/upatch-manage/main.c new file mode 100644 index 00000000..d35d2e96 --- /dev/null +++ b/upatch-manage/main.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * upatch_manage kernel module + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include "kernel_compat.h" +#include "ioctl_dev.h" +#include "patch_entity.h" +#include "target_entity.h" +#include "util.h" + +#ifndef MODNAME +#define MODNAME "upatch_manage" +#endif + +#ifndef MODVER +#define MODVER "devel" +#endif + +static int __init upatch_module_init(void) +{ + int ret; + + ret = kernel_compat_init(); + if (ret) { + log_err("failed to initialize kernel compat layer, ret=%d\n", ret); + return ret; + } + + ret = ioctl_device_init(); + if (ret) { + log_err("failed to initialize ioctl device, ret=%d\n", ret); + return ret; + } + + log_info("%s %s initialized\n", MODNAME, MODVER); + return 0; +} + +/* + * when call load_patch(), we call module_get(). When call remove_patch(), we call module_put(). + * This ensures that the module cannot be rmmod while patches are active or deactive. + * When upatch_exit() is called, module refcnt should be 0, meaning all patches have been removed. + * remove_patch() frees patch_entity, and if all patch_entity are free, target_entity will be freed too. + * At this point, there should be no patches or targets left. If any are found, we must print error + */ +static void __exit upatch_module_exit(void) +{ + verify_empty_patch_on_exit(); + verify_empty_target_on_exit(); + + kernel_compat_exit(); + ioctl_device_exit(); + + log_info("%s %s exited\n", MODNAME, MODVER); +} + +module_init(upatch_module_init); +module_exit(upatch_module_exit); + +MODULE_AUTHOR("Longjun Luo (luolongjuna@gmail.com)"); +MODULE_AUTHOR("Zongwu Li (lzw32321226@163.com)"); +MODULE_AUTHOR("renoseven (dev@renoseven.net)"); +MODULE_DESCRIPTION("syscare user patch management"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(MODVER); diff --git a/upatch-manage/patch_entity.c b/upatch-manage/patch_entity.c new file mode 100644 index 00000000..105f931d --- /dev/null +++ b/upatch-manage/patch_entity.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * maintain patch info + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "patch_entity.h" + +#include +#include + +#include "patch_load.h" +#include "util.h" + +static const char *UPATCH_STRINGS_NAME = ".upatch.strings"; +static const char *UPATCH_FUNCS_NAME = ".upatch.funcs"; +static const char *RELE_TEXT_NAME = ".rela.text."; +static const char *REL_TEXT_NAME = ".rel.text."; + +DEFINE_HASHTABLE(g_patches, PATCHES_HASH_BITS); +DEFINE_MUTEX(g_patch_table_lock); + +static void free_patch_meta(struct upatch_metadata *meta) +{ + VFREE_CLEAR(meta->patch_buff); + meta->func_count = 0; + meta->patch_size = 0; +} + +int search_got_rela_entry(struct file *patch, struct upatch_metadata *meta, Elf_Shdr *shdr) +{ + int type; + int ret = 0; + unsigned int i; + Elf_Rela *rel = vmalloc_read(patch, shdr->sh_offset, shdr->sh_size); + if (IS_ERR(rel)) { + ret = PTR_ERR(rel); + log_err("failed to read section '%s'\n", RELE_TEXT_NAME); + return ret; + } + + for (i = 0; i < shdr->sh_size / sizeof(*rel); i++) { + type = ELF_R_TYPE(rel[i].r_info); + if (is_got_rela_type(type)) { + meta->got_rela_cnt++; + } + } + + VFREE_CLEAR(rel); + return ret; +} + +static int init_patch_meta(struct upatch_metadata *meta, struct file *patch) +{ + int ret = 0; + + Elf_Ehdr *ehdr; // elf header + Elf_Shdr *shdrs; // section headers + char *shstrtab; // .shstrtab + + Elf_Shdr *shdr; + char *sh_name; + void *sh_data; + int sh_idx; + void *hdr; + size_t i; + Elf_Sym *symtab; + unsigned int symnum; + + meta->patch_size = i_size_read(file_inode(patch)); + hdr = vmalloc_read(patch, 0, meta->patch_size); + if (IS_ERR(hdr)) { + ret = PTR_ERR(hdr); + log_err("read patch file for entity failed. ret=%d\n", ret); + return ret; + } + meta->patch_buff = hdr; + + // elf header + ehdr = hdr; + + if (!is_elf_valid(ehdr, meta->patch_size, true)) { + ret = -EINVAL; + log_err("invalid patch format\n"); + goto fail; + } + + // section headers + shdrs = hdr + ehdr->e_shoff; + + // section header string table + shdr = &shdrs[ehdr->e_shstrndx]; + shstrtab = hdr + shdr->sh_offset; + + // resolve section headers + for (sh_idx = 1; sh_idx < ehdr->e_shnum; sh_idx++) { + shdr = &shdrs[sh_idx]; + sh_name = shstrtab + shdr->sh_name; + sh_data = hdr + shdr->sh_offset; + + if (shdr->sh_type == SHT_SYMTAB) { + meta->index.sym = sh_idx; + meta->index.str = shdrs[sh_idx].sh_link; + symtab = sh_data; + symnum = shdr->sh_size / sizeof(Elf_Sym); + } else if (strcmp(sh_name, UPATCH_STRINGS_NAME) == 0) { + meta->strings = sh_data; + } else if (strcmp(sh_name, UPATCH_FUNCS_NAME) == 0) { + shdr->sh_entsize = sizeof(struct upatch_function); + meta->func_count = shdr->sh_size / shdr->sh_entsize; + meta->funcs = sh_data; + } else if ((shdr->sh_type == SHT_RELA && !strncmp(sh_name, RELE_TEXT_NAME, strlen(RELE_TEXT_NAME))) || + (shdr->sh_type == SHT_REL && !strncmp(sh_name, REL_TEXT_NAME, strlen(REL_TEXT_NAME)))) { + if (search_got_rela_entry(patch, meta, shdr)) { + goto fail; + } + } + } + + if (!meta->index.sym) { + log_err("patch has no symbols (stripped?)\n"); + ret = -EINVAL; + goto fail; + } + + if (meta->func_count == 0) { + log_err("patch has no .upatch.funcs\n"); + ret = -EINVAL; + goto fail; + } + + // search UND symbol number + for (i = 1; i < symnum; i++) { + if (symtab[i].st_shndx == SHN_UNDEF) + meta->und_count++; + } + + return 0; + +fail: + free_patch_meta(meta); + return ret; +} + +static int init_grab_patch(struct patch_entity *patch, const char *file_path) +{ + int ret = 0; + struct file *file = NULL; + + INIT_HLIST_NODE(&patch->node); + INIT_LIST_HEAD(&patch->target_active_patch); + INIT_LIST_HEAD(&patch->target_all_patch); + + // open patch file + file = filp_open(file_path, O_RDONLY, 0); + if (IS_ERR(file)) { + log_err("failed to open file '%s'\n", file_path); + return PTR_ERR(file); + } + + patch->inode = igrab(file_inode(file)); + if (!patch->inode) { + pr_err("%s: failed to grab inode of '%s'\n", __func__, file_path); + ret = -ENOENT; + goto fail; + } + + patch->path = kstrdup(file_path, GFP_KERNEL); + if (!patch->path) { + ret = -ENOMEM; + iput(patch->inode); + goto fail; + } + + // resolve patch metadata + ret = init_patch_meta(&patch->meta, file); + if (ret != 0) { + iput(patch->inode); + KFREE_CLEAR(patch->path); + log_err("failed to resolve patch meta, ret=%d\n", ret); + goto fail; + } + + patch->status = UPATCH_STATUS_DEACTIVED; + +fail: + filp_close(file, NULL); + return ret; +} + +struct patch_entity *get_patch_entity_from_inode(struct inode *inode) +{ + struct patch_entity *patch; + struct patch_entity *found = NULL; + + mutex_lock(&g_patch_table_lock); + hash_for_each_possible(g_patches, patch, node, inode->i_ino) { + if (patch->inode == inode) { + found = patch; + break; + } + } + + mutex_unlock(&g_patch_table_lock); + return found; +} + +/* public interface */ +struct patch_entity *get_patch_entity(const char *path) +{ + struct inode *inode; + struct patch_entity *patch; + + inode = path_inode(path); + if (IS_ERR(inode)) { + return NULL; + } + + inode = igrab(inode); + if (!inode) { + pr_err("%s: Failed to grab inode of %s\n", __func__, path); + return NULL; + } + + patch = get_patch_entity_from_inode(inode); + iput(inode); + + return patch; +} + +static inline void insert_patch(struct patch_entity *patch) +{ + mutex_lock(&g_patch_table_lock); + hash_add(g_patches, &patch->node, patch->inode->i_ino); + mutex_unlock(&g_patch_table_lock); +} + +struct patch_entity *new_patch_entity(const char *file_path) +{ + int ret = 0; + struct patch_entity *patch = NULL; + + patch = kzalloc(sizeof(struct patch_entity), GFP_KERNEL); + if (!patch) { + log_err("failed to alloc patch entity\n"); + return ERR_PTR(-ENOMEM); + } + + ret = init_grab_patch(patch, file_path); + if (ret) { + log_err("failed to init patch '%s', ret=%d\n", file_path, ret); + kfree(patch); + return ERR_PTR(ret); + } + + insert_patch(patch); + return patch; +} + +void free_patch_entity(struct patch_entity *patch) +{ + if (!patch) { + return; + } + + log_debug("free patch '%s'\n", patch->path); + + iput(patch->inode); + KFREE_CLEAR(patch->path); + free_patch_meta(&patch->meta); + + hash_del(&patch->node); + list_del(&patch->target_active_patch); + list_del(&patch->target_all_patch); + + kfree(patch); +} + +void __exit verify_empty_patch_on_exit(void) +{ + struct patch_entity *patch; + int bkt; + + mutex_lock(&g_patch_table_lock); + hash_for_each(g_patches, bkt, patch, node) { + log_err("found patch '%s' (%d) when exit", + patch->path ? patch->path : "(null)", patch->status); + } + mutex_unlock(&g_patch_table_lock); +} \ No newline at end of file diff --git a/upatch-manage/patch_entity.h b/upatch-manage/patch_entity.h new file mode 100644 index 00000000..22207fb9 --- /dev/null +++ b/upatch-manage/patch_entity.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * maintain patch info header + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPATCH_MANAGE_PATCH_ENTITY_H +#define _UPATCH_MANAGE_PATCH_ENTITY_H + +#include +#include + +#include +#include + +struct inode; +struct target_entity; + +#define PATCHES_HASH_BITS 4 + +/* Patch status */ +enum upatch_status { + UPATCH_STATUS_NOT_APPLIED = 1, + UPATCH_STATUS_DEACTIVED, + UPATCH_STATUS_ACTIVED +}; + +static inline const char *patch_status(int status) { + static const char *STATUS_STR[] = {"NOT_APPLIED", "DEACTIVED", "ACTIVED"}; + + if (status < UPATCH_STATUS_NOT_APPLIED || status > UPATCH_STATUS_ACTIVED) { + return "INVALID_STATUS"; + } + + return STATUS_STR[status - 1]; +} + +/* Patch function relocation */ +struct upatch_func_relocation { + Elf_Rela addr; + Elf_Rela name; +}; + +#ifdef CONFIG_64BIT + /* Patch function entity */ +struct upatch_function { + u64 new_addr; + u64 new_size; + u64 old_addr; + u64 old_size; + u64 sympos; // handle local symbols + u64 name_off; // name offset in .upatch.strings +}; +#else +/* Patch function entity */ +struct upatch_function { + u32 new_addr; + u32 new_size; + u32 old_addr; + u32 old_size; + u32 sympos; // handle local symbols + u32 name_off; // name offset in .upatch.strings + u32 padding1; + u32 padding2; +}; +#endif + +/* Patch metadata */ +struct upatch_metadata { + struct { + unsigned int sym, str; + } index; + + char *strings; // .upatch.strings + struct upatch_func_rela *relas; // .rela.upatch.funcs + size_t func_count; + size_t und_count; // UND symbol count + size_t got_rela_cnt; // relocation type need add got table cnt + + struct upatch_function *funcs; // this should vmalloc, if not, relocation of new_addr may fail + void *patch_buff; + size_t patch_size; +}; + +/* Patch entity */ +struct patch_entity { + struct inode *inode; // patch file inode + struct target_entity *target; // target file inode + char *path; // patch file path + struct upatch_metadata meta; // patch metadata + + enum upatch_status status; // patch status + struct hlist_node node; // all patches store in hash table + struct list_head target_active_patch; // target's active patch + struct list_head target_all_patch; // all patches related to the target list node +}; + +struct patch_entity *get_patch_entity(const char *patch_file); + +struct patch_entity *new_patch_entity(const char *patch_file); + +void free_patch_entity(struct patch_entity *patch); + +void __exit verify_empty_patch_on_exit(void); + +#endif // _UPATCH_MANAGE_PATCH_ENTITY_H diff --git a/upatch-manage/patch_load.c b/upatch-manage/patch_load.c new file mode 100644 index 00000000..a7e42a4b --- /dev/null +++ b/upatch-manage/patch_load.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * when user program hit uprobe trap and go into kernel, load patch into VMA + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "patch_load.h" + +#include +#include + +#include "arch/patch_load.h" + +#include "patch_entity.h" +#include "target_entity.h" +#include "process_entity.h" +#include "symbol_resolve.h" +#include "kernel_compat.h" +#include "util.h" + +#define FLAG_LEN 3 + +#ifndef ARCH_SHF_SMALL +#define ARCH_SHF_SMALL 0 +#endif + +static int setup_load_info(struct upatch_info *info, struct patch_entity *patch) +{ + info->len = patch->meta.patch_size; + info->hdr = vmalloc(info->len); + if (!info->hdr) { + log_err("cannot vmalloc %ld size for upatch info\n", info->len); + return -ENOMEM; + } + + // read whole patch into kernel temporarily + memcpy(info->hdr, patch->meta.patch_buff, info->len); + + info->sechdrs = (void *)info->hdr + info->hdr->e_shoff; + info->secstrings = (void *)info->hdr + + info->sechdrs[info->hdr->e_shstrndx].sh_offset; + + info->index.sym = patch->meta.index.sym; + info->index.str = patch->meta.index.str; + info->und_cnt = patch->meta.und_count; + info->got_rela_cnt = patch->meta.got_rela_cnt; + info->strtab = (char *)info->hdr + info->sechdrs[info->index.str].sh_offset; + log_debug("patch UND symbol %d got_rela %d\n", info->und_cnt, info->got_rela_cnt); + + return 0; +} + +static int rewrite_section_headers(struct upatch_info *info) +{ + unsigned int i; + + /* This should always be true, but let's be sure. */ + info->sechdrs[0].sh_addr = 0; + info->sechdrs[0].sh_addralign = 0; + + for (i = 1; i < info->hdr->e_shnum; i++) { + Elf_Shdr *shdr = &info->sechdrs[i]; + if (shdr->sh_type != SHT_NOBITS + && info->len < shdr->sh_offset + shdr->sh_size) { + log_err("upatch len %lu truncated\n", info->len); + return -ENOEXEC; + } + + /* Mark all sections sh_addr with their address in the + temporary image. */ + shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset; + } + + return 0; +} + +static long align_size_add_sh_size(unsigned int *size, Elf_Shdr *sechdr) +{ + long ret; + + ret = ALIGN(*size, sechdr->sh_addralign ?: 1); + *size = ret + sechdr->sh_size; + return ret; +} + +static void layout_jmptable(struct upatch_layout *layout, struct upatch_info *info) +{ + unsigned long start_off; + layout->table.cur = 0; + layout->table.max = info->und_cnt * JMP_TABLE_ENTRY_MAX_SIZE + info->got_rela_cnt * JMP_TABLE_GOT_ENTRY_SIZE; + start_off = ALIGN(layout->size, sizeof(unsigned long)); + + layout->table.off = start_off; + layout->size = start_off + layout->table.max; + log_debug("\t\t%-20s \t0x%lx - 0x%x max size %d\n", "jmptable", start_off, layout->size, layout->table.max); +} + +static void layout_sections(struct upatch_layout *layout, struct upatch_info *info) +{ + static unsigned long const masks[][2] = { + /* NOTE: all executable code must be the first section + * in this array; otherwise modify the text_size + * finder in the two loops below */ + { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL }, + { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL }, + { SHF_RO_AFTER_INIT | SHF_ALLOC, ARCH_SHF_SMALL }, + { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL }, + { ARCH_SHF_SMALL | SHF_ALLOC, 0 } + }; + unsigned int m, i; + + for (i = 0; i < info->hdr->e_shnum; i++) + info->sechdrs[i].sh_entsize = ~0UL; + + log_debug("upatch section allocation order:\n"); + for (m = 0; m < ARRAY_SIZE(masks); ++m) { + for (i = 0; i < info->hdr->e_shnum; ++i) { + Elf_Shdr *s = &info->sechdrs[i]; + const char *sname = info->secstrings + s->sh_name; + + if ((s->sh_flags & masks[m][0]) != masks[m][0] + || (s->sh_flags & masks[m][1]) + || s->sh_entsize != ~0UL) + continue; + s->sh_entsize = align_size_add_sh_size(&layout->size, s); + log_debug("type[%02d] %-20s \tend at 0x%x\n", m, sname, layout->size); + } + switch (m) { + case 0: /* executable */ + layout_jmptable(layout, info); + layout->size = PAGE_ALIGN(layout->size); + layout->text_end = layout->size; + break; + case 1: /* RO: text and ro-data */ + layout->size = PAGE_ALIGN(layout->size); + layout->ro_end = layout->size; + break; + case 2: /* RO after init */ + layout->size = PAGE_ALIGN(layout->size); + layout->ro_after_init_end = layout->size; + break; + case 4: /* writable and small data */ + layout->size = PAGE_ALIGN(layout->size); + break; + default: + break; + } + } +} + +// we don't use the VMA after the patched code because we may use the VMA that heap will reuse +// we search before the patched code to find a empty VMA +static unsigned long get_upatch_hole(unsigned long start, unsigned long size) +{ + unsigned long search = start; + struct vm_area_struct *vma; + struct mm_struct *mm = current->mm; + + mmap_read_lock(mm); + vma = find_vma_intersection(mm, search, search + size); + while (vma) { + search = vma->vm_start - size; + vma = find_vma_intersection(mm, search, search + size); + } + mmap_read_unlock(mm); + + log_debug("find hole at 0x%lx - 0x%lx\n", search, search + size); + return search; +} + +/* alloc memory in userspace */ +static unsigned long find_vma_hole_and_vmmap(unsigned long vma_start, unsigned long size) +{ + unsigned long mem_addr; + unsigned long addr = get_upatch_hole(vma_start, size); + + if (!addr) { + log_err("cannot find hole start in %ld in pid %d\n", vma_start, task_pid_nr(current)); + return 0; + } + + mem_addr = vm_mmap(NULL, addr, size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, 0); + if (IS_ERR((void *)mem_addr)) { + log_err("mmap process memory failed with %ld\n", PTR_ERR((void *)mem_addr)); + return 0; + } + + /* If the address applied for by the hot patch is too far away from the VMA address of the target file + * and the relocation type (such as the jump instruction) with distance restriction is used during relocation + * we can put the jump instruction into the jmp table to avoid the jump distance restriction + */ + if (vma_start - mem_addr >= PATCH_LOAD_RANGE_LIMIT) { + log_warn("patch hole 0x%lx is 0x%lx far from the code start 0x%lx\n", + mem_addr, vma_start - mem_addr, vma_start); + } + + return mem_addr; +} + +static int alloc_mem_for_upatch(struct upatch_info *info, + struct upatch_layout *layout) +{ + /* find moudle location from code start place */ + unsigned long vma_start = info->running_elf.vma_start_addr; + + layout->base = find_vma_hole_and_vmmap(vma_start, layout->size); + if (!layout->base) + return -ENOMEM; + + layout->kbase = kzalloc(layout->size, GFP_KERNEL); + if (!layout->kbase) { + vm_munmap(layout->base, layout->size); + layout->base = 0; + return -ENOMEM; + } + + log_debug("kbase 0x%lx base 0x%lx\n", (unsigned long)(uintptr_t)layout->kbase, layout->base); + + return 0; +} + +static void load_info_clear(struct upatch_info *info) +{ + VFREE_CLEAR(info->hdr); + KFREE_CLEAR(info->layout.kbase); +} + +void parse_vma_flags(char * buf, unsigned long flags) +{ + memset(buf, '-', FLAG_LEN); + buf[FLAG_LEN] = 0; + if (flags & VM_READ) buf[0] = 'r'; + if (flags & VM_WRITE) buf[1] = 'w'; + if (flags & VM_EXEC) buf[2] = 'x'; +} + +static void print_vma_info(void) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + struct upatch_vma_iter vma_iter; + char prot[4]; + char *path; + char buf[256]; + + if (!mm) { + log_debug("No memory descriptor found\n"); + return; + } + + log_debug("Here is the pid %d VMA:\n", task_pid_nr(current)); + mmap_read_lock(mm); + upatch_vma_iter_init(&vma_iter, mm); + while ((vma = upatch_vma_next(&vma_iter))) { + if (vma->vm_file) { + path = d_path(&vma->vm_file->f_path, buf, sizeof(buf)); + } else { + path = ""; + } + parse_vma_flags(prot, vma->vm_flags); + log_debug("0x%lx-0x%lx %s %s\n", + vma->vm_start, vma->vm_end, prot, path); + } + mmap_read_unlock(mm); +} + +static int alloc_layout(struct upatch_layout *layout, struct upatch_info *info) +{ + int i, ret; + char *name; + + /* Do the allocs. */ + ret = alloc_mem_for_upatch(info, layout); + if (ret) { + log_err("alloc upatch process memory failed: %d\n", ret); + return ret; + } + + /* Transfer each section which specifies SHF_ALLOC */ + log_debug("final section addresses:\n"); + for (i = 0; i < info->hdr->e_shnum; i++) { + unsigned long dest; + uintptr_t kdest; + Elf_Shdr *shdr = &info->sechdrs[i]; + + if (!(shdr->sh_flags & SHF_ALLOC)) + continue; + + name = info->secstrings + shdr->sh_name; + + // sh_entsize is set to this section layout start offset in 'layout_sections' + dest = layout->base + shdr->sh_entsize; + kdest = (uintptr_t)layout->kbase + shdr->sh_entsize; + + if (shdr->sh_type != SHT_NOBITS) + memcpy((void *)kdest, (void *)shdr->sh_addr, shdr->sh_size); + + if (!strcmp(name, ".upatch.funcs")) { + info->upatch_func_sec = shdr; + } + + /* Update sh_addr to point to copy in image. */ + shdr->sh_addr = (unsigned long)kdest; + /* overuse this attr to record user address */ + shdr->sh_addralign = dest; + log_debug("sec[%02d] %-20s \t0x%lx -> 0x%lx size 0x%zx\n", + i, name, (unsigned long)kdest, dest, (size_t)shdr->sh_size); + } + + log_debug("patch vma layout:\n"); + log_debug("\ttext \t\t\t0x%lx size 0x%x\n", layout->base, layout->text_end); + log_debug("\trodata \t\t\t0x%lx size 0x%x\n", + layout->base + layout->text_end, layout->ro_end - layout->text_end); + log_debug("\tro after init \t\t\t0x%lx size 0x%x\n", + layout->base + layout->ro_end, layout->ro_after_init_end - layout->ro_end); + log_debug("\twritable \t\t\t0x%lx size 0x%x\n", + layout->base + layout->ro_after_init_end, layout->size - layout->ro_after_init_end); + + print_vma_info(); + + if (!info->upatch_func_sec) { + log_err("cannot find .upatch_func section\n"); + return -1; + } + return 0; +} + +static int layout_and_allocate(struct upatch_info *info) +{ + int err; + + layout_sections(&info->layout, info); + + err = alloc_layout(&info->layout, info); + if (err) { + return err; + } + + return 0; +} + +static int simplify_symbols(const struct upatch_info *info) +{ + Elf_Shdr *symsec = &info->sechdrs[info->index.sym]; + Elf_Sym *sym = (void *)symsec->sh_addr; + unsigned long secbase; + unsigned int i; + int ret = 0; + unsigned long elf_addr; + + for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) { + const char *name; + + if (ELF_ST_TYPE(sym[i].st_info) == STT_SECTION + && sym[i].st_shndx < info->hdr->e_shnum) + name = info->secstrings + info->sechdrs[sym[i].st_shndx].sh_name; + else + name = info->strtab + sym[i].st_name; + + switch (sym[i].st_shndx) { + case SHN_COMMON: + log_err("unsupported common symbol: %s\n", name); + ret = -ENOEXEC; + break; + case SHN_ABS: + break; + case SHN_UNDEF: + elf_addr = resolve_symbol(&info->running_elf, name, sym[i]); + if (!elf_addr) { + return -ENOEXEC; + } + sym[i].st_value = elf_addr; + log_debug("'%s'\t -> 0x%lx\n", + name, (unsigned long)sym[i].st_value); + break; + case SHN_LIVEPATCH: + sym[i].st_value += info->running_elf.vma_start_addr; + log_debug("resolved livepatch symbol %s at 0x%lx\n", + name, (unsigned long)sym[i].st_value); + break; + default: + /* use real address to calculate secbase */ + secbase = info->sechdrs[sym[i].st_shndx].sh_addralign; + sym[i].st_value += secbase; + log_debug("normal symbol %-20s \t 0x%012lx\n", + name, (unsigned long)sym[i].st_value); + break; + } + } + + return ret; +} + +static int apply_relocations(struct upatch_info *info) +{ + unsigned int i; + int err = 0; + + /* Now do relocations. */ + for (i = 1; i < info->hdr->e_shnum; i++) { + unsigned int infosec = info->sechdrs[i].sh_info; + const char *name = info->secstrings + info->sechdrs[i].sh_name; + + /* Not a valid relocation section? */ + if (infosec >= info->hdr->e_shnum) + continue; + + /* Don't bother with non-allocated sections */ + if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC)) + continue; + + if (info->sechdrs[i].sh_type == SHT_REL || info->sechdrs[i].sh_type == SHT_RELA) { + log_debug("do relocations for %s\n", name); + err = apply_relocate_add(info, i); + } + + if (err) { + break; + } + } + return err; +} + +static int copy_layout_into_vma(struct upatch_layout *layout) +{ + log_debug("mov content from 0x%lx to 0x%lx with 0x%x\n", + (unsigned long)layout->kbase, layout->base, layout->size); + if (copy_to_user((void *)layout->base, layout->kbase, layout->size)) { + log_err("copy_to_user from 0x%lx to 0x%lx with 0x%x failed\n", + (unsigned long)layout->kbase, layout->base, layout->size); + return -EPERM; + } + return 0; +} + +static int frob_text(const struct upatch_layout *layout) +{ + unsigned long addr = (unsigned long)layout->base; + size_t text_size = layout->text_end; + int ret; + + ret = upatch_mprotect(addr, text_size, PROT_READ | PROT_EXEC); + if (ret) { + log_err("failed to set text memory previliage to r-x, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int frob_rodata(const struct upatch_layout *layout) +{ + unsigned long ro_start = (unsigned long)layout->base + layout->text_end; + size_t ro_size = layout->ro_end - layout->text_end; + int ret; + + unsigned long ro_after_init_start = (unsigned long)layout->base + layout->ro_end; + size_t ro_after_init_size = layout->ro_after_init_end - layout->ro_end; + + ret = upatch_mprotect(ro_start, ro_size, PROT_READ); + if (ret) { + log_err("failed to set rodata memory previliage to r--, ret=%d\n", ret); + return ret; + } + + ret = upatch_mprotect(ro_after_init_start, ro_after_init_size, PROT_READ); + if (ret) { + log_err("failed to set ro_after_init memory previliage to r--, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static int set_memory_previliage(struct upatch_layout *layout) +{ + int ret; + + ret = frob_text(layout); + if (ret) { + return ret; + } + + ret = frob_rodata(layout); + if (ret) { + return ret; + } + + print_vma_info(); + + return 0; +} + +// create old_pc - new_pc maps +static int create_relocated_pc_maps(struct process_entity *process, struct upatch_info *load_info, + struct patch_entity *patch) +{ + unsigned int num; + struct upatch_function *funcs; + unsigned int i; + struct patch_info *info; + struct pc_pair *pp; + + funcs = (struct upatch_function *)load_info->upatch_func_sec->sh_addr; + num = load_info->upatch_func_sec->sh_size / (sizeof (struct upatch_function)); + + info = kzalloc(sizeof(struct patch_info), GFP_KERNEL); + if (!info) { + log_err("malloc patch_info failed!\n"); + return -ENOMEM; + } + + hash_init(info->pc_maps); + for (i = 0; i < num; ++i) { + pp = kmalloc(sizeof(*pp), GFP_KERNEL); + if (!pp) { + free_patch_info(info); + return -ENOMEM; + } + pp->old_pc = funcs[i].old_addr + load_info->running_elf.vma_start_addr + load_info->running_elf.meta->code_virt_offset; + pp->new_pc = funcs[i].new_addr; + hash_add(info->pc_maps, &pp->node, pp->old_pc); + log_debug("* pc old -> new: 0x%08lx -> 0x%08lx\n", pp->old_pc, pp->new_pc); + } + + list_add(&info->list, &process->loaded_patches); + info->patch = patch; + process->active_info = info; + + return 0; +} + +/* The main idea is from insmod */ +int upatch_resolve(struct target_entity *target, struct patch_entity *patch, struct process_entity *process, + unsigned long target_code_start) +{ + int err; + struct upatch_info info; + + memset(&info, 0, sizeof(info)); + + info.running_elf.vma_start_addr = target_code_start - target->meta.code_vma_offset; + log_debug("target %s for pid %d:\n", target->path, task_pid_nr(current)); + log_debug("vma start 0x%lx code start 0x%lx\n", info.running_elf.vma_start_addr, target_code_start); + + info.running_elf.meta = &target->meta; + info.running_elf.load_info = &info; + + err = setup_load_info(&info, patch); + if (err) { + goto fail; + } + + /* update section address */ + err = rewrite_section_headers(&info); + if (err) { + goto fail; + } + + err = layout_and_allocate(&info); + if (err) { + goto fail; + } + + /* Fix up syms, so that st_value is a pointer to location. */ + err = simplify_symbols(&info); + if (err) { + goto fail; + } + + /* upatch new address will be updated */ + err = apply_relocations(&info); + if (err) { + goto fail; + } + + err = copy_layout_into_vma(&info.layout); + if (err) { + goto fail; + } + + err = set_memory_previliage(&info.layout); + if (err) { + goto fail; + } + + err = create_relocated_pc_maps(process, &info, patch); + if (err) { + goto fail; + } + + log_debug("patch load successfully\n"); + load_info_clear(&info); + return 0; + +fail: + if (info.layout.base) { + vm_munmap(info.layout.base, info.layout.size); + info.layout.base = 0; + } + load_info_clear(&info); + return err; +} + +static inline bool is_addr_in_got_table(struct upatch_layout *layout, u64 addr) +{ + unsigned long table_start = layout->base + layout->table.off; + unsigned long table_end = table_start + layout->table.max; + return addr >= table_start && addr < table_end; +} + +unsigned long get_or_setup_got_entry(struct upatch_info *info, Elf_Sym *sym) +{ + unsigned long got; + + if (sym->st_shndx == SHN_UNDEF && is_addr_in_got_table(&info->layout, sym->st_value)) { + got = sym->st_value; + } else { + got = setup_got_table(info, sym->st_value, 0); + } + + return got; +} \ No newline at end of file diff --git a/upatch-manage/patch_load.h b/upatch-manage/patch_load.h new file mode 100644 index 00000000..eaabf57f --- /dev/null +++ b/upatch-manage/patch_load.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * when user program hit uprobe trap and go into kernel, load patch into VMA + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPATCH_IOCTL_PATCH_LOAD_H +#define _UPATCH_IOCTL_PATCH_LOAD_H + +#include +#include + +struct target_entity; +struct target_metadata; +struct patch_entity; +struct process_entity; + +struct upatch_info; + +struct jmp_table { + unsigned long off; + unsigned int cur; + unsigned int max; +}; + +/* memory layout for module */ +struct upatch_layout { + void* kbase; // kmalloc patch, will be relocated and copy to base + unsigned long base; // VMA in user space + unsigned int size; // Total size + unsigned int text_end; // The size of the executable code and jmp table + unsigned int ro_end; // Size of RO section of the module (text+rodata) + unsigned int ro_after_init_end; // Size of RO after init section + struct jmp_table table; +}; + +struct running_elf { + struct target_metadata *meta; + + // target vma start addr, the first vma could not be the text in LLVM + unsigned long vma_start_addr; + + struct upatch_info *load_info; +}; + +// when load patch, patch need resolve in different process +struct upatch_info { + unsigned long len; + Elf_Ehdr *hdr; + Elf_Shdr *sechdrs; + Elf_Shdr *upatch_func_sec; + char *secstrings, *strtab; + unsigned int und_cnt, got_rela_cnt; + struct { + unsigned int sym, str; + } index; + + /* memory layout for patch */ + struct upatch_layout layout; + + struct running_elf running_elf; +}; + +int upatch_resolve(struct target_entity *target, struct patch_entity *patch, struct process_entity *process, + unsigned long target_code_start); + +bool is_got_rela_type(int type); + +// All UND symbol have already been set up got table in resolve_symbol.c +// Except thoese GLOBAL OBJECT in target found in resolve_from_target_sym +unsigned long get_or_setup_got_entry(struct upatch_info *info, Elf_Sym *sym); + +#endif // _UPATCH_IOCTL_PATCH_LOAD_H diff --git a/upatch-manage/patch_manage.c b/upatch-manage/patch_manage.c new file mode 100644 index 00000000..683f27df --- /dev/null +++ b/upatch-manage/patch_manage.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * provide kload kactive kdeactive kremove API to manage patch + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "patch_manage.h" + +#include +#include + +#include "patch_entity.h" +#include "target_entity.h" +#include "process_entity.h" +#include "patch_load.h" +#include "util.h" + +#ifndef UPROBE_ALTER_PC +#define UPROBE_ALTER_PC 2 +#endif + +#define UPROBE_RUN_OLD_FUNC 0 + +static int jump_to_new_pc(struct pt_regs *regs, struct patch_info *info, unsigned long old_pc) +{ + struct pc_pair *pp; + bool find = false; + hash_for_each_possible(info->pc_maps, pp, node, old_pc) { + if (pp->old_pc == old_pc) { + find = true; + break; + } + } + + if (!find) { + log_err("cannot find new pc for 0x%lx\n", old_pc); + return UPROBE_RUN_OLD_FUNC; + } + + log_debug("jump from 0x%lx -> 0x%lx\n", old_pc, pp->new_pc); + + instruction_pointer_set(regs, pp->new_pc); + return UPROBE_ALTER_PC; +} + +static struct file *get_target_file_from_pc(struct mm_struct *mm, unsigned long pc, unsigned long *code_start) +{ + struct vm_area_struct *vma = NULL; + + vma = find_vma(mm, pc); + if (!vma) { + return NULL; + } + + if (!vma->vm_file) { + return NULL; + } + + *code_start = vma->vm_start; + + return vma->vm_file; +} + +static struct target_entity *get_target_from_pc(unsigned long pc, unsigned long *code_start) +{ + struct target_entity *target; + + struct mm_struct *mm = current->mm; + struct file *target_file; + + mmap_read_lock(mm); + target_file = get_target_file_from_pc(mm, pc, code_start); + mmap_read_unlock(mm); + + if (!target_file) { + log_err("no backen file found for upatch\n"); + return NULL; + } + + get_file(target_file); + target = get_target_entity_from_inode(file_inode(target_file)); + fput(target_file); + + if (!target) { + log_err("no target found in patch handler\n"); + return NULL; + } + + return target; +} + +static struct patch_info* find_loaded_patches(struct process_entity *pro, struct patch_entity *patch) +{ + struct patch_info* info; + list_for_each_entry(info, &pro->loaded_patches, list) { + if (info->patch == patch) { + return info; + } + } + return NULL; +} + +// UPROBE_RUN_OLD_FUNC means run old func +// UPROBE_ALTER_PC means run new func +static int upatch_uprobe_handler(struct uprobe_consumer *self, struct pt_regs *regs) +{ + unsigned long pc = instruction_pointer(regs); + struct target_entity *target; + struct patch_entity *active_patch; + struct process_entity *process; + int ret = UPROBE_RUN_OLD_FUNC; + pid_t pid = task_pid_nr(current); + pid_t tgid = task_tgid_nr(current); + unsigned long target_code_start; + + log_debug("patch handler works at pc 0x%lx pid %d tgid %d\n", pc, pid, tgid); + + target = get_target_from_pc(pc, &target_code_start); + if (!target) { + log_err("cannot find target entity from pc 0x%lx\n", pc); + return ret; + } + + down_read(&target->lock); + active_patch = list_first_entry(&target->active_patch_list, struct patch_entity, target_active_patch); + up_read(&target->lock); + + if (!active_patch) { + log_err("cannot find active patch\n"); + return ret; + } + + process = get_process(target); + if (!process) { + return UPROBE_RUN_OLD_FUNC; + } + + // multi thread may trap at the same time, only one thread can load patch, other thread should wait + mutex_lock(&process->lock); + + if (!process->active_info) { + log_debug("process pid %d tgid %d first load patch %s\n", pid, tgid, active_patch->path); + ret = upatch_resolve(target, active_patch, process, target_code_start); + if (ret) { + log_err("upatch_load failed, ret=%d\n", ret); + goto fail; + } + } else if (process->active_info->patch != active_patch) { + struct patch_info* info = find_loaded_patches(process, active_patch); + if (info) { + process->active_info = info; + goto ok; + } + + log_debug("process pid %d tgid %d don't have load patch %s\n", pid, tgid, active_patch->path); + ret = upatch_resolve(target, active_patch, process, target_code_start); + if (ret) { + log_err("upatch_load failed, ret=%d\n", ret); + goto fail; + } + } + +ok: + ret = jump_to_new_pc(regs, process->active_info, pc); + mutex_unlock(&process->lock); + return ret; + +fail: + mutex_unlock(&process->lock); + return UPROBE_RUN_OLD_FUNC; +} + +static struct uprobe_consumer patch_consumer = { + .handler = upatch_uprobe_handler, +}; + +// register uprobe if offset of this target have no new function +static int register_target_function(struct target_entity *target, loff_t offset, + struct upatch_function *func) +{ + struct patched_offset *off = NULL; + struct patched_func_node *func_node; + bool find = false; + int ret; + + func_node = kzalloc(sizeof(struct patched_func_node), GFP_KERNEL); + if (!func_node) { + return -ENOMEM; + } + + // find if this target have func changed in offset + list_for_each_entry(off, &target->off_head, list) { + if (off->offset == offset) { + find = true; + break; + } + } + + // This is the first func in this offset, so we should create a off node + if (!find) { + off = kzalloc(sizeof(struct patched_offset), GFP_KERNEL); + if (!off) { + kfree(func_node); + return -ENOMEM; + } + + off->offset = offset; + INIT_LIST_HEAD(&off->funcs_head); + + log_debug("register uprobe on '%s' (inode: %lu) at 0x%llx\n", + target->path, target->inode->i_ino, offset); + ret = uprobe_register(target->inode, offset, &patch_consumer); + if (ret) { + log_err("failed to register uprobe on '%s' (inode: %lu) at 0x%llx, ret=%d\n", + target->path, target->inode->i_ino, offset, ret); + kfree(func_node); + kfree(off); + return ret; + } + + list_add(&off->list, &target->off_head); + } + + func_node->func = func; + list_add(&func_node->list, &off->funcs_head); + return 0; +} + +// unregister uprobe if offset of this target have no new function +static void unregister_function_uprobe(struct target_entity *target, loff_t offset, struct upatch_function *func) +{ + struct patched_offset *off = NULL; + struct patched_func_node *func_node, *tmp; + bool find = false; + + list_for_each_entry(off, &target->off_head, list) { + if (off->offset == offset) { + find = true; + break; + } + } + + if (!find) { + log_err("cannot find offset 0x%llx for target %s\n", offset, target->path); + return; + } + + // There may be multiple version func in the same offset of a target. We should find it and delete it + list_for_each_entry_safe(func_node, tmp, &off->funcs_head, list) { + if (func_node->func == func) { + list_del(&func_node->list); + kfree(func_node); + } + } + + if (list_empty(&off->funcs_head)) { + uprobe_unregister(target->inode, offset, &patch_consumer); + list_del(&off->list); + kfree(off); + } +} + +static void unregister_target_functions(struct target_entity *target, struct patch_entity *patch, + struct upatch_function *funcs, size_t count) +{ + struct upatch_function *func = NULL; + size_t i = 0; + loff_t offset = 0; + char *name = NULL; + + log_debug("unregister patch '%s' functions:\n", target->path); + for (i = 0; i < count; i++) { + func = &funcs[i]; + offset = func->old_addr; + name = patch->meta.strings + func->name_off; + + log_debug("- function: offset=0x%08llx, size=0x%08zx, name='%s'\n", + offset, (size_t)func->old_size, name); + unregister_function_uprobe(target, offset, func); + } +} + +// patch will be actived in uprobe handler +static int do_active_patch(struct target_entity *target, struct patch_entity *patch) +{ + struct upatch_function *funcs = patch->meta.funcs; + struct upatch_function *func; + int ret = 0; + size_t i = 0; + loff_t offset; + char *name = NULL; + + log_debug("register patch '%s' functions:\n", target->path); + down_write(&target->lock); + + for (i = 0; i < patch->meta.func_count; i++) { + func = &funcs[i]; + offset = func->old_addr; + name = patch->meta.strings + func->name_off; + + ret = register_target_function(target, offset, func); + if (ret) { + log_err("failed to register patch function '%s', ret=%d\n", name, ret); + unregister_target_functions(target, patch, funcs, i); + goto out; + } + log_debug("+ function: offset=0x%08llx, size=0x%zx, name='%s'\n", + offset, (size_t)func->old_size, name); + } + + list_add(&patch->target_active_patch, &target->active_patch_list); + patch->status = UPATCH_STATUS_ACTIVED; + +out: + up_write(&target->lock); + + return ret; +} + +static void remove_patch_from_active_patch_list(struct target_entity *target, struct patch_entity *patch) +{ + struct patch_entity *p, *tmp; + bool found = false; + list_for_each_entry_safe(p, tmp, &target->active_patch_list, target_active_patch) { + if (p == patch) { + list_del_init(&p->target_active_patch); + found = true; + break; + } + } + + if (!found) { + log_err("cannot find '%s' in actived patches\n", patch->path); + } +} + +// delete patch inode & function in target +// patch will be deactived in uprobe handler +static void do_deactive_patch(struct patch_entity *patch) +{ + struct target_entity *target = patch->target; + struct upatch_function *funcs = patch->meta.funcs; + + down_write(&target->lock); + + unregister_target_functions(target, patch, funcs, patch->meta.func_count); + remove_patch_from_active_patch_list(target, patch); + patch->status = UPATCH_STATUS_DEACTIVED; + + up_write(&target->lock); +} + +/* public interface */ +enum upatch_status upatch_status(const char *patch_file) +{ + struct patch_entity *patch; + + patch = get_patch_entity(patch_file); + return patch ? patch->status : UPATCH_STATUS_NOT_APPLIED; +} + +int upatch_load(const char *patch_file, const char *target_path) +{ + struct patch_entity *patch = NULL; + struct target_entity *target = NULL; + + if (!patch_file || !target_path) { + return -EINVAL; + } + + log_info("loading patch '%s' -> '%s'...\n", patch_file, target_path); + + patch = get_patch_entity(patch_file); + if (patch) { + log_err("patch '%s' is already loaded\n", patch_file); + return -EEXIST; + } + + patch = new_patch_entity(patch_file); + if (IS_ERR(patch)) { + log_err("failed to init patch '%s'\n", patch_file); + return PTR_ERR(patch); + } + + target = get_target_entity(target_path); + if (!target) { + target = new_target_entity(target_path); + if (IS_ERR(target)) { + free_patch_entity(patch); + log_err("failed to init patch target '%s'\n", target_path); + return PTR_ERR(target); + } + } + + list_add(&patch->target_all_patch, &target->all_patches_list); + patch->target = target; + patch->status = UPATCH_STATUS_DEACTIVED; + + log_debug("patch '%s' is loaded\n", patch_file); + return 0; +} + +int upatch_remove(const char *patch_file) +{ + struct patch_entity *patch = NULL; + struct target_entity *target = NULL; + + log_info("removing patch '%s'...\n", patch_file); + + patch = get_patch_entity(patch_file); + if (!patch) { + log_err("cannot find patch entity\n"); + return -ENOENT; + } + + if (patch->status != UPATCH_STATUS_DEACTIVED) { + log_err("invalid patch status\n"); + return -EPERM; + } + + target = patch->target; + + free_patch_entity(patch); + if (!is_target_has_patch(target)) { + free_target_entity(target); + } + + log_debug("patch '%s' is removed\n", patch_file); + return 0; +} + +int upatch_active(const char *patch_file) +{ + struct patch_entity *patch = NULL; + struct target_entity *target = NULL; + int ret; + + log_info("activating patch '%s'...\n", patch_file); + + patch = get_patch_entity(patch_file); + if (!patch) { + log_err("cannot find patch entity\n"); + return -ENOENT; + } + + // check patch status + if (patch->status != UPATCH_STATUS_DEACTIVED) { + log_err("invalid patch status\n"); + return -EPERM; + } + + target = patch->target; + + ret = do_active_patch(target, patch); + if (ret) { + log_err("failed to activate patch, ret=%d\n", ret); + return ret; + } + + log_debug("patch '%s' is activated\n", patch_file); + return 0; +} + +int upatch_deactive(const char *patch_file) +{ + struct patch_entity *patch = NULL; + + log_info("deactivating patch '%s'...\n", patch_file); + + // find patch + patch = get_patch_entity(patch_file); + if (!patch) { + log_err("cannot find patch entity\n"); + return -ENOENT; + } + + // check patch status + if (patch->status != UPATCH_STATUS_ACTIVED) { + log_err("invalid patch status\n"); + return -EPERM; + } + + do_deactive_patch(patch); + + log_debug("patch '%s' is deactivated\n", patch_file); + return 0; +} + +void unregister_target_uprobes(struct target_entity *target) +{ + struct patched_offset *off, *tmp_off; + + list_for_each_entry_safe(off, tmp_off, &target->off_head, list) { + uprobe_unregister(target->inode, off->offset, &patch_consumer); + log_debug("unregister uprobe on '%s' (inode: %lu) at 0x%llx\n", + target->path, target->inode->i_ino, off->offset); + + list_del(&off->list); + kfree(off); + } +} \ No newline at end of file diff --git a/upatch-manage/upatch-relocation.h b/upatch-manage/patch_manage.h similarity index 60% rename from upatch-manage/upatch-relocation.h rename to upatch-manage/patch_manage.h index 804656ee..c27b2aff 100644 --- a/upatch-manage/upatch-relocation.h +++ b/upatch-manage/patch_manage.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * upatch-manage + * provide kload kactive kdeactive kremove API to manage patch * Copyright (C) 2024 Huawei Technologies Co., Ltd. * * This program is free software; you can redistribute it and/or modify @@ -18,25 +18,22 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __UPATCH_RELOCATION__ -#define __UPATCH_RELOCATION__ +#ifndef _UPATCH_MANAGE_PATCH_MANAGE_H +#define _UPATCH_MANAGE_PATCH_MANAGE_H -#include +enum upatch_status; +struct target_entity; -#include "log.h" -#include "upatch-common.h" -#include "upatch-elf.h" +enum upatch_status upatch_status(const char *patch_file); -// TODO: change define -#define s64 int64_t -#define u64 uint64_t -#define u32 uint32_t -#define s32 int32_t -#define u16 uint16_t -#define s16 int16_t +int upatch_load(const char *patch_file, const char *binary_file); -int apply_relocate_add(struct upatch_elf *, unsigned int, unsigned int); +int upatch_remove(const char *patch_file); -int apply_relocations(struct upatch_elf *); +int upatch_active(const char *patch_file); -#endif +int upatch_deactive(const char *patch_file); + +void unregister_target_uprobes(struct target_entity *target); + +#endif // _UPATCH_MANAGE_PATCH_MANAGE_H diff --git a/upatch-manage/process_entity.c b/upatch-manage/process_entity.c new file mode 100644 index 00000000..cfa824f9 --- /dev/null +++ b/upatch-manage/process_entity.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * maintain userspace process info if it have loaded a hot patch + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "process_entity.h" + +#include + +#include "target_entity.h" +#include "util.h" + +void free_patch_info(struct patch_info *info) +{ + struct pc_pair *pair; + struct hlist_node *tmp; + int bkt; + + hash_for_each_safe(info->pc_maps, bkt, tmp, pair, node) { + hash_del(&pair->node); + kfree(pair); + } + + kfree(info); +} + +// process may be exited already, free loaded patch info +void free_process(struct process_entity* process) +{ + struct patch_info *info, *tmp; + pid_t pid; + + pid = pid_nr(process->pid_s); + + log_debug("free process tgid %d\n", pid); + + put_pid(process->pid_s); + + mutex_lock(&process->lock); + list_for_each_entry_safe(info, tmp, &process->loaded_patches, list) { + list_del(&info->list); + free_patch_info(info); + } + + list_del(&process->list); + mutex_unlock(&process->lock); + kfree(process); +} + +// we use struct pid to reference different process +static struct process_entity *do_get_process_and_free_exit_process(struct target_entity *target) +{ + struct process_entity *process, *tmp, *res = NULL; + struct pid *pid_s; + struct task_struct *task; + + pid_s = get_task_pid(current, PIDTYPE_TGID); + + list_for_each_entry_safe(process, tmp, &target->process_head, list) { + task = get_pid_task(process->pid_s, PIDTYPE_TGID); + if (!task) { + // old process is exit, so task is NULL, free it + free_process(process); + continue; + } + put_task_struct(task); + + if (process->pid_s != pid_s) { + continue; + } + + res = process; + break; + } + + put_pid(pid_s); + return res; +} + +static struct process_entity *new_process(struct target_entity *target) +{ + struct process_entity *process = kzalloc(sizeof(struct process_entity), GFP_KERNEL); + if (!process) { + return NULL; + } + + log_debug("Create process tgid %d for %s\n", task_tgid_nr(current), target->path); + process->pid_s = get_task_pid(current, PIDTYPE_TGID); + process->task = current; + INIT_LIST_HEAD(&process->loaded_patches); + mutex_init(&process->lock); + list_add(&process->list, &target->process_head); + return process; +} + +struct process_entity *get_process(struct target_entity *target) +{ + struct process_entity *process = NULL; + + mutex_lock(&target->process_lock); + process = do_get_process_and_free_exit_process(target); + if (!process) { + process = new_process(target); + if (!process) { + log_err("cannot alloc process tgid %d, for target %s\n", + task_tgid_nr(current), target->path); + goto out; + } + } + +out: + mutex_unlock(&target->process_lock); + return process; +} diff --git a/upatch-manage/upatch-relocation.c b/upatch-manage/process_entity.h similarity index 32% rename from upatch-manage/upatch-relocation.c rename to upatch-manage/process_entity.h index e9b79dd8..b2f767fa 100644 --- a/upatch-manage/upatch-relocation.c +++ b/upatch-manage/process_entity.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * upatch-manage + * maintain userspace process info if it have loaded a hot patch * Copyright (C) 2024 Huawei Technologies Co., Ltd. * * This program is free software; you can redistribute it and/or modify @@ -18,43 +18,59 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "upatch-relocation.h" -#include - -#include "log.h" - -int apply_relocations(struct upatch_elf *uelf) -{ - unsigned int i; - int err = 0; - - /* Now do relocations. */ - for (i = 1; i < uelf->info.hdr->e_shnum; i++) { - unsigned int infosec = uelf->info.shdrs[i].sh_info; - const char *name = uelf->info.shstrtab + uelf->info.shdrs[i].sh_name; - - /* Not a valid relocation section? */ - if (infosec >= uelf->info.hdr->e_shnum) { - continue; - } - - /* Don't bother with non-allocated sections */ - if (!(uelf->info.shdrs[infosec].sh_flags & SHF_ALLOC)) { - continue; - } - - log_debug("Relocate section '%s':\n", name); - if (uelf->info.shdrs[i].sh_type == SHT_REL) { - return -EPERM; - } else if (uelf->info.shdrs[i].sh_type == SHT_RELA) { - err = apply_relocate_add(uelf, uelf->index.sym, i); - } - log_debug("\n"); - - if (err < 0) { - break; - } - } - - return err; -} +#ifndef _UPATCH_MANAGE_PROCESS_ENTITY_H +#define _UPATCH_MANAGE_PROCESS_ENTITY_H + +#include +#include +#include + +#include + +struct pid; +struct task_struct; + +struct patch_entity; +struct target_entity; + +// we assume one patch will only modify less than 2^4 = 16 old funcs in target +#define FUNC_HASH_BITS 4 + +struct pc_pair { + unsigned long old_pc; + unsigned long new_pc; + struct hlist_node node; // maintain pc pair in hash table +}; + +struct patch_info { + struct patch_entity *patch; + struct list_head list; + DECLARE_HASHTABLE(pc_maps, FUNC_HASH_BITS); +}; + +// target may be loaded into different process +// due to latency of uprobe handle, process may dealy patch loading +struct process_entity { + struct pid* pid_s; + struct task_struct* task; + struct patch_info *active_info; + + // multi-thread may trap and run uprobe_handle, we only need one to load patch + struct mutex lock; + + // loaded but deactive patch will not free from VMA because the function of deactive patch may in call stack + // so we have to maintain all patches the process loaded + // For example we load and active p1, p2, p3, the patches list will be p3->p2->p1 + // when we want to active p2, we just look through this list and active p2 to avoid load p2 again + struct list_head loaded_patches; // patch_info list head + + struct list_head list; +}; + +struct process_entity *get_process(struct target_entity *target); + +void free_process(struct process_entity *process); + +void free_patch_info(struct patch_info *info); + +#endif // _UPATCH_MANAGE_PROCESS_ENTITY_H diff --git a/upatch-manage/symbol_resolve.c b/upatch-manage/symbol_resolve.c new file mode 100644 index 00000000..671c412e --- /dev/null +++ b/upatch-manage/symbol_resolve.c @@ -0,0 +1,662 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * resolve UND symbol in target or VMA so + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "symbol_resolve.h" + +#include +#include + +#include "target_entity.h" + +#include "patch_load.h" +#include "arch/patch_load.h" +#include "kernel_compat.h" +#include "util.h" + +static inline bool is_same_name(const char *name, const char *name2) +{ + return strcmp(name, name2) == 0; +} + +static unsigned long resolve_from_patch_sym(const struct running_elf *relf, + const char *name, Elf_Sym *patch_sym) +{ + const struct target_metadata *elf = relf->meta; + unsigned long elf_addr = 0; + + if (!elf) { + return 0; + } + + if (!patch_sym->st_value) { + return 0; + } + + elf_addr = relf->vma_start_addr + patch_sym->st_value; + log_debug("patch .sym '%s' at 0x%lx\n", name, elf_addr); + + return elf_addr; +} + +/* handle external object, we need get it's address, used for R_X86_64_REX_GOTPCRELX */ +static unsigned long resolve_from_rela_dyn(const struct running_elf *relf, + const char *name, Elf_Sym *patch_sym) +{ + const struct target_metadata *elf = relf->meta; + unsigned int i; + Elf_Sym *dynsym = elf->dynsym; + unsigned long elf_addr; + char *sym_name; + Elf_Rela *rela_dyn = elf->rela_dyn; + void __user *got_addr; + + if (!dynsym || !rela_dyn || !elf->dynstr) { + return 0; + } + + for (i = 0; i < elf->num.rela_dyn; i++) { + unsigned long sym_idx = ELF_R_SYM(rela_dyn[i].r_info); + if (!sym_idx) { + continue; + } + + /* function could also be part of the GOT with the type R_X86_64_GLOB_DAT */ + sym_name = elf->dynstr + dynsym[sym_idx].st_name; + + if (!is_same_name(sym_name, name)) { + continue; + } + + /* for executable file, r_offset is virtual address of GOT table */ + got_addr = (void *)(relf->vma_start_addr + rela_dyn[i].r_offset); + elf_addr = insert_got_table(relf->load_info, ELF_R_TYPE(rela_dyn[i].r_info), got_addr); + + log_info(".got symbol %s at 0x%lx offset = 0x%zx\n", + sym_name, elf_addr, (size_t)rela_dyn[i].r_offset); + + return elf_addr; + } + + return 0; +} + +static unsigned long resolve_from_rela_plt(const struct running_elf *relf, + const char *name, Elf_Sym *patch_sym) +{ + const struct target_metadata *elf = relf->meta; + unsigned int i; + Elf_Sym *dynsym = elf->dynsym; + char *sym_name; + Elf_Rela *rela_plt = elf->rela_plt; + unsigned long elf_addr = 0; + void __user *plt_addr; + + if (!dynsym || !rela_plt || !elf->dynstr) { + return 0; + } + + for (i = 0; i < elf->num.rela_plt; i++) { + unsigned long sym_idx = ELF_R_SYM(rela_plt[i].r_info); + unsigned long sym_type = ELF_ST_TYPE(dynsym[sym_idx].st_info); + if (!sym_idx) { + continue; + } + + if ((sym_type != STT_FUNC) && + (sym_type != STT_TLS) && + (sym_type != STT_NOTYPE)) { + continue; + } + + sym_name = elf->dynstr + dynsym[sym_idx].st_name; + if (!is_same_name(sym_name, name)) { + continue; + } + + /* for executable file, r_offset is virtual address of PLT table */ + plt_addr = (void *)(relf->vma_start_addr + rela_plt[i].r_offset); + elf_addr = insert_plt_table(relf->load_info, ELF_R_TYPE(rela_plt[i].r_info), plt_addr); + if (!elf_addr) { + return 0; + } + log_info("rela.plt sym %s offset = 0x%zx plt_addr = 0x%zx\n", + sym_name, (size_t)rela_plt[i].r_offset, (size_t)plt_addr); + return elf_addr; + } + + return 0; +} + +// get symbol address from .dynsym +static unsigned long resolve_from_dynsym(const struct running_elf *relf, const char *name) +{ + const struct target_metadata *elf = relf->meta; + unsigned int i; + Elf_Sym *dynsym = elf->dynsym; + char *sym_name; + unsigned long elf_addr = 0; + void __user *tmp_addr; + + if (!dynsym) { + return 0; + } + + for (i = 0; i < elf->num.dynsym; i++) { + /* only need the st_value that is not 0 */ + if (dynsym[i].st_value == 0) { + continue; + } + + sym_name = elf->dynstr + dynsym[i].st_name; + + /* function could also be part of the GOT with the type R_X86_64_GLOB_DAT */ + if (!is_same_name(sym_name, name)) { + continue; + } + + tmp_addr = (void *)(relf->vma_start_addr + dynsym[i].st_value); + elf_addr = insert_got_table(relf->load_info, 0, tmp_addr); + log_info(".dysym symbol %s at 0x%lx\n", sym_name, elf_addr); + return elf_addr; + } + + return 0; +} + +static u32 sysv_hash(const unsigned char *name) +{ + u32 h = 0, g; + for (; *name; name++) { + h = (h << 4) + *name; + g = h & 0xf0000000; + if (g) { + h ^= g >> 24; + } + h &= ~g; + } + return h; +} + +static u32 gnu_hash(const char *name) +{ + u32 h = 5381; + for (; *name; name++) { + h += h * 32 + *name; + } + return h; +} + +struct dynamic_shared_object { + Elf_Ehdr ehdr; + Elf_Shdr *shdrs; + Elf_Dyn *dynamic; + Elf_Sym *dynsym; + Elf_Half *versions; + Elf_Word *sysv_hash_buf; + Elf_Word *gnu_hash_buf; + char *strtab; + + Elf_Word dynstr_idx; + unsigned int dynnum; + unsigned int symnum; +}; + +static void free_so(struct dynamic_shared_object *so) +{ + VFREE_CLEAR(so->shdrs); + VFREE_CLEAR(so->dynamic); + VFREE_CLEAR(so->dynsym); + VFREE_CLEAR(so->versions); + VFREE_CLEAR(so->sysv_hash_buf); + VFREE_CLEAR(so->gnu_hash_buf); + VFREE_CLEAR(so->strtab); +} + +static int parse_so(struct file *file, struct dynamic_shared_object *so) +{ + int ret = 0; + Elf_Shdr *shdr; + unsigned long i; + + // read elf header + ret = kernel_read(file, &so->ehdr, sizeof(Elf_Ehdr), 0); + if (ret != sizeof(Elf_Ehdr)) { + log_err("failed to read elf header, ret=%d\n", ret); + return -ENOEXEC; + } + + // read section headers + so->shdrs = vmalloc_read(file, so->ehdr.e_shoff, so->ehdr.e_shentsize * so->ehdr.e_shnum); + if (IS_ERR(so->shdrs)) { + ret = PTR_ERR(so->shdrs); + log_err("failed to read section header, ret=%d\n", ret); + return ret; + } + + // Find the dynamic table and dynamic symbol table + for (i = 0; i < so->ehdr.e_shnum; ++i) { + shdr = &so->shdrs[i]; + switch (shdr->sh_type) { + case SHT_DYNSYM: + so->dynsym = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + so->symnum = shdr->sh_size / shdr->sh_entsize; + so->dynstr_idx = shdr->sh_link; + break; + case SHT_DYNAMIC: + so->dynamic = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + so->dynnum = shdr->sh_size / shdr->sh_entsize; + break; + case SHT_HASH: + so->sysv_hash_buf = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + break; + case SHT_GNU_HASH: + so->gnu_hash_buf = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + break; + default: + break; + } + } + + if (IS_ERR(so->dynsym)) { + ret = PTR_ERR(so->dynsym); + log_err("failed to read dynamic symbol table, ret=%d\n", ret); + goto fail; + } + + if (IS_ERR(so->dynamic)) { + ret = PTR_ERR(so->dynamic); + log_err("failed to read dynamic table, ret=%d\n", ret); + goto fail; + } + + if (IS_ERR(so->sysv_hash_buf)) { + ret = PTR_ERR(so->sysv_hash_buf); + log_err("failed to read sysv hash table, ret=%d\n", ret); + goto fail; + } + + if (IS_ERR(so->gnu_hash_buf)) { + ret = PTR_ERR(so->gnu_hash_buf); + log_err("failed to read gnu hash table, ret=%d\n", ret); + goto fail; + } + + if (!so->sysv_hash_buf && !so->gnu_hash_buf) { + log_debug("This so doesn't have hash table!\n"); + } + + // read the version table for symbols + for (i = 1; i < so->dynnum; ++i) { + if (so->dynamic[i].d_tag != DT_VERSYM) { + continue; + } + so->versions = vmalloc_read(file, so->dynamic[i].d_un.d_val, sizeof(Elf_Half) * so->symnum); + if (IS_ERR(so->versions)) { + ret = PTR_ERR(so->versions); + log_err("failed to read version table, ret=%d\n", ret); + goto fail; + } + break; + } + + // Read the string table for symbols + shdr = &so->shdrs[so->dynstr_idx]; + so->strtab = vmalloc_read(file, shdr->sh_offset, shdr->sh_size); + if (IS_ERR(so->strtab)) { + ret = PTR_ERR(so->strtab); + log_err("failed to read symbol string table, ret=%d\n", ret); + goto fail; + } + + return 0; + +fail: + free_so(so); + return ret; +} + +u32 g_hidden_version_sym_idx; +static bool is_sym_ok(struct dynamic_shared_object *so, u32 symidx, const char *name) +{ + char *sym_name = so->strtab + so->dynsym[symidx].st_name; + if (!is_same_name(name, sym_name)) { + return false; + } + + // only glibc .so will have version symbol, other so->versions could be NULL + // if found hidden version which is xxx@GLIBC_n.nn we keep the first found one and search default version sym. + if (so->versions && (so->versions[symidx] & VERSYM_HIDDEN)) { + if (!g_hidden_version_sym_idx) { + g_hidden_version_sym_idx = symidx; + } + return false; + } else { + // if we found default symbol, which is xxx@GLIBC_n.nn, just use it and ignore hidden version + return true; + } +} + +// return symbol index if found, 0 for not found. +static unsigned long search_by_gnu_hash_table(struct dynamic_shared_object *so, const char *name) +{ + const u32 nbuckets = so->gnu_hash_buf[0]; + const u32 symoffset = so->gnu_hash_buf[1]; + const u32 bloom_size = so->gnu_hash_buf[2]; + const u32 bloom_shift = so->gnu_hash_buf[3]; + const bloom_t *bloom = (void*)&so->gnu_hash_buf[4]; + const u32 *buckets = (void*)&bloom[bloom_size]; + const u32 *chain = &buckets[nbuckets]; + + const u32 *hashval; + u32 hash, hash2; + bloom_t bloom_word, mask; + u32 symidx; + + hash = gnu_hash(name); + hash2 = hash >> bloom_shift; + + // Test the Bloom filter + mask = ((bloom_t)1 << (hash % ELF_BITS)) | ((bloom_t)1 << (hash2 % ELF_BITS)); + + bloom_word = bloom[(hash / ELF_BITS) % bloom_size]; + if ((bloom_word & mask) != mask) { + return 0; + } + + symidx = buckets[hash % nbuckets]; + if (symidx < symoffset) { + return 0; + } + + hashval = &chain[symidx - symoffset]; + + for (hash |= 1; ; symidx++) { + hash2 = *hashval; + hashval++; + if (hash == (hash2 | 1) && is_sym_ok(so, symidx, name)) { + return symidx; + } + + if (hash2 & 1) { + break; + } + } + + return 0; +} + +// return symbol index if found, 0 for not found. +static unsigned long search_by_sysv_hash_table(struct dynamic_shared_object *so, const char *name) +{ + const u32 nbucket = so->sysv_hash_buf[0]; + const u32* bucket = &so->sysv_hash_buf[2]; + const u32* chain = &so->sysv_hash_buf[nbucket]; + + const u32 hash = sysv_hash(name); + u32 i; + + for (i = bucket[hash % nbucket]; i; i = chain[i]) { + if (is_sym_ok(so, i, name)) return i; + } + + return 0; +} + +static unsigned long search_by_iterate_symtab(struct dynamic_shared_object *so, const char *name) +{ + u32 i; + for (i = 1; i < so->symnum; ++i) { + if (is_sym_ok(so, i, name)) return i; + } + return 0; +} + +static unsigned long find_sym_st_value_in_elf(struct file *file, const char *name, char *type) +{ + const char *file_name = file->f_path.dentry->d_name.name; + Elf_Sym *sym = NULL; + unsigned long offset = 0; + unsigned long symidx = 0; + struct dynamic_shared_object so = {0}; + + log_debug("'%s'\t search in file %s\n", name, file_name); + + if (parse_so(file, &so)) { + log_err("Failed to parse %s\n", file_name); + return 0; + } + + g_hidden_version_sym_idx = 0; + if (so.gnu_hash_buf) { + // gnu hash is faster to search symbol + symidx = search_by_gnu_hash_table(&so, name); + } else if (so.sysv_hash_buf) { + symidx = search_by_sysv_hash_table(&so, name); + } else { + symidx = search_by_iterate_symtab(&so, name); + } + + if (!symidx && g_hidden_version_sym_idx) { + // we only found one hidden version, use it. + log_debug("[%d]: %s is hidden version!\n", g_hidden_version_sym_idx, name); + symidx = g_hidden_version_sym_idx; + } + + if (!symidx) { + goto out; + } + + sym = &so.dynsym[symidx]; + + if (sym->st_shndx == SHN_UNDEF) { + log_debug("symbol %s is UND, skip it\n", name); + goto out; + } + + // check symbol type + if (!(1 << (ELF_ST_TYPE(sym->st_info)) & OK_TYPES)) { + log_debug("symbol %s type is %d, skip it\n", name, ELF_ST_TYPE(sym->st_info)); + goto out; + } + + // check symbol bind + if (!(1 << (ELF_ST_BIND(sym->st_info)) & OK_BINDS)) { + log_debug("symbol %s bind is %d, skip it\n", name, ELF_ST_BIND(sym->st_info)); + goto out; + } + + offset = sym->st_value; + *type = ELF_ST_TYPE(sym->st_info); + +out: + if (offset == 0) + log_debug("cannot find symbol %s in file %s\n", name, file_name); + else + log_debug("'%s'\t offset 0x%lx in %s\n", name, offset, file_name); + free_so(&so); + return offset; +} + +// Check if the current VMA is the text segment of shared object and is not the patched target itself +static inline bool is_vma_other_so_text(const struct running_elf *relf, struct vm_area_struct *vma) +{ + const char *file_path; + bool is_so; + if (!(vma->vm_file && (vma->vm_flags & VM_EXEC))) { + return false; + } + + file_path = vma->vm_file->f_path.dentry->d_name.name; + if (!file_path) { + return false; + } + + is_so = strstr(file_path, ".so") != NULL; + if (!is_so) { + return false; + } + + if (strcmp(file_path, relf->meta->file_name) == 0) { + return false; + } + + return true; +} + +/* Caller must hold mm->mmap_lock */ +static unsigned long search_base_addr(struct vm_area_struct *vma) +{ + unsigned long base_addr = vma->vm_start; + struct vm_area_struct *search_vma; + struct upatch_vma_iter vmi; + + // A file could be map into multiple VMA, find the first one + upatch_vma_iter_set(&vmi, vma); + while ((search_vma = upatch_vma_prev(&vmi))) { + if (!search_vma->vm_file) { + continue; + } + if (search_vma->vm_file->f_inode->i_sb->s_dev == vma->vm_file->f_inode->i_sb->s_dev && + search_vma->vm_file->f_inode->i_ino == vma->vm_file->f_inode->i_ino) { + base_addr = search_vma->vm_start; + } + } + + return base_addr; +} + +// Search all loaded so in current VMA, read the symbol table of so and find symbol offset +// Then combine with the so loaded base address, we can get the symbol loaded address +static unsigned long resolve_from_vma_so(const struct running_elf *relf, const char *symbol_name) +{ + unsigned long addr = 0; + unsigned long res; + struct vm_area_struct *vma; + struct mm_struct *mm = current->mm; + struct upatch_vma_iter vmi; + + char type; + unsigned long base_addr; + + if (!mm) { + return 0; + } + + mmap_read_lock(mm); + upatch_vma_iter_init(&vmi, mm); + while ((vma = upatch_vma_next(&vmi))) { + if (!is_vma_other_so_text(relf, vma)) { + continue; + } + + // Search for the symbol in the shared object + addr = find_sym_st_value_in_elf(vma->vm_file, symbol_name, &type); + if (addr == 0) { + continue; + } + + base_addr = search_base_addr(vma); + addr += base_addr; + if ((type & STT_FUNC) || (type & STT_IFUNC)) { + res = setup_jmp_table(relf->load_info, addr, type == STT_IFUNC); + log_debug("%-20s (%s) JMP: 0x%lx -> 0x%lx (base 0x%lx)\n", + symbol_name, type == STT_IFUNC ? "IFUNC" : "FUNC", res, addr, base_addr); + } else { + res = setup_got_table(relf->load_info, addr, 0); + log_debug("%-20s (OBJECT) JMP: 0x%lx -> 0x%lx (base 0x%lx)\n", + symbol_name, res, addr, base_addr); + } + addr = res; + break; + } + mmap_read_unlock(mm); + + return addr; +} + +static unsigned long resolve_from_sym(const struct running_elf *relf, const char *name) +{ + const struct target_metadata *elf = relf->meta; + unsigned int i; + Elf_Sym *sym = elf->symtab; + char *sym_name; + unsigned long addr; + + if (!sym || !elf->strtab) { + return 0; + } + + for (i = 0; i < elf->num.symtab; i++) { + if (sym[i].st_shndx == SHN_UNDEF) { + continue; + } + sym_name = elf->strtab + sym[i].st_name; + + if (is_same_name(sym_name, name)) { + log_info("found resolved undefined symbol %s at 0x%llx\n", + name, (long long)sym[i].st_value); + addr = relf->vma_start_addr + sym[i].st_value; + return addr; + } + } + + return 0; +} + +/* + * Handle external UND symbol: + * 1. use symbol address from .dynsym, but most of its address is still undefined + * 2. use address from PLT/GOT, problems are: + * 3. read symbol from library that is loaded into VMA for the new called sym in .so + */ +unsigned long resolve_symbol(const struct running_elf *relf, const char *name, Elf_Sym patch_sym) +{ + unsigned long elf_addr = 0; + + if (!elf_addr) { + elf_addr = resolve_from_vma_so(relf, name); + } + + if (!elf_addr) { + elf_addr = resolve_from_rela_plt(relf, name, &patch_sym); + } + + /* resolve from got */ + if (!elf_addr) { + elf_addr = resolve_from_rela_dyn(relf, name, &patch_sym); + } + + if (!elf_addr) { + elf_addr = resolve_from_dynsym(relf, name); + } + + if (!elf_addr) { + elf_addr = resolve_from_sym(relf, name); + } + + if (!elf_addr) { + elf_addr = resolve_from_patch_sym(relf, name, &patch_sym); + } + + if (!elf_addr) { + log_err("unable to found UND symbol %s\n", name); + } + return elf_addr; +} \ No newline at end of file diff --git a/upatch-manage/upatch-resolve.h b/upatch-manage/symbol_resolve.h similarity index 45% rename from upatch-manage/upatch-resolve.h rename to upatch-manage/symbol_resolve.h index 62efc13b..4a0ad47c 100644 --- a/upatch-manage/upatch-resolve.h +++ b/upatch-manage/symbol_resolve.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * upatch-manage + * resolve UND symbol in target or VMA so * Copyright (C) 2024 Huawei Technologies Co., Ltd. * * This program is free software; you can redistribute it and/or modify @@ -18,29 +18,39 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __UPATCH_RESOLVE__ -#define __UPATCH_RESOLVE__ +#ifndef _UPATCH_SYMBOL_RESOLVE_H +#define _UPATCH_SYMBOL_RESOLVE_H -#include "upatch-elf.h" -#include "upatch-process.h" +#include +#include +#include -#define SHN_LIVEPATCH 0xff20 +/* This flag appears in a Versym structure. It means that the symbol + is hidden, and is only visible with an explicit version number. + This is a GNU extension. */ +#define VERSYM_HIDDEN 0x8000 -/* jmp table, solve limit for the jmp instruction, Used for both PLT/GOT */ -struct upatch_jmp_table_entry; +#define STT_IFUNC 0xa // when e_ident[EI_OSABI] == ELFOSABI_GNU/ELFOSABI_FREEBSD -unsigned int get_jmp_table_entry(void); +#define OK_TYPES (1 << STT_NOTYPE | 1 << STT_OBJECT | 1 << STT_FUNC | 1 << STT_COMMON | 1 << STT_TLS | 1 << STT_IFUNC) +#define OK_BINDS (1 << STB_GLOBAL | 1 << STB_WEAK) -unsigned long insert_plt_table(struct upatch_elf *, struct object_file *, - unsigned long, unsigned long); -unsigned long insert_got_table(struct upatch_elf *, struct object_file *, - unsigned long, unsigned long); +#ifndef SHT_GNU_HASH +#define SHT_GNU_HASH 0x6ffffff6 +#endif + +#ifndef ELF_BITS +# if ELF_CLASS == ELFCLASS64 +# define ELF_BITS 64 + typedef u64 bloom_t; +# else +# define ELF_BITS 32 + typedef u32 bloom_t; +# endif +#endif -unsigned long setup_got_table(struct upatch_elf *uelf, - unsigned long, unsigned long); -unsigned long search_insert_plt_table(struct upatch_elf *, - unsigned long, unsigned long); +struct running_elf; -int simplify_symbols(struct upatch_elf *, struct object_file *); +unsigned long resolve_symbol(const struct running_elf *relf, const char *name, Elf_Sym patch_sym); -#endif +#endif // _UPATCH_SYMBOL_RESOLVE_H diff --git a/upatch-manage/target_entity.c b/upatch-manage/target_entity.c new file mode 100644 index 00000000..e2e6f634 --- /dev/null +++ b/upatch-manage/target_entity.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * maintain info about the target binary file like executive or shared object + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "target_entity.h" + +#include +#include + +#include "patch_entity.h" +#include "process_entity.h" +#include "patch_manage.h" +#include "util.h" + +#define ELF_ADDR_MAX UINT_MAX + +#define SHSTRTAB_NAME ".shstrtab" +#define STRTAB_NAME ".strtab" +#define DYNSTR_NAME ".dynstr" +#define DYN_RELA_NAME ".rela.dyn" +#define PLT_RELA_NAME ".rela.plt" +#define DYN_REL_NAME ".rel.dyn" +#define PLT_REL_NAME ".rel.plt" + +DEFINE_HASHTABLE(g_targets, TARGETS_HASH_BITS); +DEFINE_MUTEX(g_target_table_lock); + +static void free_elf_meta(struct target_metadata *meta) +{ + KFREE_CLEAR(meta->file_name); + VFREE_CLEAR(meta->symtab); + VFREE_CLEAR(meta->dynsym); + VFREE_CLEAR(meta->dynamic); + VFREE_CLEAR(meta->rela_dyn); + VFREE_CLEAR(meta->rela_plt); + VFREE_CLEAR(meta->dynstr); + VFREE_CLEAR(meta->strtab); +} + +static int parse_target_load_addr(struct target_metadata *meta, struct file *target) +{ + int ret, size, i; + Elf_Ehdr elf_header; + Elf_Phdr *phdr = NULL; + Elf_Addr vma_base_addr = ELF_ADDR_MAX; + loff_t pos; + + meta->len = i_size_read(file_inode(target)); + + ret = kernel_read(target, &elf_header, sizeof(elf_header), 0); + if (ret != sizeof(elf_header)) { + log_err("failed to read elf header, ret=%d\n", ret); + ret = -ENOEXEC; + goto out; + } + + size = sizeof(Elf_Phdr) * elf_header.e_phnum; + phdr = kmalloc(size, GFP_KERNEL); + if (!phdr) { + log_err("failed to kmalloc program headers\n"); + ret = -ENOMEM; + goto out; + } + + pos = elf_header.e_phoff; + ret = kernel_read(target, phdr, size, &pos); + if (ret < 0) { + log_err("failed to read program headers, ret=%d\n", ret); + ret = -ENOEXEC; + goto out; + } + + for (i = 0; i < elf_header.e_phnum; i++) { + if (phdr[i].p_type == PT_LOAD) { + vma_base_addr = min(vma_base_addr, phdr[i].p_vaddr); + } + } + + for (i = 0; i < elf_header.e_phnum; i++) { + if ((phdr[i].p_type == PT_LOAD) && (phdr[i].p_flags & PF_X)) { + if (meta->code_vma_offset) { + log_err("found multiple executable PT_LOAD segments (expected one)\n"); + ret = -ENOEXEC; + goto out; + } + meta->code_vma_offset = (phdr[i].p_vaddr - vma_base_addr) & PAGE_MASK; + meta->code_virt_offset = phdr[i].p_vaddr - vma_base_addr - phdr[i].p_offset; + } + } + + ret = 0; +out: + KFREE_CLEAR(phdr); + return ret; +} + +static int process_section_header(struct file *target, Elf_Shdr *shdr, char *sh_name, struct target_metadata *meta) +{ + void *sh_data = NULL; + int ret = 0; + + if (shdr->sh_type == SHT_SYMTAB) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->symtab = sh_data; + meta->num.symtab = shdr->sh_size / sizeof(Elf_Sym); + } else if (strcmp(sh_name, STRTAB_NAME) == 0) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->strtab = sh_data; + } else if (strcmp(sh_name, DYNSTR_NAME) == 0) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->dynstr = sh_data; + } else if (strcmp(sh_name, DYN_RELA_NAME) == 0 || strcmp(sh_name, DYN_REL_NAME) == 0) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->rela_dyn = sh_data; + meta->num.rela_dyn = shdr->sh_size / sizeof(Elf_Rela); + } else if (strcmp(sh_name, PLT_RELA_NAME) == 0 || strcmp(sh_name, PLT_REL_NAME) == 0) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->rela_plt = sh_data; + meta->num.rela_plt = shdr->sh_size / sizeof(Elf_Rela); + } else if (shdr->sh_type == SHT_DYNAMIC) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->dynamic = sh_data; + } else if (shdr->sh_type == SHT_DYNSYM) { + sh_data = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + meta->dynsym = sh_data; + meta->num.dynsym = shdr->sh_size / sizeof(Elf_Sym); + } + + if (IS_ERR_VALUE(sh_data)) { + ret = PTR_ERR(sh_data); + log_err("failed to read section '%s'\n", sh_name); + } + return ret; +} + +static int init_target_meta(struct target_metadata *meta, struct file *target) +{ + int ret; + + Elf_Ehdr *ehdr = NULL; // elf header + Elf_Shdr *shdrs = NULL; // section headers + char *shstrtab = NULL; // section string table + Elf_Phdr *phdrs = NULL; + + Elf_Shdr *shdr; + Elf_Half i; + char *sh_name; + + const unsigned char *base_name; + + meta->file_name = NULL; + + // Check if target dentry are valid before accessing + if (!target->f_path.dentry) { + log_err("Invalid target file pointer or dentry\n"); + ret = -EINVAL; + goto out; + } + + base_name = target->f_path.dentry->d_name.name; + + meta->file_name = kstrdup(base_name, GFP_KERNEL); + if (!meta->file_name) { + log_err("Failed to allocate memory for filename\n"); + ret = -ENOMEM; + goto out; + } + + ret = parse_target_load_addr(meta, target); + if (ret) + goto out; + + // read elf header + ret = kernel_read(target, &meta->ehdr, sizeof(Elf_Ehdr), 0); + if (ret != sizeof(Elf_Ehdr)) { + ret = -ENOEXEC; + log_err("read elf header failed ret=%d\n", ret); + goto out; + } + + ehdr = &meta->ehdr; + if (!is_elf_valid(ehdr, i_size_read(file_inode(target)), false)) { + ret = -ENOEXEC; + log_err("invalid target format\n"); + goto out; + } + + // read section headers + shdrs = vmalloc_read(target, ehdr->e_shoff, ehdr->e_shentsize * ehdr->e_shnum); + if (IS_ERR(shdrs)) { + ret = PTR_ERR(shdrs); + log_err("failed to read section header\n"); + goto out; + } + + // read section header string table + shdr = &shdrs[ehdr->e_shstrndx]; + shstrtab = vmalloc_read(target, shdr->sh_offset, shdr->sh_size); + if (IS_ERR(shstrtab)) { + ret = PTR_ERR(shstrtab); + log_err("failed to read '%s' section\n", SHSTRTAB_NAME); + goto out; + } + + // resolve section headers + for (i = 1; i < ehdr->e_shnum; i++) { + shdr = &shdrs[i]; + sh_name = shstrtab + shdr->sh_name; + + ret = process_section_header(target, shdr, sh_name, meta); + if (ret) + goto out; + } + + phdrs = vmalloc_read(target, ehdr->e_phoff, ehdr->e_phentsize * ehdr->e_phnum); + if (IS_ERR(phdrs)) { + ret = PTR_ERR(phdrs); + log_err("failed to read program header\n"); + goto out; + } + + for (i = 0; i < ehdr->e_phnum; i++) { + if (phdrs[i].p_type == PT_TLS) { + meta->tls_size = phdrs[i].p_memsz; + meta->tls_align = phdrs[i].p_align; + log_debug("Found TLS size = %zd, align = %zd\n", + (size_t)meta->tls_size, (size_t)meta->tls_align); + break; + } + } + ret = 0; + +out: + if (ret != 0) { + free_elf_meta(meta); + } + VFREE_CLEAR(shdrs); + VFREE_CLEAR(shstrtab); + VFREE_CLEAR(phdrs); + return ret; +} + +static int init_grab_target(struct target_entity *target, const char *file_path) +{ + int ret = 0; + struct file *file = NULL; + + init_rwsem(&target->lock); + mutex_init(&target->process_lock); + INIT_HLIST_NODE(&target->node); + INIT_LIST_HEAD(&target->off_head); + INIT_LIST_HEAD(&target->active_patch_list); + INIT_LIST_HEAD(&target->all_patches_list); + INIT_LIST_HEAD(&target->process_head); + + // open target file + file = filp_open(file_path, O_RDONLY, 0); // open file by inode + if (IS_ERR(file)) { + log_err("failed to open file '%s'\n", file_path); + return PTR_ERR(file); + } + + target->inode = igrab(file_inode(file)); + if (!target->inode) { + pr_err("%s: Failed to grab inode of %s\n", __func__, file_path); + ret = -ENOENT; + goto fail; + } + + target->path = kstrdup(file_path, GFP_KERNEL); + if (!target->path) { + iput(target->inode); + ret = -ENOMEM; + goto fail; + } + + // resolve elf meta + ret = init_target_meta(&target->meta, file); + if (ret != 0) { + iput(target->inode); + KFREE_CLEAR(target->path); + log_err("failed to resolve elf meta\n"); + goto fail; + } + +fail: + filp_close(file, NULL); + return ret; +} + +struct target_entity *get_target_entity_from_inode(struct inode *inode) +{ + struct target_entity *target; + struct target_entity *found = NULL; + + mutex_lock(&g_target_table_lock); + hash_for_each_possible(g_targets, target, node, inode->i_ino) { + if (target->inode == inode) { + found = target; + break; + } + } + + mutex_unlock(&g_target_table_lock); + return found; +} + +/* public interface */ +struct target_entity *get_target_entity(const char *path) +{ + struct inode *inode = path_inode(path); + struct target_entity *target; + + log_debug("start to get target_entity for %s\n", path); + + if (IS_ERR(inode)) { + return NULL; + } + + inode = igrab(inode); + if (!inode) { + pr_err("failed to grab inode of %s\n", path); + return NULL; + } + + target = get_target_entity_from_inode(inode); + iput(inode); + return target; +} + +static void insert_target(struct target_entity *target) +{ + mutex_lock(&g_target_table_lock); + hash_add(g_targets, &target->node, target->inode->i_ino); + mutex_unlock(&g_target_table_lock); +} + +struct target_entity *new_target_entity(const char *file_path) +{ + struct target_entity *target = NULL; + int ret = 0; + + log_debug("create patch target entity '%s'\n", file_path); + + if (!file_path) { + return ERR_PTR(-EINVAL); + } + + target = kzalloc(sizeof(struct target_entity), GFP_KERNEL); + if (!target) { + log_err("failed to alloc target entity\n"); + return ERR_PTR(-ENOMEM); + } + + ret = init_grab_target(target, file_path); + if (ret != 0) { + log_err("failed to init patch target '%s', ret=%d\n", file_path, ret); + kfree(target); + return ERR_PTR(ret); + } + + insert_target(target); + return target; +} + +// caller should lock g_target_table_lock +void free_target_entity(struct target_entity *target) +{ + struct process_entity *process, *tmp_pro; + struct patch_entity *patch; + struct patched_offset *off; + + log_debug("free patch target '%s'\n", target->path); + down_write(&target->lock); + + list_for_each_entry(off, &target->off_head, list) { + log_err("found uprobe in 0x%lx\n", (unsigned long)off->offset); + } + + list_for_each_entry(patch, &target->active_patch_list, target_active_patch) { + log_err("found actived patch '%s'\n", patch->path ? patch->path : "NULL"); + } + + list_for_each_entry(patch, &target->all_patches_list, target_all_patch) { + log_err("found patch '%s'\n", patch->path ? patch->path : "NULL"); + } + + mutex_lock(&target->process_lock); + list_for_each_entry_safe(process, tmp_pro, &target->process_head, list) { + free_process(process); + } + mutex_unlock(&target->process_lock); + + iput(target->inode); + KFREE_CLEAR(target->path); + free_elf_meta(&target->meta); + hash_del(&target->node); + + unregister_target_uprobes(target); + + up_write(&target->lock); + + kfree(target); +} + +bool is_target_has_patch(const struct target_entity *target) +{ + return !list_empty(&target->all_patches_list); +} + +bool upatch_binary_has_addr(const struct target_entity *target, loff_t offset) +{ + struct patched_offset *addr = NULL; + + if (!target) { + return false; + } + + list_for_each_entry(addr, &target->off_head, list) { + if (addr->offset == offset) { + return true; + } + } + + return false; +} + +void __exit verify_empty_target_on_exit(void) +{ + struct target_entity *target; + int bkt; + + mutex_lock(&g_target_table_lock); + hash_for_each(g_targets, bkt, target, node) { + log_err("found target %s", target->path ? target->path : "(null)"); + } + mutex_unlock(&g_target_table_lock); +} \ No newline at end of file diff --git a/upatch-manage/target_entity.h b/upatch-manage/target_entity.h new file mode 100644 index 00000000..512b05c6 --- /dev/null +++ b/upatch-manage/target_entity.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * maintain info about the target binary file like executive or shared object + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UPATCH_MANAGE_TARGET_ENTITY_H +#define _UPATCH_MANAGE_TARGET_ENTITY_H + +#include +#include +#include + +#include +#include + +#define TARGETS_HASH_BITS 4 + +struct inode; + +struct upatch_function; + +/* target elf metadata */ +struct target_metadata { + unsigned long len; // file len + + Elf_Ehdr ehdr; + + char *file_name; + + // tables + Elf_Sym *symtab; + Elf_Sym *dynsym; + Elf_Dyn *dynamic; + Elf_Rela *rela_dyn; + Elf_Rela *rela_plt; + char *strtab; + char *dynstr; + + // table entity number + struct { + unsigned int symtab, rela_dyn, rela_plt, dynsym; + } num; + + Elf_Addr tls_size; + Elf_Addr tls_align; + + // In LLVM, offset != virtaddr, target VMA start != vma_code_start - offset + // code_vma_offset = VirtAddr round down to PAGE_SIZE + // The target VMA start = vma_code_start - code_vma_offset + Elf_Addr code_vma_offset; + + // code LOAD segment VirtAddr - offset + unsigned long code_virt_offset; +}; + +struct target_entity { + struct inode *inode; + char *path; + struct target_metadata meta; // store target elf info + + // there is only one thread to call active/deactive so we don't need to lock + struct list_head off_head; // list of file offset of active patch function for struct patched_offset + struct hlist_node node; // all target store in hash table + + // active patch list need lock, uprobe handle will read it, active method will write it + struct rw_semaphore lock; + struct list_head active_patch_list; + + // all patches related to this target, including active and deactive patches + // don't need lock. only load_patch, remove_patch, rmmod upatch_manage will read/write this list + // uprobe_handle will not use this list, and we limit there is only one thread to manage patch + struct list_head all_patches_list; + + // target ELF may run in different process (so) + // every process will have a active patch + struct mutex process_lock; // uprobe handle will call free_process, so we need lock + struct list_head process_head; +}; + +/* Patched address */ +struct patched_offset { + loff_t offset; // offset of the patched func addr + struct list_head funcs_head; // patched function list head + struct list_head list; // address list node +}; + +/* Patched function record */ +struct patched_func_node { + struct upatch_function *func; // patched function + struct list_head list; +}; + +/* + * Find a target entity + * @param ino: target file inode number + * @return target entity + */ +struct target_entity *get_target_entity(const char* target_path); + +struct target_entity *get_target_entity_from_inode(struct inode *inode); + +/* + * Load a target entity + * @param file_path: target file path + * @return target entity + */ +struct target_entity *new_target_entity(const char *file_path); + +/* + * Remove a target entity + * @param target: target entity + * @return void + */ +void free_target_entity(struct target_entity *target); + +/* + * Check if a target has related patches. DEACTIVE/ACTIVE patches are all counted + * @param target: target entity + * @param offset: target offset + * @return result + */ +bool is_target_has_patch(const struct target_entity *target); + +/* + * Check if a target offset has been patched + * @param target: target entity + * @param offset: target offset + * @return result + */ +bool upatch_binary_has_addr(const struct target_entity *target, loff_t offset); + +void __exit verify_empty_target_on_exit(void); + +#endif // _UPATCH_MANAGE_TARGET_ENTITY_H diff --git a/upatch-manage/upatch-common.c b/upatch-manage/upatch-common.c deleted file mode 100644 index f9076ee8..00000000 --- a/upatch-manage/upatch-common.c +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-lib - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include - -#include "upatch-common.h" - -bool streql(const char *a, const char *b) -{ - return strlen(a) == strlen(b) && !strncmp(a, b, strlen(a)); -} diff --git a/upatch-manage/upatch-elf.c b/upatch-manage/upatch-elf.c deleted file mode 100644 index f3773f90..00000000 --- a/upatch-manage/upatch-elf.c +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "upatch-common.h" -#include "upatch-elf.h" -#include "upatch-ptrace.h" - -static int read_from_offset(int fd, void **buf, unsigned long len, off_t offset) -{ - *buf = malloc(len); - if (*buf == NULL) { - return -errno; - } - - ssize_t size = pread(fd, *buf, len, offset); - if (size == -1) { - return -errno; - } - - return 0; -} - -static int open_elf(struct elf_info *einfo, const char *name) -{ - int ret = 0; - int fd = -1; - struct stat st; - - fd = open(name, O_RDONLY); - if (fd == -1) { - ret = -errno; - log_error("Failed to open file '%s'\n", name); - goto out; - } - - ret = stat(name, &st); - if (ret != 0) { - ret = -errno; - log_error("Failed to stat file '%s'\n", name); - goto out; - } - - ret = read_from_offset(fd, (void **)&einfo->patch_buff, - (unsigned long)st.st_size, 0); - if (ret != 0) { - log_error("Failed to read file '%s'\n", name); - goto out; - } - - einfo->name = name; - einfo->inode = st.st_ino; - einfo->patch_size = (unsigned long)st.st_size; - einfo->hdr = (void *)einfo->patch_buff; - einfo->shdrs = (void *)einfo->hdr + einfo->hdr->e_shoff; - einfo->shstrtab = (void *)einfo->hdr + - einfo->shdrs[einfo->hdr->e_shstrndx].sh_offset; - - void *einfo_eof = einfo->hdr + einfo->patch_size; - if ((void *)einfo->shdrs > einfo_eof || - (void *)einfo->shstrtab > einfo_eof) { - log_error("File '%s' is not a valid elf\n", name); - ret = -ENOEXEC; - goto out; - } - - ret = 0; - -out: - if (fd > 0) { - close(fd); - } - return ret; -} - -int upatch_init(struct upatch_elf *uelf, const char *name) -{ - int ret = open_elf(&uelf->info, name); - if (ret) { - log_error("Failed to open file '%s'\n", name); - return ret; - } - - for (unsigned int i = 1; i < uelf->info.hdr->e_shnum; ++i) { - char *sec_name = uelf->info.shstrtab + uelf->info.shdrs[i].sh_name; - if (uelf->info.shdrs[i].sh_type == SHT_SYMTAB) { - uelf->num_syms = uelf->info.shdrs[i].sh_size / sizeof(GElf_Sym); - uelf->index.sym = i; - uelf->index.str = uelf->info.shdrs[i].sh_link; - uelf->strtab = (char *)uelf->info.hdr + - uelf->info.shdrs[uelf->info.shdrs[i].sh_link].sh_offset; - } else if (streql(sec_name, UPATCH_FUNC_NAME)) { - uelf->index.upatch_funcs = i; - } else if (streql(sec_name, UPATCH_FUNC_STRING)) { - uelf->index.upatch_string = i; - } - } - - return 0; -} - -static bool is_pie_elf(struct running_elf *relf) -{ - GElf_Shdr *shdr = &relf->info.shdrs[relf->index.dynamic]; - GElf_Dyn *dyns = (void *)relf->info.hdr + shdr->sh_offset; - - if (relf->index.dynamic == 0) { - return false; - } - - for (Elf64_Xword i = 0; i < shdr->sh_size / sizeof(GElf_Dyn); i++) { - log_debug("Syminfo %lx, %lx\n", dyns[i].d_tag, dyns[i].d_un.d_val); - if (dyns[i].d_tag == DT_FLAGS_1) { - if ((dyns[i].d_un.d_val & DF_1_PIE) != 0) { - return true; - } - break; - } - } - - return false; -} - -static inline bool is_dyn_elf(struct running_elf *relf) -{ - return relf->info.hdr->e_type == ET_DYN; -} - -int binary_init(struct running_elf *relf, const char *name) -{ - int ret = open_elf(&relf->info, name); - if (ret) { - log_error("Failed to open file '%s'\n", name); - return ret; - } - - for (unsigned int i = 1; i < relf->info.hdr->e_shnum; i++) { - char *sec_name = relf->info.shstrtab + relf->info.shdrs[i].sh_name; - if (relf->info.shdrs[i].sh_type == SHT_SYMTAB) { - log_debug("Found section '%s', idx=%d\n", SYMTAB_NAME, i); - relf->num_syms = relf->info.shdrs[i].sh_size / sizeof(GElf_Sym); - relf->index.sym = i; - relf->index.str = relf->info.shdrs[i].sh_link; - relf->strtab = (char *)relf->info.hdr + - relf->info.shdrs[relf->info.shdrs[i].sh_link].sh_offset; - } else if (relf->info.shdrs[i].sh_type == SHT_DYNSYM) { - log_debug("Found section '%s', idx=%d\n", DYNSYM_NAME, i); - relf->index.dynsym = i; - relf->index.dynstr = relf->info.shdrs[i].sh_link; - relf->dynstrtab = (char *)relf->info.hdr + - relf->info.shdrs[relf->info.shdrs[i].sh_link].sh_offset; - } else if (relf->info.shdrs[i].sh_type == SHT_DYNAMIC) { - log_debug("Found section '%s', idx=%d\n", DYNAMIC_NAME, i); - relf->index.dynamic = i; - } else if (streql(sec_name, PLT_RELA_NAME) && - relf->info.shdrs[i].sh_type == SHT_RELA) { - log_debug("Found section '%s', idx=%d\n", PLT_RELA_NAME, i); - relf->index.rela_plt = i; - } else if (streql(sec_name, GOT_RELA_NAME) && - relf->info.shdrs[i].sh_type == SHT_RELA) { - log_debug("Found section '%s' idx=%d\n", GOT_RELA_NAME, i); - relf->index.rela_dyn = i; - } - } - - relf->phdrs = (void *)relf->info.hdr + relf->info.hdr->e_phoff; - for (int i = 0; i < relf->info.hdr->e_phnum; i++) { - if (relf->phdrs[i].p_type == PT_TLS) { - relf->tls_size = relf->phdrs[i].p_memsz; - relf->tls_align = relf->phdrs[i].p_align; - log_debug("Found TLS size = %ld, align = %ld\n", - relf->tls_size, relf->tls_align); - break; - } - } - - relf->info.is_pie = is_pie_elf(relf); - relf->info.is_dyn = is_dyn_elf(relf); - - return 0; -} - -void binary_close(struct running_elf *relf) -{ - if (relf->info.patch_buff) { - free(relf->info.patch_buff); - } -} - -void upatch_close(struct upatch_elf *uelf) -{ - if (uelf->info.patch_buff) { - free(uelf->info.patch_buff); - } - if (uelf->core_layout.kbase) { - free(uelf->core_layout.kbase); - } -} - -bool is_upatch_section(const char *name) -{ - return !strncmp(name, ".upatch.", strlen(".upatch.")); -} - -bool is_note_section(GElf_Word type) -{ - return type == SHT_NOTE; -} diff --git a/upatch-manage/upatch-elf.h b/upatch-manage/upatch-elf.h deleted file mode 100644 index 6f62c7df..00000000 --- a/upatch-manage/upatch-elf.h +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef __UPATCH_FILE__ -#define __UPATCH_FILE__ - -#include -#include -#include -#include - -#include "list.h" - -#define SYMTAB_NAME ".symtab" -#define DYNSYM_NAME ".dynsym" -#define DYNAMIC_NAME ".dynamic" -#define GOT_RELA_NAME ".rela.dyn" -#define PLT_RELA_NAME ".rela.plt" -#define BUILD_ID_NAME ".note.gnu.build-id" -#define UPATCH_FUNC_NAME ".upatch.funcs" -#define UPATCH_FUNC_STRING ".upatch.strings" -#define TDATA_NAME ".tdata" -#define TBSS_NAME ".tbss" - -#define JMP_TABLE_MAX_ENTRY 4096 -#define UPATCH_HEADER "UPATCH" -#define UPATCH_HEADER_LEN 6 -#define UPATCH_ID_LEN 40 - -struct upatch_func_addr { - unsigned long new_addr; - unsigned long new_size; - unsigned long old_addr; - unsigned long old_size; -}; - -struct upatch_info_func { - struct upatch_func_addr addr; - unsigned long old_insn[2]; - unsigned long new_insn; - char *name; -}; - -struct upatch_info { - char magic[7]; // upatch magic - char id[UPATCH_ID_LEN + 1]; // upatch id - unsigned long size; // upatch_info and upatch_info_func size - unsigned long start; // upatch vma start - unsigned long end; // upatch vma end - unsigned long changed_func_num; - struct upatch_info_func *funcs; - char *func_names; - unsigned long func_names_size; -}; - -struct upatch_layout { - /* The actual code + data. */ - void *kbase; - void *base; - /* Total size. */ - unsigned long size; - /* The size of the executable code. */ - unsigned long text_size; - /* Size of RO section of the module (text+rodata) */ - unsigned long ro_size; - /* Size of RO after init section, not use it now */ - unsigned long ro_after_init_size; - /* The size of the info. */ - unsigned long info_size; -}; - -struct upatch_patch_func { - struct upatch_func_addr addr; - unsigned long sympos; /* handle local symbols */ - char *name; -}; - -struct elf_info { - const char *name; - ino_t inode; - void *patch_buff; - size_t patch_size; - - GElf_Ehdr *hdr; - GElf_Shdr *shdrs; - char *shstrtab; - - unsigned int num_build_id; - bool is_pie; - bool is_dyn; -}; - -struct running_elf { - struct elf_info info; - - unsigned long num_syms; - char *strtab; - char *dynstrtab; - - GElf_Phdr *phdrs; - GElf_Xword tls_size; - GElf_Xword tls_align; - - struct { - unsigned int sym, str; - unsigned int rela_dyn, rela_plt; - unsigned int dynsym, dynstr, dynamic; - } index; - - /* load bias, used to handle ASLR */ - unsigned long load_bias; - unsigned long load_start; -}; - -struct upatch_elf { - struct elf_info info; - - unsigned long num_syms; - char *strtab; - - struct { - unsigned int sym, str; - unsigned int upatch_funcs; - unsigned int upatch_string; - } index; - - unsigned long symoffs, stroffs, core_typeoffs; - unsigned long jmp_offs; - unsigned int jmp_cur_entry, jmp_max_entry; - - /* memory layout for patch */ - struct upatch_layout core_layout; - - struct running_elf *relf; -}; - -int upatch_init(struct upatch_elf *, const char *); -int binary_init(struct running_elf *, const char *); -void upatch_close(struct upatch_elf *); -void binary_close(struct running_elf *); - -bool is_upatch_section(const char *); - -bool is_note_section(GElf_Word); - -#endif diff --git a/upatch-manage/upatch-manage.c b/upatch-manage/upatch-manage.c deleted file mode 100644 index 98868b71..00000000 --- a/upatch-manage/upatch-manage.c +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "upatch-elf.h" -#include "upatch-patch.h" -#include "upatch-stack-check.h" - -#define PROG_VERSION "upatch-manage "BUILD_VERSION -#define COMMAND_SIZE 4 - -enum log_level g_loglevel = NORMAL; -char *g_logprefix; - -char *command[COMMAND_SIZE] = { "", "patch", "unpatch", "info" }; -enum Command { - DEFAULT, - PATCH, - UNPATCH, - INFO, -}; - -struct arguments { - int cmd; - int pid; - char *upatch; - char *binary; - char *uuid; - bool verbose; -}; - -static struct argp_option options[] = { - { "cmd", 0, "patch", 0, "Apply a upatch file to a process", 0 }, - { "cmd", 0, "unpatch", 0, "Unapply a upatch file to a process", 0 }, - { "pid", 'p', "pid", 0, "the pid of the user-space process", 0 }, - { "uuid", 'U', "uuid", 0, "the uuid of the upatch", 0 }, - { "upatch", 'u', "upatch", 0, "the upatch file", 0 }, - { "binary", 'b', "binary", 0, "the binary file", 0 }, - { "verbose", 'v', NULL, 0, "Show verbose output", 0 }, - { NULL } -}; - -static char program_doc[] = "Operate a upatch file on the process"; - -static char args_doc[] = " --pid --uuid " - "--upatch --binary "; - -const char *argp_program_version = PROG_VERSION; - -static error_t check_opt(struct argp_state *state) -{ - struct arguments *arguments = state->input; - - if (arguments->cmd == DEFAULT) { - argp_usage(state); - return ARGP_ERR_UNKNOWN; - } - - switch (arguments->cmd) { - case PATCH: - case UNPATCH: - case INFO: - if (!arguments->pid || arguments->upatch == NULL || - arguments->binary == NULL || arguments->uuid == NULL) { - argp_usage(state); - return ARGP_ERR_UNKNOWN; - } - default: - break; - } - - return 0; -} - -static error_t parse_opt(int key, char *arg, struct argp_state *state) -{ - struct arguments *arguments = state->input; - - switch (key) { - case 'v': - arguments->verbose = true; - break; - case 'p': - arguments->pid = atoi(arg); - break; - case 'u': - arguments->upatch = arg; - break; - case 'b': - arguments->binary = arg; - break; - case 'U': - arguments->uuid = arg; - break; - case ARGP_KEY_ARG: - if (state->arg_num >= 1) { - argp_usage(state); - } - if (arguments->cmd != DEFAULT) { - argp_usage(state); - } - for (int i = 1; i < COMMAND_SIZE; ++i) { - if (!strcmp(arg, command[i])) { - arguments->cmd = i; - break; - } - } - break; - case ARGP_KEY_END: - return check_opt(state); - default: - return ARGP_ERR_UNKNOWN; - } - return 0; -} - -static struct argp argp = { - options, parse_opt, args_doc, program_doc, NULL, NULL, NULL -}; - -int patch_upatch(const char *uuid, const char *binary_path, - const char *upatch_path, int pid) -{ - struct upatch_elf uelf; - struct running_elf relf; - memset(&uelf, 0, sizeof(struct upatch_elf)); - memset(&relf, 0, sizeof(struct running_elf)); - - int ret = upatch_init(&uelf, upatch_path); - if (ret) { - log_error("Failed to initialize patch, pid=%d, ret=%d\n", pid, ret); - goto out; - } - - ret = process_patch(pid, &uelf, &relf, uuid, binary_path); - if (ret) { - log_error("Failed to patch process, pid=%d, ret=%d\n", pid, ret); - goto out; - } - -out: - upatch_close(&uelf); - binary_close(&relf); - - return ret; -} - -int unpatch_upatch(const char *uuid, int pid) -{ - int ret = 0; - - ret = process_unpatch(pid, uuid); - if (ret) { - log_error("Failed to unpatch process, pid=%d, ret=%d\n", pid, ret); - return ret; - } - - return 0; -} - -int info_upatch(int pid) -{ - int ret = process_info(pid); - if (ret != 0) { - log_error("Failed to get patch info, pid=%d, ret=%d\n", pid, ret); - return ret; - } - - return 0; -} - -int main(int argc, char *argv[]) -{ - int ret; - - struct arguments args; - - memset(&args, 0, sizeof(struct arguments)); - argp_parse(&argp, argc, argv, 0, NULL, &args); - if (args.verbose) { - g_loglevel = DEBUG; - } - - g_logprefix = basename(args.upatch); - log_debug("PID: %d\n", args.pid); - log_debug("UUID: %s\n", args.uuid); - log_debug("Patch: %s\n", args.upatch); - log_debug("Binary: %s\n", args.binary); - - args.pid = args.pid & INT32_MAX; - switch (args.cmd) { - case PATCH: - ret = patch_upatch(args.uuid, args.binary, args.upatch, args.pid); - break; - case UNPATCH: - ret = unpatch_upatch(args.uuid, args.pid); - break; - case INFO: - ret = info_upatch(args.pid); - break; - default: - ERROR("Unknown command"); - ret = EINVAL; - break; - } - - return abs(ret); -} diff --git a/upatch-manage/upatch-patch.c b/upatch-manage/upatch-patch.c deleted file mode 100644 index fd2c5f22..00000000 --- a/upatch-manage/upatch-patch.c +++ /dev/null @@ -1,1039 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include - -#include -#include - -#include "log.h" -#include "upatch-common.h" -#include "upatch-patch.h" -#include "upatch-process.h" -#include "upatch-ptrace.h" -#include "upatch-relocation.h" -#include "upatch-resolve.h" -#include "upatch-stack-check.h" - -#ifndef ARCH_SHF_SMALL -#define ARCH_SHF_SMALL 0 -#endif -#ifndef SHF_RO_AFTER_INIT -#define SHF_RO_AFTER_INIT 0x00200000 -#endif - -/* If this is set, the section belongs in the init part of the module */ -#define BITS_PER_LONG sizeof(unsigned long) * 8 - -static GElf_Off calculate_load_address(struct running_elf *relf, - bool check_code) -{ - GElf_Off min_addr = (unsigned long)-1; - - /* TODO: for ET_DYN, consider check PIE */ - if (relf->info.hdr->e_type != ET_EXEC && - relf->info.hdr->e_type != ET_DYN) { - log_error("invalid elf type, it should be ET_EXEC or ET_DYN\n"); - goto out; - } - - for (int i = 0; i < relf->info.hdr->e_phnum; ++i) { - if (relf->phdrs[i].p_type != PT_LOAD) { - continue; - } - if (!check_code || - (check_code && (relf->phdrs[i].p_flags & PF_X))) { - min_addr = (min_addr > relf->phdrs[i].p_vaddr) ? - relf->phdrs[i].p_vaddr : min_addr; - } - } - -out: - return min_addr; -} - -static unsigned long calculate_mem_load(struct object_file *obj) -{ - struct obj_vm_area *ovma; - unsigned long load_addr = (unsigned long)-1; - - list_for_each_entry(ovma, &obj->vma, list) { - if (ovma->inmem.prot & PROT_EXEC) { - load_addr = (load_addr > ovma->inmem.start) ? - ovma->inmem.start : load_addr; - } - } - - return load_addr; -} - -static int rewrite_section_headers(struct upatch_elf *uelf) -{ - unsigned int i; - /* Handle SHF_ALLOC in this part */ - - /* This should always be true, but let's be sure. */ - uelf->info.shdrs[0].sh_addr = 0; - uelf->info.shdrs[0].sh_addralign = 0; - - for (i = 1; i < uelf->info.hdr->e_shnum; i++) { - GElf_Shdr *shdr = &uelf->info.shdrs[i]; - if (shdr->sh_type != SHT_NOBITS && - uelf->info.patch_size < shdr->sh_offset + shdr->sh_size) { - log_error("upatch len %lu truncated\n", uelf->info.patch_size); - return -ENOEXEC; - } - - /* Mark all sections sh_addr with their address in the - temporary image. */ - shdr->sh_addr = (size_t)uelf->info.hdr + shdr->sh_offset; - log_debug("section %s at 0x%lx\n", - uelf->info.shstrtab + shdr->sh_name, shdr->sh_addr); - } - - return 0; -} - -static unsigned long get_offset(unsigned long *size, GElf_Shdr *sechdr) -{ - unsigned long ret; - - ret = ALIGN(*size, (unsigned long)(sechdr->sh_addralign ?: 1)); - *size = (unsigned long)ret + sechdr->sh_size; - - return ret; -} - -static void layout_upatch_info(struct upatch_elf *uelf) -{ - GElf_Shdr *upatch_func = uelf->info.shdrs + uelf->index.upatch_funcs; - unsigned long num = upatch_func->sh_size / sizeof(struct upatch_patch_func); - GElf_Shdr *upatch_string = uelf->info.shdrs + uelf->index.upatch_string; - - uelf->core_layout.info_size = uelf->core_layout.size; - uelf->core_layout.size += sizeof(struct upatch_info) + - num * sizeof(struct upatch_info_func) + upatch_string->sh_size; - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); -} - -static void layout_jmptable(struct upatch_elf *uelf) -{ - uelf->jmp_cur_entry = 0; - uelf->jmp_max_entry = JMP_TABLE_MAX_ENTRY; - uelf->jmp_offs = ALIGN(uelf->core_layout.size, sizeof(unsigned long)); - uelf->core_layout.size = uelf->jmp_offs + - uelf->jmp_max_entry * get_jmp_table_entry(); - uelf->core_layout.text_size = uelf->core_layout.size; -} - -static void layout_sections(struct upatch_elf *uelf) -{ - static unsigned long const masks[][2] = { - /* NOTE: all executable code must be the last section - * in this array; otherwise modify the text_size - * finder in the two loops below */ - { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL }, - { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL }, - { SHF_RO_AFTER_INIT | SHF_ALLOC, ARCH_SHF_SMALL }, - { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL }, - { ARCH_SHF_SMALL | SHF_ALLOC, 0 } - }; - unsigned int m; - unsigned int i; - - for (i = 0; i < uelf->info.hdr->e_shnum; i++) { - uelf->info.shdrs[i].sh_entsize = ~0UL; - } - - log_debug("upatch section allocation order:\n"); - for (m = 0; m < ARRAY_SIZE(masks); ++m) { - for (i = 0; i < uelf->info.hdr->e_shnum; ++i) { - GElf_Shdr *s = &uelf->info.shdrs[i]; - const char *sname = uelf->info.shstrtab + s->sh_name; - - if ((s->sh_flags & masks[m][0]) != masks[m][0] || - (s->sh_flags & masks[m][1]) || s->sh_entsize != ~0UL) { - continue; - } - - s->sh_entsize = get_offset(&uelf->core_layout.size, s); - log_debug("\tm = %d; %s: sh_entsize: 0x%lx\n", m, sname, - s->sh_entsize); - } - switch (m) { - case 0: /* executable */ - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); - uelf->core_layout.text_size = uelf->core_layout.size; - break; - case 1: /* RO: text and ro-data */ - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); - uelf->core_layout.ro_size = uelf->core_layout.size; - break; - case 2: /* RO after init */ - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); - uelf->core_layout.ro_after_init_size = - uelf->core_layout.size; - break; - case 3: /* whole core */ - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); - break; - default: - break; - } - } -} - -/* TODO: only included used symbol */ -static bool is_upatch_symbol(void) -{ - return true; -} - -/* - * We only allocate and copy the strings needed by the parts of symtab - * we keep. This is simple, but has the effect of making multiple - * copies of duplicates. We could be more sophisticated, see - * linux-kernel thread starting with - * <73defb5e4bca04a6431392cc341112b1@localhost>. - */ -static void layout_symtab(struct upatch_elf *uelf) -{ - GElf_Shdr *symsect = uelf->info.shdrs + uelf->index.sym; - GElf_Shdr *strsect = uelf->info.shdrs + uelf->index.str; - /* TODO: only support same arch as kernel now */ - const GElf_Sym *src; - unsigned long i; - unsigned long nsrc; - unsigned long ndst; - unsigned long strtab_size = 0; - - /* Put symbol section at end of init part of module. */ - symsect->sh_flags |= SHF_ALLOC; - symsect->sh_entsize = get_offset(&uelf->core_layout.size, symsect); - log_debug("\t%s\n", uelf->info.shstrtab + symsect->sh_name); - - src = (void *)uelf->info.hdr + symsect->sh_offset; - nsrc = symsect->sh_size / sizeof(*src); - - /* Compute total space required for the symbols' strtab. */ - for (ndst = i = 0; i < nsrc; i++) { - if (i == 0 || is_upatch_symbol()) { - strtab_size += strlen(&uelf->strtab[src[i].st_name]) + 1; - ndst++; - } - } - - /* Append room for core symbols at end of core part. */ - uelf->symoffs = ALIGN(uelf->core_layout.size, symsect->sh_addralign ?: 1); - uelf->stroffs = uelf->core_layout.size = - uelf->symoffs + ndst * sizeof(GElf_Sym); - uelf->core_layout.size += strtab_size; - uelf->core_typeoffs = uelf->core_layout.size; - uelf->core_layout.size += ndst * sizeof(char); - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); - - /* Put string table section at end of init part of module. */ - strsect->sh_flags |= SHF_ALLOC; - strsect->sh_entsize = get_offset(&uelf->core_layout.size, strsect); - uelf->core_layout.size = PAGE_ALIGN(uelf->core_layout.size); - log_debug("\t%s\n", uelf->info.shstrtab + strsect->sh_name); -} - -static void *upatch_alloc(struct object_file *obj, size_t len) -{ - struct upatch_ptrace_ctx *pctx = proc2pctx(obj->proc); - if (pctx == NULL) { - log_error("Failed to find process context\n"); - return NULL; - } - - log_debug("Finding patch region for '%s', len=0x%lx\n", obj->name, len); - struct vm_hole *hole = find_patch_region(obj, len); - if (hole == NULL) { - log_error("Failed to find patch region for '%s'\n", obj->name); - return NULL; - } - - uintptr_t addr = PAGE_ALIGN(hole->start); - log_debug("Found patch region at 0x%lx, size=0x%lx\n", addr, len); - - addr = upatch_mmap_remote(pctx, addr, len, - PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, - (unsigned long)-1, 0); - if (addr == 0) { - log_error("Failed to map patch region, ret=%d\n", errno); - return NULL; - } - - int ret = vm_hole_split(hole, addr, (addr + len)); - if (ret != 0) { - log_error("Failed to split vm hole, ret=%d\n", ret); - return NULL; - } - - return (void *)addr; -} - -static void upatch_free(struct object_file *obj, void *base, unsigned long size) -{ - log_debug("Free patch memory %p\n", base); - if (upatch_munmap_remote(proc2pctx(obj->proc), (unsigned long)base, size)) { - log_error("Failed to free patch memory %p\n", base); - } -} - -static int alloc_memory(struct upatch_elf *uelf, struct object_file *obj) -{ - struct upatch_layout *layout = &uelf->core_layout; - - layout->base = upatch_alloc(obj, layout->size); - if (layout->base == NULL) { - log_error("Failed to alloc patch memory\n"); - return ENOMEM; - } - - layout->kbase = calloc(1, layout->size); - if (!layout->kbase) { - log_error("Failed to alloc memory\n"); - upatch_free(obj, layout->base, layout->size); - return ENOMEM; - } - - /* Transfer each section which specifies SHF_ALLOC */ - log_debug("Final section addresses:\n"); - for (int i = 0; i < uelf->info.hdr->e_shnum; i++) { - GElf_Shdr *shdr = &uelf->info.shdrs[i]; - - if (!(shdr->sh_flags & SHF_ALLOC)) { - continue; - } - - void *dest = layout->base + shdr->sh_entsize; - void *kdest = layout->kbase + shdr->sh_entsize; - if (shdr->sh_type != SHT_NOBITS) { - memcpy(kdest, (void *)shdr->sh_addr, shdr->sh_size); - } - - shdr->sh_addr = (uintptr_t)kdest; - /* overuse this attr to record user address */ - shdr->sh_addralign = (uintptr_t)dest; - log_debug("\t0x%lx %s <- 0x%lx\n", (uintptr_t)kdest, - uelf->info.shstrtab + shdr->sh_name, (uintptr_t)dest); - } - - return 0; -} - -static int post_memory(struct upatch_elf *uelf, struct object_file *obj) -{ - log_debug("Post memory 0x%lx to 0x%lx, len=0x%lx\n", - (uintptr_t)uelf->core_layout.kbase, (uintptr_t)uelf->core_layout.base, - uelf->core_layout.size); - - int ret = upatch_process_mem_write(obj->proc, - uelf->core_layout.kbase, (unsigned long)uelf->core_layout.base, - uelf->core_layout.size); - if (ret) { - log_error("Failed to write process memory, ret=%d\n", ret); - } - - return ret; -} - -static int upatch_info_alloc(struct upatch_elf *uelf, struct upatch_info *uinfo) -{ - GElf_Shdr *upatch_funcs = &uelf->info.shdrs[uelf->index.upatch_funcs]; - size_t num = upatch_funcs->sh_size / sizeof(struct upatch_patch_func); - - uinfo->funcs = (void *)malloc(num * sizeof(*uinfo->funcs)); - if (uinfo->funcs == NULL) { - log_error("Failed to malloc uinfo->funcs\n"); - return -ENOMEM; - } - return 0; -} - -static void upatch_info_init(struct upatch_elf *uelf, struct upatch_info *uinfo) -{ - GElf_Shdr *ufuncs = &uelf->info.shdrs[uelf->index.upatch_funcs]; - GElf_Shdr *ustring = &uelf->info.shdrs[uelf->index.upatch_string]; - struct upatch_patch_func *funcs = (void *)uelf->info.hdr + - ufuncs->sh_offset; - char *names = (void *)uelf->info.hdr + ustring->sh_offset; - - uinfo->changed_func_num = ufuncs->sh_size / - sizeof(struct upatch_patch_func); - uinfo->func_names_size = ustring->sh_size; - uinfo->func_names = names; - - for (unsigned long i = 0; i < uinfo->changed_func_num; i++) { - uinfo->funcs[i].addr = funcs[i].addr; - uinfo->funcs[i].addr.old_addr += uelf->relf->load_bias; - uinfo->funcs[i].name = names; - names += strlen(names) + 1; - } -} - -static int upatch_active_stack_check(struct upatch_elf *uelf, - struct upatch_process *proc) -{ - struct upatch_info uinfo; - - int ret = upatch_info_alloc(uelf, &uinfo); - if (ret < 0) { - return ret; - } - - upatch_info_init(uelf, &uinfo); - ret = upatch_stack_check(&uinfo, proc, ACTIVE); - - free(uinfo.funcs); - return ret; -} - -static struct object_file *upatch_find_obj(struct upatch_elf *uelf, - struct upatch_process *proc) -{ - struct object_file *obj = NULL; - GElf_Off min_addr; - - list_for_each_entry(obj, &proc->objs, list) { - if (obj->inode == uelf->relf->info.inode) { - min_addr = calculate_load_address(uelf->relf, true); - uelf->relf->load_start = calculate_mem_load(obj); - uelf->relf->load_bias = uelf->relf->load_start - min_addr; - - log_debug("load_bias = %lx\n", uelf->relf->load_bias); - return obj; - } - } - - log_error("Cannot find inode %lu in pid %d, file is not loaded\n", - uelf->relf->info.inode, proc->pid); - return NULL; -} -static int complete_info(struct upatch_elf *uelf, struct object_file *obj, - const char *uuid) -{ - int ret = 0; - - struct upatch_info *uinfo = (void *)uelf->core_layout.kbase + - uelf->core_layout.info_size; - struct upatch_patch_func *upatch_funcs_addr = - (void *)uelf->info.shdrs[uelf->index.upatch_funcs].sh_addr; - GElf_Shdr *upatch_string = &uelf->info.shdrs[uelf->index.upatch_string]; - - memcpy(uinfo->magic, UPATCH_HEADER, strlen(UPATCH_HEADER)); - memcpy(uinfo->id, uuid, strlen(uuid)); - - uinfo->size = uelf->core_layout.size - uelf->core_layout.info_size; - uinfo->start = (unsigned long)uelf->core_layout.base; - uinfo->end = (unsigned long)uelf->core_layout.base + - uelf->core_layout.size; - uinfo->changed_func_num = - uelf->info.shdrs[uelf->index.upatch_funcs].sh_size / - sizeof(struct upatch_patch_func); - - uinfo->func_names = (void *)uinfo + sizeof(*uinfo); - uinfo->func_names_size = upatch_string->sh_size; - uinfo->funcs = (void *)uinfo->func_names + uinfo->func_names_size; - - memcpy(uinfo->func_names, (void *)upatch_string->sh_addr, - upatch_string->sh_size); - - unsigned long offset = 0; - for (unsigned long i = 0; i < uinfo->changed_func_num; ++i) { - char *name = (char *)uinfo->func_names + offset; - - uinfo->funcs[i].name = name; - offset += strlen(name) + 1; - } - - log_debug("Changed function:\n"); - for (unsigned int i = 0; i < uinfo->changed_func_num; ++i) { - struct upatch_info_func *upatch_func = &uinfo->funcs[i]; - - upatch_func->addr = upatch_funcs_addr[i].addr; - upatch_func->addr.old_addr += uelf->relf->load_bias; - -#ifdef __riscv -#define RISCV_MAX_JUMP_RANGE (1L << 31) - /* - * On RISC-V, to jump to arbitrary address, there must be - * at least 12 bytes to hold 3 instructors. Struct upatch_info - * new_insn field is only 8 bytes. We can only jump into - * +-2G ranges. Here do the check. - */ - long riscv_offset = (long)(upatch_func->addr.new_addr - upatch_func->addr.old_addr); - if (riscv_offset >= RISCV_MAX_JUMP_RANGE || riscv_offset < -RISCV_MAX_JUMP_RANGE) { - log_error("new_addr=%lx old_addr=%lx exceed +-2G range\n", - upatch_func->addr.new_addr, upatch_func->addr.old_addr); - goto out; - } -#endif - - ret = upatch_process_mem_read(obj->proc, upatch_func->addr.old_addr, - &upatch_func->old_insn, get_origin_insn_len()); - if (ret) { - log_error("can't read origin insn at 0x%lx - %d\n", - upatch_func->addr.old_addr, ret); - goto out; - } - -#ifdef __riscv - upatch_func->new_insn = get_new_insn( - upatch_func->addr.old_addr, upatch_func->addr.new_addr); -#else - upatch_func->new_insn = get_new_insn(); -#endif - log_debug("\taddr: 0x%lx -> 0x%lx, insn: 0x%lx -> 0x%lx, name: '%s'\n", - upatch_func->addr.old_addr, upatch_func->addr.new_addr, - upatch_func->old_insn[0], upatch_func->new_insn, - upatch_func->name); - } - -out: - return ret; -} - -static int unapply_patch(struct object_file *obj, - struct upatch_info_func *funcs, unsigned long changed_func_num) -{ - log_debug("Changed function:\n"); - for (unsigned int i = 0; i < changed_func_num; ++i) { - struct upatch_info_func *upatch_func = &funcs[i]; - - log_debug("\taddr: 0x%lx -> 0x%lx, insn: 0x%lx -> 0x%lx, name: '%s'\n", - upatch_func->addr.new_addr, upatch_func->addr.old_addr, - upatch_func->new_insn, upatch_func->old_insn[0], - upatch_func->name); - int ret = upatch_process_mem_write(obj->proc, &funcs[i].old_insn, - (unsigned long)funcs[i].addr.old_addr, get_origin_insn_len()); - if (ret) { - log_error("Failed to write old insn at 0x%lx, ret=%d\n", - funcs[i].addr.old_addr, ret); - return ret; - } - } - return 0; -} - -static int apply_patch(struct upatch_elf *uelf, struct object_file *obj) -{ - int ret = 0; - unsigned int i; - - struct upatch_info *uinfo = (void *)uelf->core_layout.kbase + - uelf->core_layout.info_size; - for (i = 0; i < uinfo->changed_func_num; ++i) { - struct upatch_info_func *upatch_func = &uinfo->funcs[i]; - - // write jumper insn to first 8 bytes - ret = upatch_process_mem_write(obj->proc, &upatch_func->new_insn, - (unsigned long)upatch_func->addr.old_addr, get_upatch_insn_len()); - if (ret) { - log_error( - "Failed to ptrace upatch func at 0x%lx(0x%lx) - %d\n", - upatch_func->addr.old_addr, upatch_func->new_insn, - ret); - goto out; - } - // write 64bit new addr to second 8 bytes - ret = upatch_process_mem_write(obj->proc, &upatch_func->addr.new_addr, - (unsigned long)upatch_func->addr.old_addr + get_upatch_insn_len(), - get_upatch_addr_len()); - if (ret) { - log_error( - "Failed to ptrace upatch func at 0x%lx(0x%lx) - %d\n", - upatch_func->addr.old_addr + get_upatch_insn_len(), - upatch_func->addr.new_addr, ret); - goto out; - } - } - -out: - if (ret) { - unapply_patch(obj, uinfo->funcs, uinfo->changed_func_num); - } - return ret; -} - -static int upatch_mprotect(struct upatch_elf *uelf, struct object_file *obj) -{ - int ret; - - if (uelf->core_layout.text_size > 0) { - ret = upatch_mprotect_remote( - proc2pctx(obj->proc), - (unsigned long)uelf->core_layout.base, - uelf->core_layout.text_size, PROT_READ | PROT_EXEC); - if (ret < 0) { - log_error("Failed to change upatch text protection to r-x"); - return ret; - } - } - - if (uelf->core_layout.ro_size > uelf->core_layout.text_size) { - ret = upatch_mprotect_remote( - proc2pctx(obj->proc), - (unsigned long)uelf->core_layout.base + uelf->core_layout.text_size, - uelf->core_layout.ro_size - uelf->core_layout.text_size, - PROT_READ); - if (ret < 0) { - log_error("Failed to change upatch ro protection to r--"); - return ret; - } - } - - if (uelf->core_layout.ro_after_init_size > uelf->core_layout.ro_size) { - ret = upatch_mprotect_remote( - proc2pctx(obj->proc), - (unsigned long)uelf->core_layout.base + uelf->core_layout.ro_size, - uelf->core_layout.ro_after_init_size - uelf->core_layout.ro_size, - PROT_READ); - if (ret < 0) { - log_error("Failed to change upatch ro init protection to r--"); - return ret; - } - } - - if (uelf->core_layout.info_size > - uelf->core_layout.ro_after_init_size) { - ret = upatch_mprotect_remote( - proc2pctx(obj->proc), - (unsigned long)uelf->core_layout.base + uelf->core_layout.ro_after_init_size, - uelf->core_layout.info_size - uelf->core_layout.ro_after_init_size, - PROT_READ | PROT_WRITE); - if (ret < 0) { - log_error("Failed to change upatch rw protection to rw-"); - return ret; - } - } - - if (uelf->core_layout.size > uelf->core_layout.info_size) { - ret = upatch_mprotect_remote( - proc2pctx(obj->proc), - (unsigned long)uelf->core_layout.base + uelf->core_layout.info_size, - uelf->core_layout.size - uelf->core_layout.info_size, - PROT_READ); - if (ret < 0) { - log_error("Failed to change upatch info protection to r--"); - return ret; - } - } - - return 0; -} - -static int upatch_apply_patches(struct object_file *obj, - struct upatch_elf *uelf, const char *uuid) -{ - int ret = 0; - - ret = rewrite_section_headers(uelf); - if (ret) { - return ret; - } - - // Caculate upatch mem size - layout_jmptable(uelf); - layout_sections(uelf); - layout_symtab(uelf); - layout_upatch_info(uelf); - - log_debug("calculate core layout = %lx\n", uelf->core_layout.size); - log_debug( - "Core layout: text_size = %lx, ro_size = %lx, ro_after_init_size = " - "%lx, info = %lx, size = %lx\n", - uelf->core_layout.text_size, uelf->core_layout.ro_size, - uelf->core_layout.ro_after_init_size, - uelf->core_layout.info_size, uelf->core_layout.size); - - /* - * Map patch as close to the original code as possible. - * Otherwise we can't use 32-bit jumps. - */ - ret = alloc_memory(uelf, obj); - if (ret) { - goto free; - } - - ret = upatch_mprotect(uelf, obj); - if (ret) { - goto free; - } - - /* Fix up syms, so that st_value is a pointer to location. */ - ret = simplify_symbols(uelf, obj); - if (ret) { - goto free; - } - - /* upatch new address will be updated */ - ret = apply_relocations(uelf); - if (ret) { - goto free; - } - - /* upatch upatch info */ - ret = complete_info(uelf, obj, uuid); - if (ret) { - goto free; - } - - ret = post_memory(uelf, obj); - if (ret) { - goto free; - } - - ret = apply_patch(uelf, obj); - if (ret) { - goto free; - } - - ret = 0; - goto out; - -// TODO: clear -free: - upatch_free(obj, uelf->core_layout.base, uelf->core_layout.size); -out: - return ret; -} - -static void upatch_time_tick(int pid) -{ - static struct timeval start_tv; - static struct timeval end_tv; - - if ((end_tv.tv_sec != 0) || (end_tv.tv_usec != 0)) { - memset(&start_tv, 0, sizeof(struct timeval)); - memset(&end_tv, 0, sizeof(struct timeval)); - } - - if ((start_tv.tv_sec == 0) && (start_tv.tv_usec == 0)) { - gettimeofday(&start_tv, NULL); - } else { - gettimeofday(&end_tv, NULL); - } - - if ((start_tv.tv_sec == 0) || (start_tv.tv_usec == 0) || - (end_tv.tv_sec == 0) || (end_tv.tv_usec == 0)) { - return; - } - - long frozen_time = get_microseconds(&start_tv, &end_tv); - log_debug("Process %d frozen time is %ld microsecond(s)\n", - pid, frozen_time); -} - -static struct object_patch *upatch_find_patch(struct upatch_process *proc, - const char *uuid) -{ - struct object_file *obj = NULL; - struct object_patch *patch = NULL; - - // Traverse all mapped memory and find all upatch memory - list_for_each_entry(obj, &proc->objs, list) { - if (!obj->is_patch) { - continue; - } - list_for_each_entry(patch, &obj->applied_patch, list) { - if (strncmp(patch->uinfo->id, uuid, UPATCH_ID_LEN) == 0) { - return patch; - } - } - } - return NULL; -} - -static int upatch_apply_prepare(struct upatch_elf *uelf, - struct upatch_process *proc, struct object_file **obj) -{ - int ret = 0; - - for (int i = 0; i < STACK_CHECK_RETRY_TIMES; i++) { - ret = upatch_process_attach(proc); - if (ret < 0) { - log_error("Failed to attach process\n"); - goto detach; - } - - *obj = upatch_find_obj(uelf, proc); - if (*obj == NULL) { - ret = -ENODATA; - goto detach; - } - - ret = upatch_active_stack_check(uelf, proc); - if (ret != -EBUSY) { - return ret; - } - upatch_process_detach(proc); - sleep(1); - } -detach: - upatch_process_detach(proc); - return ret; -} - -int process_patch(int pid, struct upatch_elf *uelf, struct running_elf *relf, - const char *uuid, const char *binary_path) -{ - struct upatch_process proc; - struct object_file *obj = NULL; - - // 查看process的信息,pid: maps, mem, cmdline, exe - int ret = upatch_process_init(&proc, pid); - if (ret < 0) { - log_error("Failed to init process\n"); - goto out; - } - - log_debug("Patch '%s' to ", uuid); - upatch_process_print_short(&proc); - - ret = upatch_process_mem_open(&proc, MEM_READ); - if (ret < 0) { - log_error("Failed to open process memory\n"); - goto out_free; - } - - // use uprobe to interpose function. the program has been executed to the - // entry point - - /* - * For each object file that we want to patch (either binary itself or - * shared library) we need its ELF structure to perform relocations. - * Because we know uniq BuildID of the object the section addresses - * stored in the patch are valid for the original object. - */ - // 解析process的mem-maps,获得各个块的内存映射以及phdr - ret = upatch_process_map_object_files(&proc); - if (ret < 0) { - log_error("Failed to read process memory mapping\n"); - goto out_free; - } - struct object_patch *patch = upatch_find_patch(&proc, uuid); - if (patch != NULL) { - log_error("Patch '%s' already exists\n", uuid); - goto out_free; - } - ret = binary_init(relf, binary_path); - if (ret) { - log_error("Failed to load binary\n"); - goto out_free; - } - - uelf->relf = relf; - upatch_time_tick(pid); - - ret = upatch_apply_prepare(uelf, &proc, &obj); - if (ret < 0) { - goto out_free; - } - // 应用 - ret = upatch_apply_patches(obj, uelf, uuid); - if (ret < 0) { - log_error("Failed to apply patch\n"); - goto out_free; - } - -out_free: - upatch_process_detach(&proc); - upatch_time_tick(pid); - upatch_process_destroy(&proc); -out: - return ret; -} - -static int upatch_unapply_patches(struct object_file *obj, - struct upatch_info *uinfo) -{ - int ret = 0; - - ret = unapply_patch(obj, uinfo->funcs, uinfo->changed_func_num); - if (ret) { - return ret; - } - - log_debug("munmap upatch layout core:\n"); - upatch_free(obj, (void *)uinfo->start, uinfo->end - uinfo->start); - return ret; -} - -static int upatch_unapply_prepare(struct upatch_process *proc, - const char *uuid, struct object_patch **patch) -{ - int ret = 0; - - for (int i = 0; i < STACK_CHECK_RETRY_TIMES; i++) { - ret = upatch_process_attach(proc); - if (ret < 0) { - log_error("Failed to attach process\n"); - goto detach; - } - *patch = upatch_find_patch(proc, uuid); - if (*patch == NULL) { - log_error("Patch '%s' is not found\n", uuid); - ret = -ENODATA; - goto detach; - } - ret = upatch_stack_check((*patch)->uinfo, proc, DEACTIVE); - if (ret != -EBUSY) { - return ret; - } - upatch_process_detach(proc); - sleep(1); - } -detach: - upatch_process_detach(proc); - return ret; -} - -int process_unpatch(int pid, const char *uuid) -{ - struct upatch_process proc; - struct object_patch *patch = NULL; - - // 查看process的信息,pid: maps, mem, cmdline, exe - int ret = upatch_process_init(&proc, pid); - if (ret < 0) { - log_error("Failed to init process\n"); - goto out; - } - - log_debug("Unpatch '%s' from ", uuid); - upatch_process_print_short(&proc); - - ret = upatch_process_mem_open(&proc, MEM_READ); - if (ret < 0) { - log_error("Failed to open process memory\n"); - goto out_free; - } - - // use uprobe to interpose function. the program has been executed to the - // entry point - - /* - * For each object file that we want to patch (either binary itself or - * shared library) we need its ELF structure to perform relocations. - * Because we know uniq BuildID of the object the section addresses - * stored in the patch are valid for the original object. - */ - // 解析process的mem-maps,获得各个块的内存映射以及phdr - ret = upatch_process_map_object_files(&proc); - if (ret < 0) { - log_error("Failed to read process memory mapping\n"); - goto out_free; - } - - upatch_time_tick(pid); - ret = upatch_unapply_prepare(&proc, uuid, &patch); - if (ret < 0) { - goto out_free; - } - // 应用 - ret = upatch_unapply_patches(patch->obj, patch->uinfo); - if (ret < 0) { - log_error("Failed to remove patch\n"); - goto out_free; - } - -out_free: - upatch_process_detach(&proc); - upatch_time_tick(pid); - upatch_process_destroy(&proc); - -out: - return ret; -} - -static int upatch_info(struct upatch_process *proc) -{ - struct object_file *obj = NULL; - struct object_patch *patch = NULL; - bool found = false; - - list_for_each_entry(obj, &proc->objs, list) { - if (obj->is_patch) { - found = true; - break; - } - } - - if (!found) { - return found; - } - - found = false; - list_for_each_entry(patch, &obj->applied_patch, list) { - found = true; - break; - } - - return found; -} - -int process_info(int pid) -{ - int ret; - struct upatch_process proc; - char *status = "error"; - - // TODO: check build id - // TODO: 栈解析 - // 查看process的信息,pid: maps, mem, cmdline, exe - ret = upatch_process_init(&proc, pid); - if (ret < 0) { - log_error("Failed to init process\n"); - goto out; - } - - ret = upatch_process_mem_open(&proc, MEM_READ); - if (ret < 0) { - log_error("Failed to open process memory\n"); - goto out_free; - } - - ret = upatch_process_map_object_files(&proc); - if (ret < 0) { - log_error("Failed to read process memory mapping\n"); - goto out_free; - } - - ret = upatch_info(&proc); - if (ret) { - status = "actived"; - } else { - status = "removed"; - } - ret = 0; - -out_free: - upatch_process_destroy(&proc); - -out: - log_debug("%s\n", status); - return ret; -} diff --git a/upatch-manage/upatch-process.c b/upatch-manage/upatch-process.c deleted file mode 100644 index 8e1882f8..00000000 --- a/upatch-manage/upatch-process.c +++ /dev/null @@ -1,918 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "list.h" -#include "log.h" -#include "upatch-common.h" -#include "upatch-elf.h" -#include "upatch-process.h" -#include "upatch-ptrace.h" - -static const int MAX_ATTACH_ATTEMPTS = 3; - -/* - * Locks process by opening /proc//maps - * This ensures that task_struct will not be - * deleted in the kernel while we are working with - * the process - */ -static int lock_process(int pid) -{ - int fd; - char path[128]; - - log_debug("Locking PID %d...", pid); - (void) snprintf(path, sizeof(path), "/proc/%d/maps", pid); - - fd = open(path, O_RDONLY); - if (fd < 0) { - log_error("Failed to open file '%s'\n", path); - return -1; - } - log_debug("OK\n"); - - return fd; -} - -static void unlock_process(int fdmaps) -{ - int errsv = errno; - close(fdmaps); - errno = errsv; -} - -// TODO: get addr_space -static int upatch_coroutines_init(struct upatch_process *proc) -{ - INIT_LIST_HEAD(&proc->coro.coros); - - return 0; -} - -static int process_get_comm(struct upatch_process *proc) -{ - char path[128]; - char realpath[PATH_MAX]; - char *bn; - char *c; - ssize_t ret; - - (void) snprintf(path, sizeof(path), "/proc/%d/exe", proc->pid); - log_debug("Reading from '%s'...", path); - - ret = readlink(path, realpath, sizeof(realpath)); - if (ret < 0) { - return -1; - } - - realpath[ret] = '\0'; - bn = basename(realpath); - strncpy(path, bn, sizeof(path) - 1); - if ((c = strstr(path, " (deleted)"))) { - *c = '\0'; - } - - proc->comm[sizeof(proc->comm) - 1] = '\0'; - memcpy(proc->comm, path, sizeof(proc->comm) - 1); - // TODO: the comm is ldxxx - log_debug("OK\n"); - - return 0; -} - -int upatch_process_init(struct upatch_process *proc, int pid) -{ - int fdmaps; - - fdmaps = lock_process(pid); - if (fdmaps < 0) { - goto out_err; - } - - memset(proc, 0, sizeof(*proc)); - - proc->pid = pid; - proc->fdmaps = fdmaps; - proc->memfd = -1; - - INIT_LIST_HEAD(&proc->ptrace.pctxs); - INIT_LIST_HEAD(&proc->objs); - INIT_LIST_HEAD(&proc->vma_holes); - proc->num_objs = 0; - - if (upatch_coroutines_init(proc)) { - goto out_unlock; - } - - if (process_get_comm(proc)) { - goto out_unlock; - } - - return 0; - -out_unlock: - unlock_process(fdmaps); -out_err: - return -1; -} - -static void upatch_object_memfree(struct object_file *obj) -{ - struct object_patch *opatch; - struct object_patch *opatch_safe; - struct obj_vm_area *ovma; - struct obj_vm_area *ovma_safe; - - if (obj->name) { - free(obj->name); - } - - list_for_each_entry_safe(opatch, opatch_safe, &obj->applied_patch, list) { - if (opatch->uinfo) { - if (opatch->uinfo->funcs) { - free(opatch->uinfo->funcs); - } - free(opatch->uinfo); - } - free(opatch); - } - - list_for_each_entry_safe(ovma, ovma_safe, &obj->vma, list) { - free(ovma); - } -} - -static void upatch_process_memfree(struct upatch_process *proc) -{ - struct upatch_ptrace_ctx *p; - struct upatch_ptrace_ctx *p_safe; - struct object_file *obj; - struct object_file *obj_safe; - struct vm_hole *hole; - struct vm_hole *hole_safe; - - list_for_each_entry_safe(p, p_safe, &proc->ptrace.pctxs, list) { - free(p); - } - - list_for_each_entry_safe(hole, hole_safe, &proc->vma_holes, list) { - free(hole); - } - - list_for_each_entry_safe(obj, obj_safe, &proc->objs, list) { - upatch_object_memfree(obj); - free(obj); - } -} - -void upatch_process_destroy(struct upatch_process *proc) -{ - unlock_process(proc->fdmaps); - upatch_process_memfree(proc); -} - -static void process_print_cmdline(struct upatch_process *proc) -{ - char buf[PATH_MAX]; - ssize_t i; - ssize_t rv; - - (void) snprintf(buf, PATH_MAX, "/proc/%d/cmdline", proc->pid); - - int fd = open(buf, O_RDONLY); - if (fd == -1) { - log_error("Failed to open file '%s'\n", buf); - return; - } - - while (1) { - rv = read(fd, buf, sizeof(buf)); - if (rv == -1) { - if (errno == EINTR) { - continue; - } - log_error("Failed to read cmdline\n"); - goto err_close; - } - - if (rv == 0) { - break; - } - - for (i = 0; i < rv; i++) { - if (isprint(buf[i])) { - log_debug("%c", buf[i]); - } else { - log_debug(" "); - } - } - } - -err_close: - close(fd); -} - -void upatch_process_print_short(struct upatch_process *proc) -{ - log_debug("process %d, cmdline: ", proc->pid); - process_print_cmdline(proc); - log_debug("\n"); -} - -int upatch_process_mem_open(struct upatch_process *proc, int mode) -{ - char path[PATH_MAX]; - - if (proc->memfd >= 0) { - close(proc->memfd); - } - - (void) snprintf(path, sizeof(path), "/proc/%d/mem", proc->pid); - proc->memfd = open(path, mode == MEM_WRITE ? O_RDWR : O_RDONLY); - if (proc->memfd < 0) { - log_error("Failed to open file '%s'\n", path); - return -1; - } - - return 0; -} - -static unsigned int perms2prot(const char *perms) -{ - unsigned int prot = 0; - - if (perms[0] == 'r') { - prot |= PROT_READ; - } - if (perms[1] == 'w') { - prot |= PROT_WRITE; - } - if (perms[2] == 'x') { - prot |= PROT_EXEC; - } - /* Ignore 'p'/'s' flag, we don't need it */ - return prot; -} - -static struct vm_hole *process_add_vm_hole(struct upatch_process *proc, - unsigned long start, unsigned long end) -{ - struct vm_hole *hole = malloc(sizeof(*hole)); - if (hole == NULL) { - return NULL; - } - - hole->start = start; - hole->end = end; - hole->len = end - start; - list_init(&hole->list); - - list_add(&hole->list, &proc->vma_holes); - return hole; -} - -static int process_get_object_type(struct upatch_process *proc, - struct vm_area *vma, char *name, unsigned char *buf, size_t bufsize) -{ - int ret; - int type = OBJECT_UNKNOWN; - - ret = upatch_process_mem_read(proc, vma->start, buf, bufsize); - if (ret < 0) { - return -1; - } - - if (vma->prot == PROT_READ && - !strncmp(name, "[anonymous]", strlen("[anonymous]")) && - !memcmp(buf, UPATCH_HEADER, UPATCH_HEADER_LEN)) { - type = OBJECT_UPATCH; - } else if (!memcmp(buf, ELFMAG, SELFMAG)) { - type = OBJECT_ELF; - } else { - type = OBJECT_UNKNOWN; - } - - return type; -} - -static int vm_area_same(struct vm_area *a, struct vm_area *b) -{ - return ((a->start == b->start) && - (a->end == b->end) && - (a->prot == b->prot)); -} - -static int object_add_vm_area(struct object_file *o, struct vm_area *vma, - struct vm_hole *hole) -{ - struct obj_vm_area *ovma; - - if (o->prev_hole == NULL) { - o->prev_hole = hole; - } - - list_for_each_entry(ovma, &o->vma, list) { - if (vm_area_same(vma, &ovma->inmem)) { - return 0; - } - } - - ovma = malloc(sizeof(*ovma)); - if (!ovma) { - return -1; - } - - memset(ovma, 0, sizeof(*ovma)); - ovma->inmem = *vma; - - list_add(&ovma->list, &o->vma); - return 0; -} - -static struct object_file *process_new_object(struct upatch_process *proc, - dev_t dev, ino_t inode, const char *name, - struct vm_area *vma, struct vm_hole *hole) -{ - struct object_file *o; - - log_debug("Creating object file '%s' for %lx:%lu...", name, dev, inode); - - o = malloc(sizeof(*o)); - if (!o) { - log_error("FAILED\n"); - return NULL; - } - memset(o, 0, sizeof(struct object_file)); - - INIT_LIST_HEAD(&o->list); - INIT_LIST_HEAD(&o->vma); - INIT_LIST_HEAD(&o->applied_patch); - o->num_applied_patch = 0; - o->proc = proc; - o->dev = dev; - o->inode = inode; - o->is_patch = 0; - - o->prev_hole = hole; - if (object_add_vm_area(o, vma, hole) < 0) { - log_error("Cannot add vm area for %s\n", name); - free(o); - return NULL; - } - - o->name = strdup(name); - o->is_elf = 0; - - list_add(&o->list, &proc->objs); - proc->num_objs++; - - log_debug("OK\n"); - return o; -} - -static void link_funcs_name(struct upatch_info *uinfo) -{ - unsigned long idx = 0; - - for (unsigned long i = 0; i < uinfo->changed_func_num; i++) { - char *name = (char *)uinfo->func_names + idx; - - uinfo->funcs[i].name = name; - idx += strlen(name) + 1; - } -} - -static void free_object_patch(struct object_patch *opatch) -{ - if (opatch == NULL) { - return; - } - - if (opatch->uinfo != NULL) { - if (opatch->uinfo->funcs != NULL) { - free(opatch->uinfo->funcs); - } - if (opatch->uinfo->func_names != NULL) { - free(opatch->uinfo->func_names); - } - free(opatch->uinfo); - } - - free(opatch); -} - -static int add_upatch_object(struct upatch_process *proc, struct object_file *o, - unsigned long src, unsigned char *header_buf) -{ - struct object_patch *opatch; - - opatch = malloc(sizeof(struct object_patch)); - if (opatch == NULL) { - log_error("malloc opatch failed\n"); - return -1; - } - - opatch->obj = o; - opatch->uinfo = malloc(sizeof(struct upatch_info)); - if (opatch->uinfo == NULL) { - log_error("malloc opatch->uinfo failed\n"); - free(opatch); - return -1; - } - - memcpy(opatch->uinfo->magic, header_buf, sizeof(struct upatch_info)); - - opatch->uinfo->func_names = malloc(opatch->uinfo->func_names_size); - if (opatch->uinfo->func_names == NULL) { - log_error("Failed to malloc funcs_names\n"); - free_object_patch(opatch); - return -ENOMEM; - } - - if (upatch_process_mem_read(proc, src, - opatch->uinfo->func_names, opatch->uinfo->func_names_size)) { - log_error("Cannot read patch func names at 0x%lx\n", src); - free_object_patch(opatch); - return -1; - } - - src += opatch->uinfo->func_names_size; - opatch->uinfo->funcs = malloc(opatch->uinfo->changed_func_num * - sizeof(struct upatch_info_func)); - if (upatch_process_mem_read(proc, src, opatch->uinfo->funcs, - opatch->uinfo->changed_func_num * sizeof(struct upatch_info_func))) { - log_error("can't read patch funcs at 0x%lx\n", src); - free_object_patch(opatch); - return -1; - } - - link_funcs_name(opatch->uinfo); - list_add(&opatch->list, &o->applied_patch); - o->num_applied_patch++; - o->is_patch = 1; - - return 0; -} -/** - * Returns: 0 if everything is ok, -1 on error. - */ -static int process_add_vma(struct upatch_process *proc, - dev_t dev, ino_t inode, char *name, - struct vm_area *vma, struct vm_hole *hole) -{ - int object_type; - unsigned char header_buf[1024]; - struct object_file *o; - - /* Event though process_get_object_type() return -1, - * we still need continue process. */ - object_type = process_get_object_type(proc, vma, name, - header_buf, sizeof(header_buf)); - if (object_type != OBJECT_UPATCH) { - /* Is not a upatch, look if this is a vm_area of an already - * enlisted object. - */ - list_for_each_entry_reverse(o, &proc->objs, list) { - if ((dev && inode && o->dev == dev && - o->inode == (ino_t)inode) || - (dev == 0 && !strcmp(o->name, name))) { - return object_add_vm_area(o, vma, hole); - } - } - } - - o = process_new_object(proc, dev, inode, name, vma, hole); - if (o == NULL) { - return -1; - } - - if (object_type == OBJECT_UPATCH) { - unsigned long src = vma->start + sizeof(struct upatch_info); - if (add_upatch_object(proc, o, src, header_buf) != 0) { - return -1; - } - } - - if (object_type == OBJECT_ELF) { - o->is_elf = 1; - } - - return 0; -} - -int upatch_process_map_object_files(struct upatch_process *proc) -{ - int ret = 0; - - /* - * 1. Create the list of all objects in the process - * 2. Check whether we have patch for any of them - * 3. If we have at least one patch, create files for all - * of the object (we might have references to them - * in the patch). - */ - int fd = dup(proc->fdmaps); - if (fd < 0) { - log_error("unable to dup fd %d", proc->fdmaps); - return -1; - } - - lseek(fd, 0, SEEK_SET); - FILE *file = fdopen(fd, "r"); - if (file == NULL) { - log_error("unable to fdopen %d", fd); - close(fd); - return -1; - } - - unsigned long hole_start = 0; - - char line[1024]; - while (fgets(line, sizeof(line), file) != NULL) { - struct vm_area vma; - unsigned long vma_start; - unsigned long vma_end; - unsigned long offset; - unsigned int maj; - unsigned int min; - unsigned int inode; - char perms[5]; - char name_buf[256]; - char *name = name_buf; - - ret = sscanf(line, "%lx-%lx %s %lx %x:%x %u %255s", - &vma_start, &vma_end, perms, &offset, - &maj, &min, &inode, name_buf); - if (ret == EOF) { - log_error("Failed to read maps: unexpected EOF"); - goto error; - } - if (ret != 8) { - name = "[anonymous]"; - } - - vma.start = vma_start; - vma.end = vma_end; - vma.offset = offset; - vma.prot = perms2prot(perms); - - /* Hole must be at least 2 pages for guardians */ - struct vm_hole *hole = NULL; - if ((hole_start != 0) && - (vma_start - hole_start > 2 * (uintptr_t)PAGE_SIZE)) { - uintptr_t start = hole_start + (uintptr_t)PAGE_SIZE; - uintptr_t end = vma_start - (uintptr_t)PAGE_SIZE; - - hole = process_add_vm_hole(proc, start, end); - if (hole == NULL) { - log_error("Failed to add vma hole"); - goto error; - } - log_debug("vm_hole: start=0x%lx, end=0x%lx, len=0x%lx\n", - hole->start, hole->end, hole->len); - } - hole_start = vma_end; - - name = name[0] == '/' ? basename(name) : name; - ret = process_add_vma(proc, makedev(maj, min), inode, name, &vma, hole); - if (ret < 0) { - log_error("Failed to add object vma"); - goto error; - } - - if ((proc->libc_base == 0) && - (vma.prot & PROT_EXEC) && - !strncmp(basename(name), "libc", 4)) { - proc->libc_base = vma_start; - } - } - - (void)fclose(file); - (void)close(fd); - log_debug("Found %d object file(s)\n", proc->num_objs); - - if (proc->libc_base == 0) { - log_error("Cannot find libc_base, pid=%d", - proc->pid); - return -1; - } - - return 0; - -error: - (void)fclose(file); - (void)close(fd); - return -1; -} - -static int process_list_threads(struct upatch_process *proc, int **ppids, - size_t *npids, size_t *alloc) -{ - DIR *dir = NULL; - struct dirent *de; - char path[PATH_MAX]; - int *pids = *ppids; - - (void) snprintf(path, sizeof(path), "/proc/%d/task", proc->pid); - dir = opendir(path); - if (!dir) { - log_error("Failed to open directory '%s'\n", path); - goto dealloc; - } - - *npids = 0; - while ((de = readdir(dir))) { - int *t; - if (de->d_name[0] == '.') { - continue; - } - if (*npids >= *alloc) { - *alloc = *alloc ? *alloc * 2 : 1; - t = realloc(pids, *alloc * sizeof(*pids)); - if (t == NULL) { - log_error("Failed to (re)allocate memory for pids\n"); - goto dealloc; - } - pids = t; - } - - pids[*npids] = atoi(de->d_name); - (*npids)++; - } - - closedir(dir); - *ppids = pids; - - return (int)*npids; - -dealloc: - if (dir) { - closedir(dir); - } - - free(pids); - *ppids = NULL; - *alloc = *npids = 0; - return -1; -} - -int upatch_process_attach(struct upatch_process *proc) -{ - int *pids = NULL; - int ret; - - size_t i; - size_t npids = 0; - size_t alloc = 0; - size_t prevnpids = 0; - size_t nattempts; - - if (upatch_process_mem_open(proc, MEM_WRITE) < 0) { - return -1; - } - - for (nattempts = 0; nattempts < MAX_ATTACH_ATTEMPTS; nattempts++) { - ret = process_list_threads(proc, &pids, &npids, &alloc); - if (ret == -1) { - goto detach; - } - - if (nattempts == 0) { - log_debug("Found %lu thread(s), attaching...\n", npids); - } else { - /* - * FIXME(pboldin): This is wrong, amount of threads can - * be the same because some new spawned and some old - * died - */ - if (prevnpids == npids) { - break; - } - log_debug("Found %lu new thread(s), attaching...\n", - prevnpids - npids); - } - - for (i = prevnpids; i < npids; i++) { - int pid = pids[i]; - - ret = upatch_ptrace_attach_thread(proc, pid); - if ((ret != 0) && (ret != ESRCH)) { - goto detach; - } - } - - prevnpids = npids; - } - - if (nattempts == MAX_ATTACH_ATTEMPTS) { - log_error("Unable to catch up with process, bailing\n"); - goto detach; - } - - log_debug("Attached to %lu thread(s): %d", npids, pids[0]); - for (i = 1; i < npids; i++) { - log_debug(", %d", pids[i]); - } - log_debug("\n"); - - free(pids); - return 0; - -detach: - upatch_process_detach(proc); - free(pids); - return -1; -} - -void upatch_process_detach(struct upatch_process *proc) -{ - struct upatch_ptrace_ctx *p; - struct upatch_ptrace_ctx *ptmp; - int status; - pid_t pid; - - if (proc->memfd >= 0 && close(proc->memfd) < 0) { - log_error("Failed to close memfd"); - } - proc->memfd = -1; - - list_for_each_entry_safe(p, ptmp, &proc->ptrace.pctxs, list) { - /** - * If upatch_ptrace_detach(p) return -ESRCH, there are two situations, - * as described below: - * 1. the specified thread does not exist, it means the thread dead - * during the attach processing, so we need to wait for the thread - * to exit; - * 2. the specified thread is not currently being traced by us, - * or is not stopped, so we just ignore it; - * - * We using the running variable of the struct upatch_ptrace_ctx to - * distinguish them: - * 1. if pctx->running = 0, it means the thread is traced by us, we - * will wait for the thread to exit; - * 2. if pctx->running = 1, it means we can not sure about the status of - * the thread, we just ignore it; - */ - if (upatch_ptrace_detach(p) == -ESRCH && !p->running) { - do { - pid = waitpid(p->pid, &status, __WALL); - } while (pid > 0 && !WIFEXITED(status)); - } - list_del(&p->list); - free(p); - } - log_debug("Process detached\n"); -} - -static inline struct vm_hole *next_hole(struct vm_hole *hole, - struct list_head *head) -{ - if (hole == NULL || hole->list.next == head) { - return NULL; - } - - return list_entry(hole->list.next, struct vm_hole, list); -} - -static inline struct vm_hole *prev_hole(struct vm_hole *hole, - struct list_head *head) -{ - if (hole == NULL || hole->list.prev == head) { - return NULL; - } - - return list_entry(hole->list.prev, struct vm_hole, list); -} - -int vm_hole_split(struct vm_hole *hole, uintptr_t start, uintptr_t end) -{ - uintptr_t new_start = ROUND_DOWN(start, (uintptr_t)PAGE_SIZE) - - (uintptr_t)PAGE_SIZE; - uintptr_t new_end = ROUND_UP(end, (uintptr_t)PAGE_SIZE) + - (uintptr_t)PAGE_SIZE; - - if (new_start > hole->start) { - struct vm_hole *left = NULL; - - left = malloc(sizeof(*hole)); - if (left == NULL) { - log_error("Failed to malloc for vm hole"); - return ENOMEM; - } - - left->start = hole->start; - left->end = new_start; - - list_add(&left->list, &hole->list); - } - - /* Reuse hole pointer as the right hole since it is pointed to by - * the `prev_hole` of some `object_file`. */ - hole->start = new_end; - hole->end = hole->end > new_end ? hole->end : new_end; - - return 0; -} - -static bool is_vm_hole_suitable(struct obj_vm_area *vma, - struct vm_hole *hole, size_t len) -{ - uintptr_t vma_start = vma->inmem.start; - uintptr_t vma_end = vma->inmem.end; - uintptr_t hole_start = PAGE_ALIGN(hole->start); - uintptr_t hole_end = PAGE_ALIGN(hole->start + len); - - log_debug("vma_start=0x%lx, vma_end=0x%lx, " - "hole_start=0x%lx, hole_end=0x%lx, hole_len=0x%lx\n", - vma_start, vma_end, hole->start, hole->end, hole->len); - if (hole->len < len) { - return false; - } - - if (hole_end < vma_start) { - // hole is on the left side of the vma - if ((vma_start - hole_start) <= MAX_DISTANCE) { - return true; - } - } else if (hole_start > vma_end) { - // hole is on the right side of the vma - if ((hole_end - vma_end) <= MAX_DISTANCE) { - return true; - } - } - - return false; -} -/* - * Take object's `prev_hole` as a left candidate - * and the next hole as a right candidate. Pace through them until there is - * enough space in the hole for the patch. - * - * Due to relocation constraints, the hole position should be whin 4GB range - * from the obj. - * eg: R_AARCH64_ADR_GOT_PAGE - */ -struct vm_hole *find_patch_region(struct object_file *obj, size_t len) -{ - struct list_head *vma_holes = &obj->proc->vma_holes; - - struct obj_vm_area *vma = NULL; - list_for_each_entry(vma, &obj->vma, list) { - struct vm_hole *left_hole = obj->prev_hole; - struct vm_hole *right_hole = NULL; - if (left_hole) { - right_hole = next_hole(left_hole, vma_holes); - } else { - if (!list_empty(vma_holes)) { - right_hole = list_first_entry(vma_holes, struct vm_hole, list); - } - } - - while ((left_hole != NULL) || (right_hole != NULL)) { - if (left_hole != NULL) { - if (is_vm_hole_suitable(vma, left_hole, len)) { - return left_hole; - } - left_hole = prev_hole(left_hole, vma_holes); - } - if (right_hole != NULL) { - if (is_vm_hole_suitable(vma, right_hole, len)) { - return right_hole; - } - right_hole = next_hole(right_hole, vma_holes); - } - } - } - - return NULL; -} diff --git a/upatch-manage/upatch-process.h b/upatch-manage/upatch-process.h deleted file mode 100644 index e8d5dee0..00000000 --- a/upatch-manage/upatch-process.h +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef __UPATCH_PROCESS__ -#define __UPATCH_PROCESS__ - -#include - -#include "list.h" -#include "upatch-patch.h" - -#define OBJECT_UNKNOWN 0 -#define OBJECT_ELF 1 -#define OBJECT_UPATCH 2 - -#define ELFMAG "\177ELF" -#define SELFMAG 4 - -#ifndef MAX_DISTANCE -#define MAX_DISTANCE (1UL << 32) -#endif - -enum { - MEM_READ, - MEM_WRITE, -}; - -struct object_file { - struct list_head list; - struct upatch_process *proc; - - /* Device the object resides on */ - dev_t dev; - ino_t inode; - - /* Object name (as seen in /proc//maps) */ - char *name; - - /* List of object's VM areas */ - struct list_head vma; - - /* Pointer to the previous hole in the patient's mapping */ - struct vm_hole *prev_hole; - - /* Pointer to the applied patch list, if any */ - struct list_head applied_patch; - /* The number of applied patch */ - size_t num_applied_patch; - - /* Is that a patch for some object? */ - unsigned int is_patch; - - /* Is it an ELF or a mmap'ed regular file? */ - unsigned int is_elf; -}; - -struct vm_area { - unsigned long start; - unsigned long end; - unsigned long offset; - unsigned int prot; -}; - -struct vm_hole { - unsigned long start; - unsigned long end; - unsigned long len; - struct list_head list; -}; - -struct obj_vm_area { - struct list_head list; - struct vm_area inmem; -}; - -struct object_patch { - struct list_head list; - struct upatch_info *uinfo; - struct object_file *obj; -}; - -struct upatch_process { - /* Pid of target process */ - int pid; - - /* memory fd of /proc//mem */ - int memfd; - - /* /proc//maps FD, also works as lock */ - int fdmaps; - - /* Process name */ - char comm[16]; - - /* List of process objects */ - struct list_head objs; - int num_objs; - - /* List ptrace contexts (one per each thread) */ - struct { - struct list_head pctxs; - } ptrace; - - struct { - struct list_head coros; - } coro; - - /* List of free VMA areas */ - struct list_head vma_holes; - - // TODO: other base? - /* libc's base address to use as a worksheet */ - unsigned long libc_base; -}; - -int upatch_process_init(struct upatch_process *, int); - -void upatch_process_destroy(struct upatch_process *); - -void upatch_process_print_short(struct upatch_process *); - -int upatch_process_mem_open(struct upatch_process *, int); - -int upatch_process_map_object_files(struct upatch_process *); - -int upatch_process_attach(struct upatch_process *); - -void upatch_process_detach(struct upatch_process *proc); - -int vm_hole_split(struct vm_hole *, uintptr_t, uintptr_t); - -struct vm_hole *find_patch_region(struct object_file *obj, size_t len); - -#endif diff --git a/upatch-manage/upatch-ptrace.c b/upatch-manage/upatch-ptrace.c deleted file mode 100644 index 4ebe0380..00000000 --- a/upatch-manage/upatch-ptrace.c +++ /dev/null @@ -1,298 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include -#include -#include -#include - -#include -#ifdef __riscv -/* user_regs_struct defined here */ -#include -#endif -#include -#include - -#include "upatch-common.h" -#include "upatch-ptrace.h" - -/* process's memory access */ -int upatch_process_mem_read(struct upatch_process *proc, unsigned long src, - void *dst, size_t size) -{ - ssize_t r = pread(proc->memfd, dst, size, (off_t)src); - - return r != (ssize_t)size ? -1 : 0; -} - -static int upatch_process_mem_write_ptrace(struct upatch_process *proc, - const void *src, unsigned long dst, size_t size) -{ - long ret; - - while (ROUND_DOWN(size, sizeof(long)) != 0) { - ret = ptrace(PTRACE_POKEDATA, proc->pid, dst, *(const unsigned long *)src); - if (ret) { - return -1; - } - dst += sizeof(long); - src += sizeof(long); - size -= sizeof(long); - } - - if (size) { - long tmp; - - tmp = ptrace(PTRACE_PEEKDATA, proc->pid, dst, NULL); - if (tmp == -1 && errno) { - return -1; - } - memcpy(&tmp, src, size); - - ret = ptrace(PTRACE_POKEDATA, proc->pid, dst, tmp); - if (ret) { - return -1; - } - } - - return 0; -} - -int upatch_process_mem_write(struct upatch_process *proc, const void *src, - unsigned long dst, size_t size) -{ - static int use_pwrite = 1; - ssize_t w; - - if (use_pwrite) { - w = pwrite(proc->memfd, src, size, (off_t)dst); - } - if (!use_pwrite || (w == -1 && errno == EINVAL)) { - use_pwrite = 0; - return upatch_process_mem_write_ptrace(proc, src, dst, size); - } - - return w != (ssize_t)size ? -1 : 0; -} - -static struct upatch_ptrace_ctx* upatch_ptrace_ctx_alloc( - struct upatch_process *proc) -{ - struct upatch_ptrace_ctx *p; - - p = malloc(sizeof(*p)); - if (!p) { - return NULL; - } - - memset(p, 0, sizeof(*p)); - - p->execute_until = 0UL; - p->running = 1; - p->proc = proc; - - INIT_LIST_HEAD(&p->list); - list_add(&p->list, &proc->ptrace.pctxs); - - return p; -} - -int upatch_ptrace_attach_thread(struct upatch_process *proc, int tid) -{ - struct upatch_ptrace_ctx *pctx = upatch_ptrace_ctx_alloc(proc); - if (pctx == NULL) { - log_error("Failed to alloc ptrace context"); - return ENOMEM; - } - - pctx->pid = tid; - log_debug("Attaching to %d...", tid); - - long ret = ptrace(PTRACE_ATTACH, tid, NULL, NULL); - if (ret < 0) { - log_error("Failed to attach thread, pid=%d, ret=%ld\n", tid, ret); - return errno; - } - - do { - int status = 0; - - ret = waitpid(tid, &status, __WALL); - if (ret < 0) { - log_error("Failed to wait thread, tid=%d, ret=%ld\n", tid, ret); - return errno; - } - - /* We are expecting SIGSTOP */ - if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) { - break; - } - - /* If we got SIGTRAP because we just got out of execve, wait - * for the SIGSTOP - */ - if (WIFSTOPPED(status)) { - status = (WSTOPSIG(status) == SIGTRAP) ? 0 : WSTOPSIG(status); - } else if (WIFSIGNALED(status)) { - /* Resend signal */ - status = WTERMSIG(status); - } - - ret = ptrace(PTRACE_CONT, tid, NULL, (void *)(uintptr_t)status); - if (ret < 0) { - log_error("Failed to continue thread, tid=%d, ret=%ld\n", tid, ret); - return errno; - } - } while (1); - - pctx->running = 0; - - log_debug("OK\n"); - return 0; -} - -int wait_for_stop(struct upatch_ptrace_ctx *pctx, const void *data) -{ - long ret; - - int status = 0; - int pid = (int)(uintptr_t)data ?: pctx->pid; - log_debug("wait_for_stop(pctx->pid=%d, pid=%d)\n", pctx->pid, pid); - - while (1) { - ret = ptrace(PTRACE_CONT, pctx->pid, NULL, (void *)(uintptr_t)status); - if (ret < 0) { - log_error("Cannot start tracee %d, ret=%ld\n", pctx->pid, ret); - return -1; - } - - ret = waitpid(pid, &status, __WALL); - if (ret < 0) { - log_error("Cannot wait tracee %d, ret=%ld\n", pid, ret); - return -1; - } - - if (WIFSTOPPED(status)) { - if (WSTOPSIG(status) == SIGSTOP || WSTOPSIG(status) == SIGTRAP) { - break; - } - status = WSTOPSIG(status); - continue; - } - - status = WIFSIGNALED(status) ? WTERMSIG(status) : 0; - } - - return 0; -} - -int upatch_ptrace_detach(struct upatch_ptrace_ctx *pctx) -{ - if (!pctx->pid) { - return 0; - } - - log_debug("Detaching from %d...", pctx->pid); - long ret = ptrace(PTRACE_DETACH, pctx->pid, NULL, NULL); - if (ret < 0) { - log_error("Failed to detach from process, pid=%d, ret=%ld\n", pctx->pid, ret); - return -errno; - } - log_debug("OK\n"); - - pctx->running = 1; - pctx->pid = 0; - return 0; -} - -long upatch_execute_remote(struct upatch_ptrace_ctx *pctx, - const unsigned char *code, size_t codelen, - struct user_regs_struct *pregs) -{ - return upatch_arch_execute_remote_func(pctx, code, codelen, pregs, - wait_for_stop, NULL); -} - -unsigned long upatch_mmap_remote(struct upatch_ptrace_ctx *pctx, - unsigned long addr, size_t length, unsigned long prot, - unsigned long flags, unsigned long fd, unsigned long offset) -{ - long ret; - unsigned long res = 0; - - log_debug("mmap_remote: 0x%lx+%lx, %lx, %lx, %lu, %lx\n", addr, length, - prot, flags, fd, offset); - ret = upatch_arch_syscall_remote(pctx, __NR_mmap, - (unsigned long)addr, length, prot, flags, fd, offset, &res); - if (ret < 0) { - return 0; - } - - if (ret == 0 && res >= (unsigned long)-MAX_ERRNO) { - errno = -(int)res; - return 0; - } - - return res; -} - -int upatch_mprotect_remote(struct upatch_ptrace_ctx *pctx, unsigned long addr, - size_t length, unsigned long prot) -{ - long ret; - unsigned long res; - - log_debug("mprotect_remote: 0x%lx+%lx\n", addr, length); - ret = upatch_arch_syscall_remote(pctx, __NR_mprotect, - (unsigned long)addr, length, prot, 0, 0, 0, &res); - if (ret < 0) { - return -1; - } - - if (ret == 0 && res >= (unsigned long)-MAX_ERRNO) { - errno = -(int)res; - return -1; - } - - return 0; -} - -int upatch_munmap_remote(struct upatch_ptrace_ctx *pctx, unsigned long addr, - size_t length) -{ - long ret; - unsigned long res; - - log_debug("munmap_remote: 0x%lx+%lx\n", addr, length); - ret = upatch_arch_syscall_remote(pctx, __NR_munmap, - (unsigned long)addr, length, 0, 0, 0, 0, &res); - if (ret < 0) { - return -1; - } - - if (ret == 0 && res >= (unsigned long)-MAX_ERRNO) { - errno = -(int)res; - return -1; - } - - return 0; -} diff --git a/upatch-manage/upatch-ptrace.h b/upatch-manage/upatch-ptrace.h deleted file mode 100644 index f2503cfd..00000000 --- a/upatch-manage/upatch-ptrace.h +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef __UPATCH_PTRACE__ -#define __UPATCH_PTRACE__ - -#include -#ifdef __riscv -#include -#endif - -#include "upatch-process.h" -#include "list.h" -#include "log.h" - -#define MAX_ERRNO 4095 - -struct upatch_ptrace_ctx { - int pid; - int running; - unsigned long execute_until; - struct upatch_process *proc; - struct list_head list; -}; - -#define proc2pctx(proc) \ - list_first_entry(&(proc)->ptrace.pctxs, struct upatch_ptrace_ctx, list) - -int upatch_process_mem_read(struct upatch_process *proc, unsigned long src, - void *dst, size_t size); - -int upatch_process_mem_write(struct upatch_process *, const void *, - unsigned long, size_t); - -int upatch_ptrace_attach_thread(struct upatch_process *, int); - -int upatch_ptrace_detach(struct upatch_ptrace_ctx *); - -int wait_for_stop(struct upatch_ptrace_ctx *, const void *); - -void copy_regs(struct user_regs_struct *, struct user_regs_struct *); - -long upatch_arch_execute_remote_func(struct upatch_ptrace_ctx *pctx, - const unsigned char *code, size_t codelen, - struct user_regs_struct *pregs, - int (*func)(struct upatch_ptrace_ctx *pctx, const void *data), - const void *data); - -long upatch_arch_syscall_remote(struct upatch_ptrace_ctx *, int, unsigned long, - unsigned long, unsigned long, unsigned long, - unsigned long, unsigned long, unsigned long *); - -unsigned long upatch_mmap_remote(struct upatch_ptrace_ctx *pctx, - unsigned long addr, size_t length, unsigned long prot, - unsigned long flags, unsigned long fd, unsigned long offset); - -int upatch_mprotect_remote(struct upatch_ptrace_ctx *pctx, unsigned long addr, - size_t length, unsigned long prot); - -int upatch_munmap_remote(struct upatch_ptrace_ctx *, unsigned long, size_t); - -long upatch_execute_remote(struct upatch_ptrace_ctx *, - const unsigned char *, size_t, struct user_regs_struct *); - -size_t get_origin_insn_len(void); -size_t get_upatch_insn_len(void); -size_t get_upatch_addr_len(void); - -#ifdef __riscv -unsigned long get_new_insn(unsigned long old_addr, unsigned long new_addr); -#else -unsigned long get_new_insn(void); -#endif - -#endif diff --git a/upatch-manage/upatch-resolve.c b/upatch-manage/upatch-resolve.c deleted file mode 100644 index 53debe60..00000000 --- a/upatch-manage/upatch-resolve.c +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * upatch-manage - * Copyright (C) 2024 Huawei Technologies Co., Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include -#include - -#include "log.h" -#include "upatch-common.h" -#include "upatch-elf.h" -#include "upatch-resolve.h" - -static unsigned long resolve_rela_dyn(struct upatch_elf *uelf, - struct object_file *obj, const char *name, GElf_Sym *patch_sym) -{ - unsigned long elf_addr = 0; - struct running_elf *relf = uelf->relf; - - if (!relf || !relf->index.dynsym || !relf->index.rela_dyn) { - return 0; - } - - GElf_Shdr *dynsym_shdr = &relf->info.shdrs[relf->index.dynsym]; - GElf_Shdr *rela_dyn_shdr = &relf->info.shdrs[relf->index.rela_dyn]; - - GElf_Sym *dynsym = (void *)relf->info.hdr + dynsym_shdr->sh_offset; - GElf_Rela *rela_dyn = (void *)relf->info.hdr + rela_dyn_shdr->sh_offset; - - for (Elf64_Xword i = 0; i < rela_dyn_shdr->sh_size / sizeof(GElf_Rela); i++) { - unsigned long sym_idx = GELF_R_SYM(rela_dyn[i].r_info); - - if (sym_idx == 0) { - /* - * some rela don't have the symbol index, use the symbol's value and - * rela's addend to find the symbol. for example, R_X86_64_IRELATIVE. - */ - if (rela_dyn[i].r_addend != (long)patch_sym->st_value) { - continue; - } - } else { - char *sym_name = relf->dynstrtab + dynsym[sym_idx].st_name; - char *sym_splitter = NULL; - - /* strip symbol version if exists */ - sym_splitter = strchr(sym_name, '@'); - if (sym_splitter != NULL) { - *sym_splitter = '\0'; - } - - /* function could also be part of the GOT with the type R_X86_64_GLOB_DAT */ - if (!streql(sym_name, name)) { - continue; - } - } - - /* r_offset is virtual address of GOT table */ - unsigned long sym_addr = relf->load_bias + rela_dyn[i].r_offset; - elf_addr = insert_got_table(uelf, obj, GELF_R_TYPE(rela_dyn[i].r_info), sym_addr); - - log_debug("resolved %s from .rela_dyn at 0x%lx\n", name, elf_addr); - break; - } - - return elf_addr; -} - -static unsigned long resolve_rela_plt(struct upatch_elf *uelf, - struct object_file *obj, const char *name, GElf_Sym *patch_sym) -{ - unsigned long elf_addr = 0; - struct running_elf *relf = uelf->relf; - - if (!relf || !relf->index.dynsym || !relf->index.rela_plt) { - return 0; - } - - GElf_Shdr *dynsym_shdr = &relf->info.shdrs[relf->index.dynsym]; - GElf_Shdr *rela_plt_shdr = &relf->info.shdrs[relf->index.rela_plt]; - - GElf_Sym *dynsym = (void *)relf->info.hdr + dynsym_shdr->sh_offset; - GElf_Rela *rela_plt = (void *)relf->info.hdr + rela_plt_shdr->sh_offset; - - for (Elf64_Xword i = 0; i < rela_plt_shdr->sh_size / sizeof(GElf_Rela); i++) { - unsigned long sym_idx = GELF_R_SYM(rela_plt[i].r_info); - unsigned long sym_type = GELF_ST_TYPE(dynsym[sym_idx].st_info); - if ((sym_type == STT_NOTYPE) && - (sym_type != STT_FUNC) && - (sym_type != STT_TLS)) { - continue; - } - - if (sym_idx == 0) { - /* - * some rela don't have the symbol index, use the symbol's value and - * rela's addend to find the symbol. for example, R_X86_64_IRELATIVE. - */ - if (rela_plt[i].r_addend != (long)patch_sym->st_value) { - continue; - } - } else { - char *sym_name = relf->dynstrtab + dynsym[sym_idx].st_name; - char *sym_splitter = NULL; - - /* strip symbol version if exists */ - sym_splitter = strchr(sym_name, '@'); - if (sym_splitter != NULL) { - *sym_splitter = '\0'; - } - - if (!streql(sym_name, name)) { - continue; - } - } - - /* r_offset is virtual address of PLT table */ - unsigned long sym_addr = relf->load_bias + rela_plt[i].r_offset; - elf_addr = insert_plt_table(uelf, obj, GELF_R_TYPE(rela_plt[i].r_info), sym_addr); - - log_debug("Resolved '%s' from '.rela_plt' at 0x%lx\n", name, elf_addr); - break; - } - - return elf_addr; -} - -static unsigned long resolve_dynsym(struct upatch_elf *uelf, - struct object_file *obj, const char *name) -{ - unsigned long elf_addr = 0; - struct running_elf *relf = uelf->relf; - - if (!relf || !relf->index.dynsym) { - return 0; - } - - GElf_Shdr *dynsym_shdr = &relf->info.shdrs[relf->index.dynsym]; - GElf_Sym *dynsym = (void *)relf->info.hdr + dynsym_shdr->sh_offset; - - for (Elf64_Xword i = 0; i < dynsym_shdr->sh_size / sizeof(GElf_Sym); i++) { - if (dynsym[i].st_value == 0) { - continue; - } - - char *sym_name = relf->dynstrtab + dynsym[i].st_name; - char *sym_splitter = strchr(sym_name, '@'); - if (sym_splitter != NULL) { - *sym_splitter = '\0'; - } - - /* function could also be part of the GOT with the type R_X86_64_GLOB_DAT */ - if (!streql(sym_name, name)) { - continue; - } - - unsigned long sym_addr = relf->load_bias + dynsym[i].st_value; - elf_addr = insert_got_table(uelf, obj, 0, sym_addr); - - log_debug("Resolved '%s' from '.dynsym' at 0x%lx\n", name, elf_addr); - break; - } - - return elf_addr; -} - -static unsigned long resolve_sym(struct upatch_elf *uelf, const char *name) -{ - unsigned long elf_addr = 0; - struct running_elf *relf = uelf->relf; - - if (!relf || !relf->index.sym) { - return 0; - } - - GElf_Shdr *sym_shdr = &relf->info.shdrs[relf->index.sym]; - GElf_Sym *sym = (void *)relf->info.hdr + sym_shdr->sh_offset; - - for (Elf64_Xword i = 0; i < sym_shdr->sh_size / sizeof(GElf_Sym); i++) { - if (sym[i].st_shndx == SHN_UNDEF) { - continue; - } - - /* strip symbol version if exists */ - char *sym_name = relf->strtab + sym[i].st_name; - char *sym_splitter = strchr(sym_name, '@'); - if (sym_splitter != NULL) { - *sym_splitter = '\0'; - } - - if (!streql(sym_name, name)) { - continue; - } - - elf_addr = relf->load_bias + sym[i].st_value; - - log_debug("Resolved '%s' from '.sym' at 0x%lx\n", name, elf_addr); - break; - } - - return elf_addr; -} - -static unsigned long resolve_patch_sym(struct upatch_elf *uelf, - const char *name, GElf_Sym *patch_sym) -{ - unsigned long elf_addr = 0; - struct running_elf *relf = uelf->relf; - - if (!relf) { - return 0; - } - - if (!patch_sym->st_value) { - return 0; - } - - elf_addr = relf->load_bias + patch_sym->st_value; - log_debug("Resolved '%s' from patch '.sym' at 0x%lx\n", name, elf_addr); - - return elf_addr; -} - -static unsigned long resolve_symbol(struct upatch_elf *uelf, - struct object_file *obj, const char *name, - GElf_Sym patch_sym) -{ - unsigned long elf_addr = 0; - /* - * Handle external symbol, several possible solutions here: - * 1. use symbol address from .dynsym, but most of its address is still - * undefined - * 2. use address from PLT/GOT, problems are: - * 1) range limit(use jmp table?) - * 2) only support existed symbols - * 3. read symbol from library, combined with load_bias, calculate it - * directly and then worked with jmp table. - * - * Currently, we will try approach 1 and approach 2. - * Approach 3 is more general, but difficulty to implement. - */ - - /* resolve from plt */ - elf_addr = resolve_rela_plt(uelf, obj, name, &patch_sym); - /* resolve from got */ - if (!elf_addr) { - elf_addr = resolve_rela_dyn(uelf, obj, name, &patch_sym); - } - /* resolve from dynsym */ - if (!elf_addr) { - elf_addr = resolve_dynsym(uelf, obj, name); - } - /* resolve from sym */ - if (!elf_addr) { - elf_addr = resolve_sym(uelf, name); - } - /* resolve from patch sym */ - if (!elf_addr) { - elf_addr = resolve_patch_sym(uelf, name, &patch_sym); - } - if (!elf_addr) { - log_error("Cannot resolve symbol '%s'\n", name); - } - - return elf_addr; -} - -int simplify_symbols(struct upatch_elf *uelf, struct object_file *obj) -{ - GElf_Sym *sym = (void *)uelf->info.shdrs[uelf->index.sym].sh_addr; - unsigned long secbase; - unsigned int i; - int ret = 0; - unsigned long elf_addr; - - for (i = 1; i < uelf->num_syms; i++) { - const char *name; - - if (GELF_ST_TYPE(sym[i].st_info) == STT_SECTION && - sym[i].st_shndx < uelf->info.hdr->e_shnum) { - name = uelf->info.shstrtab + - uelf->info.shdrs[sym[i].st_shndx].sh_name; - } else { - name = uelf->strtab + sym[i].st_name; - } - switch (sym[i].st_shndx) { - case SHN_COMMON: - log_debug("Unsupported common symbol '%s'\n", name); - ret = -ENOEXEC; - break; - case SHN_ABS: - break; - case SHN_UNDEF: - elf_addr = resolve_symbol(uelf, obj, name, sym[i]); - if (!elf_addr) { - ret = -ENOEXEC; - } - sym[i].st_value = elf_addr; - log_debug("Resolved symbol '%s' at 0x%lx\n", - name, (unsigned long)sym[i].st_value); - break; - case SHN_LIVEPATCH: - sym[i].st_value += uelf->relf->load_bias; - log_debug("Resolved livepatch symbol '%s' at 0x%lx\n", - name, (unsigned long)sym[i].st_value); - break; - default: - /* use real address to calculate secbase */ - secbase = uelf->info.shdrs[sym[i].st_shndx].sh_addralign; - sym[i].st_value += secbase; - log_debug("Symbol '%s' at 0x%lx\n", - name, (unsigned long)sym[i].st_value); - break; - } - } - - return ret; -} diff --git a/upatch-manage/upatch-stack-check.c b/upatch-manage/upatch-stack-check.c deleted file mode 100644 index 474d8579..00000000 --- a/upatch-manage/upatch-stack-check.c +++ /dev/null @@ -1,129 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "upatch-elf.h" -#include "upatch-stack-check.h" -#include "upatch-ptrace.h" -#include "upatch-common.h" -#include "log.h" - -static int stack_check(struct upatch_info *uinfo, unsigned long pc, upatch_action_t action) -{ - unsigned long start; - unsigned long end; - - for (size_t i = 0; i < uinfo->changed_func_num; i++) { - struct upatch_func_addr addr = uinfo->funcs[i].addr; - - if (action == ACTIVE) { - start = addr.old_addr; - end = addr.old_addr + addr.old_size; - } else if (action == DEACTIVE) { - start = addr.new_addr; - end = addr.new_addr + addr.new_size; - } else { - log_error("Unknown upatch action\n"); - return -1; - } - if (pc >= start && pc <= end) { - log_error("Failed to check stack, running function: %s\n", - uinfo->funcs[i].name); - return -1; - } - } - return 0; -} - -static unsigned long *stack_alloc(size_t *size) -{ - struct rlimit rl; - unsigned long *stack = NULL; - - if (getrlimit(RLIMIT_STACK, &rl) != 0) { - log_error("Failed to get system stack size config\n"); - return 0; - } - - *size = rl.rlim_cur; - stack = (unsigned long *)malloc(*size); - if (stack == NULL) { - log_error("Failed to malloc stack\n"); - } - - return stack; -} - -static size_t read_stack(struct upatch_process *proc, - unsigned long *stack, size_t size, unsigned long sp) -{ - return (size_t)pread(proc->memfd, (void *)stack, size, (off_t)sp); -} - -static int stack_check_each_pid(struct upatch_process *proc, - struct upatch_info *uinfo, int pid, upatch_action_t action) -{ - unsigned long sp, pc; - unsigned long *stack = NULL; - size_t stack_size = 0; - int ret = 0; - - if (upatch_arch_reg_init(pid, &sp, &pc) < 0) { - return -1; - } - ret = stack_check(uinfo, pc, action); - if (ret < 0) { - return ret; - } - - stack = stack_alloc(&stack_size); - if (stack == NULL) { - return -1; - } - - stack_size = read_stack(proc, stack, stack_size, sp); - log_debug("[%d] Stack size %lu, region [0x%lx, 0x%lx]\n", - pid, stack_size, sp, sp + stack_size); - - for (size_t i = 0; i < stack_size / sizeof(*stack); i++) { - if (stack[i] == 0 || stack[i] == -1UL) { - continue; - } - - ret = stack_check(uinfo, stack[i], action); - if (ret < 0) { - goto free; - } - } -free: - free(stack); - return ret; -} - -int upatch_stack_check(struct upatch_info *uinfo, struct upatch_process *proc, - upatch_action_t action) -{ - struct upatch_ptrace_ctx *pctx; - struct timeval start, end; - - if (gettimeofday(&start, NULL) < 0) { - log_error("Failed to get stack check start time\n"); - } - - list_for_each_entry(pctx, &proc->ptrace.pctxs, list) { - if (stack_check_each_pid(proc, uinfo, pctx->pid, action) < 0) { - return -EBUSY; - } - } - - if (gettimeofday(&end, NULL) < 0) { - log_error("Failed to get stack check end time\n"); - } - - log_debug("Stack check time %ld microseconds\n", - get_microseconds(&start, &end)); - return 0; -} diff --git a/upatch-manage/upatch-stack-check.h b/upatch-manage/upatch-stack-check.h deleted file mode 100644 index 49be6e3c..00000000 --- a/upatch-manage/upatch-stack-check.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef __UPATCH_STACK_CHECK_H -#define __UPATCH_STACK_CHECK_H - -#include "upatch-elf.h" -#include "upatch-process.h" - -#define STACK_CHECK_RETRY_TIMES 3 - -typedef enum { - ACTIVE, - DEACTIVE, -} upatch_action_t; - -int upatch_arch_reg_init(int pid, unsigned long *sp, unsigned long *pc); -int upatch_stack_check(struct upatch_info *uinfo, struct upatch_process *proc, - upatch_action_t action); -#endif diff --git a/upatch-manage/util.c b/upatch-manage/util.c new file mode 100644 index 00000000..a31a42b4 --- /dev/null +++ b/upatch-manage/util.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * provide utils + * Copyright (C) 2024 Huawei Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "patch_entity.h" + +bool is_elf_valid(Elf_Ehdr *ehdr, size_t len, bool is_patch) +{ + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { + log_err("elf magic wrong!\n"); + return false; + } + + if (!elf_check_arch(ehdr)) { + log_err("elf_check_arch failed, e_machine = %d\n", ehdr->e_machine); + return false; + } + + if (!ehdr->e_shoff || !ehdr->e_shnum || !ehdr->e_shentsize) { + log_err("file don't have section\n"); + return false; + } + + if (ehdr->e_shentsize != sizeof(Elf_Shdr)) { + log_err("e_shentsize is %d not %zu\n", ehdr->e_shentsize, sizeof(Elf_Shdr)); + return false; + } + + if (ehdr->e_shstrndx > (ehdr->e_shnum - 1)) { + log_err("e_shstrndx = %d, greater than e_shnum = %d\n", + ehdr->e_shstrndx, ehdr->e_shnum); + return false; + } + + if (len < sizeof(Elf_Ehdr) || ehdr->e_shoff >= len || + ehdr->e_shnum * sizeof(Elf_Shdr) > len - ehdr->e_shoff) { + log_err("len is %ld, not suitable with e_shnum %d and e_shoff %lld\n", + (long int)len, ehdr->e_shnum, (long long)ehdr->e_shoff); + return false; + } + + if (is_patch) { + if (ehdr->e_type != ET_REL) { + log_err("patch is not REL format\n"); + return false; + } + } else { + if ((ehdr->e_type != ET_EXEC) && (ehdr->e_type != ET_DYN)) { + log_err("file is not exe or so\n"); + return false; + } + + if (!ehdr->e_phoff || !ehdr->e_phnum || !ehdr->e_phentsize) { + log_err("file don't have program header\n"); + return false; + } + } + + return true; +} + +struct inode *path_inode(const char *file) +{ + struct path path; + struct inode *inode; + int ret = 0; + + ret = kern_path(file, LOOKUP_FOLLOW, &path); + if (ret) { + log_err("%s: cannot get inode of %s\n", __func__, file); + return ERR_PTR(ret); + } + + inode = path.dentry->d_inode; + if (!inode) { + log_err("%s: path inode is NULL, path = %s\n", __func__, file); + return ERR_PTR(-ENOENT); + } + path_put(&path); + return inode; +} + +void *vmalloc_read(struct file *file, loff_t offset, size_t len) +{ + void *buff = NULL; + ssize_t read = 0; + + if (!len) { + return ERR_PTR(-EINVAL); + } + + buff = vmalloc(len); + if (!buff) { + return ERR_PTR(-ENOMEM); + } + + read = kernel_read(file, buff, len, &offset); + if (read < 0) { + vfree(buff); + return ERR_PTR(read); + } + if (read != len) { + vfree(buff); + return ERR_PTR(-EIO); + } + + return buff; +} \ No newline at end of file diff --git a/upatch-manage/upatch-common.h b/upatch-manage/util.h similarity index 36% rename from upatch-manage/upatch-common.h rename to upatch-manage/util.h index 9b5a0e6d..d44b0e79 100644 --- a/upatch-manage/upatch-common.h +++ b/upatch-manage/util.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * upatch-lib + * provide utils * Copyright (C) 2024 Huawei Technologies Co., Ltd. * * This program is free software; you can redistribute it and/or modify @@ -18,61 +18,63 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __UPATCH_COMMON__ -#define __UPATCH_COMMON__ +#ifndef _UPATCH_MANAGE_UTIL_H +#define _UPATCH_MANAGE_UTIL_H -#include -#include +#include +#include +#include +#include -#define ALLOC_LINK(_new, _list) \ - do { \ - (_new) = calloc(1, sizeof(*(_new))); \ - if (!(_new)) { \ - ERROR("calloc"); \ - } \ - INIT_LIST_HEAD(&(_new)->list); \ - if (_list) { \ - list_add(&(_new)->list, (_list)); \ - } \ - } while (0) +#include +#include -static inline int page_shift(long n) -{ - int res = -1; - - while (n) { - res++; - n >>= 1; - } - - return res; -} +static const char* MODULE_NAME = THIS_MODULE->name; -#ifndef PAGE_SIZE -#define PAGE_SIZE sysconf(_SC_PAGE_SIZE) -#define PAGE_MASK (~(PAGE_SIZE - 1)) -#define PAGE_SHIFT page_shift(PAGE_SIZE) -#endif +#define log_err(fmt, args...) pr_err("%s: " fmt, MODULE_NAME, ##args) +#define log_warn(fmt, args...) pr_warn("%s: " fmt, MODULE_NAME, ##args) +#define log_info(fmt, args...) pr_info("%s: " fmt, MODULE_NAME, ##args) +#define log_debug(fmt, args...) pr_debug("%s: " fmt, MODULE_NAME, ##args) -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) -#define ALIGN(x, a) (((x) + (a) - 1) & (~((a) - 1))) -#define PAGE_ALIGN(x) ALIGN((x), (unsigned long)PAGE_SIZE) - -#define ROUND_DOWN(x, m) ((x) & ~((m) - 1)) -#define ROUND_UP(x, m) (((x) + (m) - 1) & ~((m) - 1)) +/* + * Alloc buffer and read file content + * @param path: file + * @param offset: file offset + * @param len: read length + * @return buffer pointer + */ +void *vmalloc_read(struct file *file, loff_t offset, size_t len); -#define BIT(x) (1UL << (x)) +/* + * Free kalloc() allocated memory safely + * @param addr: memory address + * @return void + */ +static inline void kfree_safe(const void *addr) +{ + if (addr) { + kfree(addr); + } +} -#define SEC2MICRO 1000000 +#define KFREE_CLEAR(ptr) do { kfree_safe(ptr); ptr = NULL; } while (0) -static inline long get_microseconds(struct timeval *start, struct timeval *end) +/* + * Free valloc() allocated memory safely + * @param addr: memory address + * @return void + */ +static inline void vfree_safe(const void *addr) { - long sec = end->tv_sec - start->tv_sec; - long usec = end->tv_usec - start->tv_usec; - - return sec * SEC2MICRO + usec; + if (addr) { + vfree(addr); + } } -bool streql(const char *, const char *); +#define VFREE_CLEAR(ptr) do { vfree_safe(ptr); ptr = NULL; } while (0) + +bool is_elf_valid(Elf_Ehdr *ehdr, size_t len, bool is_patch); + +struct inode *path_inode(const char *file); -#endif +#endif // _UPATCH_MANAGE_UTIL_H -- Gitee