578 lines
14 KiB
C
Executable File
578 lines
14 KiB
C
Executable File
/*
|
|
* patch_file.c - transform patch to code
|
|
*
|
|
* Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
|
|
*
|
|
* You should have received a copy of license along with this program;
|
|
* if not, ask for it from Baidu, Inc.
|
|
*
|
|
*/
|
|
|
|
#if IS_ENABLED(CONFIG_MODULES)
|
|
#include <linux/moduleloader.h>
|
|
#endif
|
|
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/crc32.h>
|
|
#include <asm/cacheflush.h>
|
|
|
|
#include "util.h"
|
|
#include "patch_api.h"
|
|
#include "patch_file.h"
|
|
#include "patch_info.h"
|
|
#include "patch_mgr.h"
|
|
#include "oases_signing.h"
|
|
#include "plts.h"
|
|
#include "vmarea.h"
|
|
#include "kallsyms.h"
|
|
|
|
struct func_relocation_item {
|
|
char* name; /* relocation function name */
|
|
unsigned long addr; /* relocation function addr */
|
|
};
|
|
|
|
#define funcptr_2_ul(func) ((unsigned long)(&(func)))
|
|
#define FUNC_RELOCATION_ITEM(s) {#s, funcptr_2_ul(s)}
|
|
|
|
/* api function from patch_api.h */
|
|
static const struct func_relocation_item api_fri_list[] = {
|
|
FUNC_RELOCATION_ITEM(oases_lookup_name),
|
|
FUNC_RELOCATION_ITEM(oases_register_patch),
|
|
FUNC_RELOCATION_ITEM(oases_printk),
|
|
FUNC_RELOCATION_ITEM(oases_memset),
|
|
FUNC_RELOCATION_ITEM(oases_memcpy),
|
|
FUNC_RELOCATION_ITEM(oases_memcmp),
|
|
FUNC_RELOCATION_ITEM(oases_copy_from_user),
|
|
FUNC_RELOCATION_ITEM(oases_copy_to_user),
|
|
FUNC_RELOCATION_ITEM(oases_get_user_u8),
|
|
FUNC_RELOCATION_ITEM(oases_put_user_u8),
|
|
FUNC_RELOCATION_ITEM(oases_get_user_u16),
|
|
FUNC_RELOCATION_ITEM(oases_put_user_u16),
|
|
FUNC_RELOCATION_ITEM(oases_get_user_u32),
|
|
FUNC_RELOCATION_ITEM(oases_put_user_u32),
|
|
FUNC_RELOCATION_ITEM(oases_get_user_u64),
|
|
FUNC_RELOCATION_ITEM(oases_put_user_u64),
|
|
FUNC_RELOCATION_ITEM(oases_get_current),
|
|
FUNC_RELOCATION_ITEM(oases_linux_version_code),
|
|
FUNC_RELOCATION_ITEM(oases_version),
|
|
FUNC_RELOCATION_ITEM(oases_query_patch_info),
|
|
FUNC_RELOCATION_ITEM(oases_setup_callbacks),
|
|
};
|
|
|
|
static void fix_oases_plt(unsigned long symbol, void* plt_entry)
|
|
{
|
|
#ifdef __aarch64__
|
|
struct oases_plt_entry entry = {
|
|
.ldr = 0x58000050U, /* LDR X16, loc_addr */
|
|
.br = 0xd61f0200U, /* BR X16 */
|
|
.addr = symbol /* lable: loc_addr */
|
|
};
|
|
#else
|
|
struct oases_plt_entry entry = {
|
|
.ldr = 0xE51FF004U, /* LDR PC, [PC, #-4] */
|
|
.addr = symbol /* DCD: loc_addr */
|
|
};
|
|
#endif
|
|
memcpy(plt_entry, &entry, sizeof(entry));
|
|
}
|
|
|
|
static unsigned long get_symbol_addr(const char *name)
|
|
{
|
|
unsigned int i;
|
|
void *addr = NULL;
|
|
size_t listsize = ARRAY_SIZE(api_fri_list);
|
|
|
|
for (i = 0; i < listsize; i++) {
|
|
if (!strncmp(name, api_fri_list[i].name, RELOC_FUNC_NAME_SIZE)) {
|
|
return api_fri_list[i].addr;
|
|
}
|
|
}
|
|
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
addr = oases_lookup_name(VMLINUX, name, NULL);
|
|
#endif
|
|
|
|
return (unsigned long)addr;
|
|
}
|
|
|
|
static int reloc_api_function(void *code_base, const char *name, unsigned int offset)
|
|
{
|
|
unsigned long symbol;
|
|
|
|
symbol = get_symbol_addr(name);
|
|
if (!symbol) {
|
|
oases_debug("symbol:%s, offset: %#x find addr fail\n", name, offset);
|
|
return -EINVAL;
|
|
}
|
|
|
|
fix_oases_plt(symbol, code_base + offset);
|
|
return 0;
|
|
}
|
|
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
static int reloc_symbol_data(void *code_base, void *symbols, unsigned int count)
|
|
{
|
|
unsigned long addr = 0;
|
|
unsigned size = 0;
|
|
int i = 0;
|
|
int j = 0;
|
|
struct reloc_symbol_info *symbol;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
symbol = (struct reloc_symbol_info *)(symbols + size);
|
|
addr = (unsigned long)oases_lookup_name(VMLINUX, symbol->name, NULL);
|
|
if (!addr) {
|
|
oases_debug("symbol:%s find addr fail\n", symbol->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (j = 0; j < symbol->count; j++)
|
|
memcpy(code_base+symbol->offsets[j], &addr, sizeof(unsigned long));
|
|
|
|
size = sizeof(struct reloc_function_info) + sizeof(unsigned int) * symbol->count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void reset_data(void *data, unsigned long value)
|
|
{
|
|
unsigned long tmp;
|
|
tmp = *((unsigned long *)data);
|
|
tmp += value;
|
|
memcpy(data, &tmp, sizeof(unsigned long));
|
|
}
|
|
|
|
static int oases_patch_sig_check(struct oases_patch_file *patch, char *data)
|
|
{
|
|
const unsigned long markerlen = sizeof(OASES_SIG_STRING) - 1;
|
|
int ret;
|
|
|
|
if (patch->len < markerlen
|
|
|| memcmp(data + patch->len - markerlen, OASES_SIG_STRING, markerlen) != 0) {
|
|
oases_error("system sig marker check fail\n");
|
|
return -ENOKEY;
|
|
}
|
|
|
|
patch->len -= markerlen;
|
|
ret = oases_verify_sig(data, &patch->len, SIG_TYPE_VENDOR);
|
|
if (ret < 0) {
|
|
oases_error("vendor sig verify fail\n");
|
|
return ret;
|
|
}
|
|
|
|
if (patch->len > markerlen
|
|
&& !memcmp(data + patch->len - markerlen, OASES_SIG_STRING, markerlen)) {
|
|
patch->len -= markerlen;
|
|
ret = oases_verify_sig(data, &patch->len, SIG_TYPE_OASES);
|
|
if (ret < 0) {
|
|
oases_error("oases sig verify fail\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int verify_patch_checksum(struct oases_patch_header *pheader, char *data, unsigned long size)
|
|
{
|
|
unsigned int checksum;
|
|
unsigned int offset = PATCH_MAGIC_SIZE + sizeof(pheader->checksum);
|
|
|
|
checksum = crc32(0, data + offset, size - offset);
|
|
if (checksum != pheader->checksum)
|
|
return -ENOEXEC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verify_patch_header(struct oases_patch_header *pheader, void *data, unsigned long size)
|
|
{
|
|
unsigned long offset;
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
struct reloc_symbol_info *relsymbols;
|
|
unsigned int i;
|
|
#endif
|
|
|
|
if (pheader->header_size != sizeof(struct oases_patch_header))
|
|
return -ENOEXEC;
|
|
if (pheader->patch_size != size)
|
|
return -ENOEXEC;
|
|
|
|
offset = pheader->header_size;
|
|
|
|
/* reset_data_offset and reset_data_count */
|
|
if (pheader->reset_data_offset != offset
|
|
|| (offset + pheader->reset_data_count * sizeof(unsigned int) >= size)) {
|
|
oases_debug("reset_data error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
offset += pheader->reset_data_count * sizeof(unsigned int);
|
|
|
|
/* reloc_func_offset and reloc_func_count */
|
|
if (pheader->reloc_func_offset != offset
|
|
|| (offset + pheader->reloc_func_count * sizeof(struct reloc_function_info) >= size)) {
|
|
oases_debug("reloc_func error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
offset += pheader->reloc_func_count * sizeof(struct reloc_function_info);
|
|
|
|
/* section_info_offset */
|
|
if (pheader->section_info_offset != offset
|
|
|| (offset + SECTION_NR * sizeof(struct section_info) >= size)) {
|
|
oases_debug("section_info error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
offset += SECTION_NR * sizeof(struct section_info);
|
|
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
/* reloc_symbol */
|
|
if (pheader->reloc_symbol_count) {
|
|
if (pheader->reloc_symbol_offset != offset) {
|
|
oases_debug("reloc_symbol error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
for (i = 0; i < pheader->reloc_symbol_count; i++) {
|
|
if (offset + sizeof(struct reloc_symbol_info) >= size) {
|
|
oases_debug("reloc_symbol error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
relsymbols = data + offset;
|
|
offset += sizeof(*relsymbols);
|
|
offset += relsymbols->count * sizeof(unsigned int);
|
|
}
|
|
if (offset >= size) {
|
|
oases_debug("reloc_symbol error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* code_offset and code_entry_offset */
|
|
if (pheader->code_offset != offset
|
|
|| offset + pheader->code_size != size
|
|
|| pheader->code_entry_offset > (pheader->code_size - sizeof(unsigned int))) {
|
|
oases_debug("code_offset error\n");
|
|
return -ENOEXEC;
|
|
}
|
|
offset += pheader->code_size;
|
|
|
|
/* all parsed */
|
|
if (offset != size)
|
|
return -ENOEXEC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int is_valid_type(unsigned int attr)
|
|
{
|
|
#if !OASES_ENABLE_PRECISE_PATCH
|
|
unsigned int type = (attr >> 4) & 0x0F;
|
|
if (type == PATCH_ATTR_PRECISE)
|
|
return 0;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int is_valid_arch(unsigned int attr)
|
|
{
|
|
unsigned int arch = attr & 0x0F;
|
|
#ifdef __aarch64__
|
|
return (arch == PATCH_ATTR_ARM64) ? 1: 0;
|
|
#else
|
|
return (arch == PATCH_ATTR_ARM32) ? 1: 0;
|
|
#endif
|
|
}
|
|
|
|
static int is_valid_version(unsigned int version)
|
|
{
|
|
if (version > OASES_VERSION)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int is_valid_section_info(struct section_info *sections, unsigned int code_size)
|
|
{
|
|
unsigned long size = 0, section_end, page_size;
|
|
int i;
|
|
struct section_info *s;
|
|
|
|
for (i = 0; i < SECTION_NR; i++) {
|
|
s = sections + i;
|
|
/* type must be SECTION_* */
|
|
if (s->type != i)
|
|
return 0;
|
|
/* section data must be within code */
|
|
section_end = s->offset + s->size;
|
|
if (section_end > code_size)
|
|
return 0;
|
|
/* size must within page */
|
|
page_size = s->page * PAGE_SIZE;
|
|
if (PAGE_ALIGN(s->size) != page_size)
|
|
return 0;
|
|
size += s->size;
|
|
}
|
|
/* sum of per section must match code size */
|
|
if (size != code_size)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int is_valid_reset_data(unsigned int *data, unsigned int count,
|
|
unsigned int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
if (data[i] > size)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int is_valid_reloc_function_info(struct reloc_function_info *relfuncs,
|
|
unsigned int count, unsigned int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if (relfuncs[i].offset > size
|
|
|| relfuncs[i].name[RELOC_FUNC_NAME_SIZE - 1] != 0
|
|
|| relfuncs[i].name[0] == 0)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
static int is_valid_reloc_symbol_info(struct reloc_symbol_info *relsymbols,
|
|
unsigned int count, unsigned int size)
|
|
{
|
|
int i, n;
|
|
void *data = relsymbols;
|
|
struct reloc_symbol_info *p;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
p = data;
|
|
for (n = 0; n < p->count; n++) {
|
|
if (p->offsets[n] > size)
|
|
return 0;
|
|
}
|
|
data += sizeof(*p);
|
|
data += p->count * sizeof(unsigned int);
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static int oases_layout_patch(struct oases_patch_file *pfile, void *data)
|
|
{
|
|
int ret = 0, i;
|
|
struct oases_patch_header *pheader = data;
|
|
unsigned long code_size = 0;
|
|
unsigned int *redatas;
|
|
struct reloc_function_info *relfuncs;
|
|
struct section_info *sections;
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
struct reloc_symbol_info *relsymbols;
|
|
#endif
|
|
|
|
if (strncmp(pheader->magic, PATCH_FILE_MAGIC, PATCH_MAGIC_SIZE)) {
|
|
oases_debug("invalid PATCH_FILE_MAGIC\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = verify_patch_checksum(pheader, data, pfile->len);
|
|
if (ret < 0) {
|
|
oases_debug("invalid patch checksum\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = is_valid_arch(pheader->attr);
|
|
if (!ret) {
|
|
oases_debug("invalid patch arch\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
ret = is_valid_type(pheader->attr);
|
|
if (!ret) {
|
|
oases_debug("invalid patch type\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
ret = is_valid_version(pheader->oases_version);
|
|
if (!ret) {
|
|
oases_debug("invalid patch version\n");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
ret = oases_valid_name(pheader->id, PATCH_ID_LEN - 1);
|
|
if (ret < 0) {
|
|
oases_debug("invalid patch id\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* ensures that all fields are within data */
|
|
ret = verify_patch_header(pheader, data, pfile->len);
|
|
if (ret < 0) {
|
|
oases_debug("invalid patch header\n");
|
|
return ret;
|
|
}
|
|
|
|
/* check section info */
|
|
ret = is_valid_section_info(data + pheader->section_info_offset, pheader->code_size);
|
|
if (!ret) {
|
|
oases_debug("invalid patch header, section info\n");
|
|
return -EINVAL;
|
|
}
|
|
sections = (struct section_info *)(data + pheader->section_info_offset);
|
|
|
|
for (i= 0; i < SECTION_NR; i++)
|
|
code_size += sections[i].page * PAGE_SIZE;
|
|
pfile->code_size = code_size;
|
|
code_size -= sizeof(unsigned long);
|
|
|
|
/* check reset data offset */
|
|
ret = is_valid_reset_data(data + pheader->reset_data_offset,
|
|
pheader->reset_data_count, code_size);
|
|
if (!ret) {
|
|
oases_debug("invalid patch header, reset data\n");
|
|
return -EINVAL;
|
|
}
|
|
redatas = data + pheader->reset_data_offset;
|
|
|
|
/* check reloc function info */
|
|
ret = is_valid_reloc_function_info(data + pheader->reloc_func_offset,
|
|
pheader->reloc_func_count, code_size);
|
|
if (!ret) {
|
|
oases_debug("invalid patch header, reloc function info\n");
|
|
return -EINVAL;
|
|
}
|
|
relfuncs = data + pheader->reloc_func_offset;
|
|
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
/* check reloc symbol info */
|
|
if (pheader->reloc_symbol_count > 0) {
|
|
ret = is_valid_reloc_symbol_info(data + pheader->reloc_symbol_offset,
|
|
pheader->reloc_symbol_count, code_size);
|
|
if (!ret) {
|
|
oases_debug("invalid patch header, reloc symbol info\n");
|
|
return -EINVAL;
|
|
}
|
|
relsymbols = data + pheader->reloc_symbol_offset;
|
|
} else {
|
|
relsymbols = NULL;
|
|
}
|
|
#endif
|
|
pfile->pheader = pheader;
|
|
pfile->redatas = redatas;
|
|
pfile->relfuncs = relfuncs;
|
|
pfile->sections = sections;
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
pfile->relsymbols = relsymbols;
|
|
#endif
|
|
pfile->codes = data + pheader->code_offset;
|
|
return 0;
|
|
}
|
|
|
|
int oases_build_code(struct oases_patch_info *info, struct oases_patch_file *pfile)
|
|
{
|
|
int i, ret;
|
|
unsigned int cnt, code_size = 0;
|
|
struct oases_patch_header *pheader = pfile->pheader;
|
|
struct section_info *sections = pfile->sections;
|
|
struct reloc_function_info *relocinfo;
|
|
void *code_base, *tmp;
|
|
|
|
code_size = pfile->code_size;
|
|
code_base = vmalloc_exec(code_size);
|
|
if (!code_base) {
|
|
oases_debug("vmalloc_exec %u failed\n", code_size);
|
|
return -ENOMEM;
|
|
}
|
|
memset(code_base, 0, code_size);
|
|
/* copy sections */
|
|
tmp = code_base;
|
|
for (i = 0; i < SECTION_NR; i++) {
|
|
if (sections[i].size > 0) {
|
|
memcpy(tmp, pfile->codes + sections[i].offset, sections[i].size);
|
|
tmp += sections[i].page * PAGE_SIZE;
|
|
}
|
|
}
|
|
|
|
cnt = pheader->reset_data_count;
|
|
for (i = 0; i < cnt; i++) {
|
|
reset_data(code_base + pfile->redatas[i], (unsigned long)code_base);
|
|
}
|
|
|
|
cnt = pheader->reloc_func_count;
|
|
for (i = 0; i < cnt; i++) {
|
|
relocinfo = &pfile->relfuncs[i];
|
|
ret = reloc_api_function(code_base, relocinfo->name, relocinfo->offset);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
|
|
#if OASES_ENABLE_PRECISE_PATCH
|
|
if (pfile->relsymbols) {
|
|
ret = reloc_symbol_data(code_base, pfile->relsymbols, pheader->reloc_symbol_count);
|
|
if (ret < 0)
|
|
goto fail;
|
|
}
|
|
#endif
|
|
/* set page attr , section layout
|
|
+++++++++++++++
|
|
+ RX | RW | RO +
|
|
+++++++++++++++
|
|
*/
|
|
tmp = code_base;
|
|
for (i = 0; i < SECTION_NR; i++) {
|
|
if (sections[i].size == 0) {
|
|
continue;
|
|
}
|
|
/* already RWX */
|
|
if (sections[i].type == SECTION_RX) {
|
|
/* kill W for RX */
|
|
oases_set_vmarea_ro((unsigned long)tmp, sections[i].page);
|
|
} else if (sections[i].type == SECTION_RW) {
|
|
/* kill X for RW */
|
|
oases_set_vmarea_nx((unsigned long)tmp, sections[i].page);
|
|
} else {
|
|
/* kill WX for RO */
|
|
oases_set_vmarea_ro((unsigned long)tmp, sections[i].page);
|
|
oases_set_vmarea_nx((unsigned long)tmp, sections[i].page);
|
|
}
|
|
tmp += sections[i].page * PAGE_SIZE;
|
|
}
|
|
|
|
flush_icache_range((unsigned long) code_base, (unsigned long)(code_base + code_size));
|
|
info->code_base = code_base;
|
|
info->code_entry = code_base + pheader->code_entry_offset;
|
|
info->code_size = code_size;
|
|
return 0;
|
|
fail:
|
|
vfree(code_base);
|
|
return ret;
|
|
}
|
|
|
|
int oases_init_patch_file(struct oases_patch_file *pfile, void *data)
|
|
{
|
|
int ret;
|
|
|
|
ret = oases_patch_sig_check(pfile, data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = oases_layout_patch(pfile, data);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = oases_check_patch(pfile->pheader->id);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|