symbol location verification support

This commit introduces functionality to verify the location of symbols
used in both the patch and dynrelas sections.  It adds significant
protection from mismatches between the base and running kernels.

Signed-off-by: Seth Jennings <sjenning@redhat.com>
This commit is contained in:
Seth Jennings 2014-05-23 16:36:31 -05:00
parent 08dc2ae78c
commit 505e948af0
3 changed files with 191 additions and 4 deletions

View File

@ -41,6 +41,7 @@
#include <linux/hashtable.h>
#include <linux/hardirq.h>
#include <linux/uaccess.h>
#include <linux/kallsyms.h>
#include <asm/stacktrace.h>
#include <asm/cacheflush.h>
#include "kpatch.h"
@ -420,6 +421,49 @@ static void kpatch_remove_funcs_from_filter(struct kpatch_func *funcs,
}
}
int kpatch_verify_symbol_match(char *name, unsigned long addr)
{
int ret;
unsigned long offset;
char buf[KSYM_SYMBOL_LEN], *symname, *offsetstr, *pos;
sprint_symbol(buf, addr);
/*
* sprint_symbol() doesn't return an error if it can't find the symbol.
* It returns the addr we passed in as a hex string in 'name'.
* Check for this and, if so, error.
*/
if (!strncmp(buf, "0x", 2)) {
pr_err("failed to find named symbol\n");
return -EINVAL;
}
/* hack out the symbol name and offset */
/* example format for buf 'printk+0x0/0x8e' */
symname = buf;
pos = strchr(buf, '+');
*pos = '\0';
pos += 3; /* skip the "+0x" */
offsetstr = pos;
pos = strchr(offsetstr, '/');
*pos = '\0';
ret = kstrtoul(offsetstr, 16, &offset);
if (ret) {
pr_err("failed to parse symbol offset\n");
return ret;
}
if (strcmp(symname, name) || offset != 0) {
pr_err("base kernel mismatch for symbol '%s'\n", name);
pr_err("expected address was 0x%016lx\n", addr);
return -EINVAL;
}
return 0;
}
int kpatch_register(struct kpatch_module *kpmod, bool replace)
{
int ret, i;
@ -455,15 +499,19 @@ int kpatch_register(struct kpatch_module *kpmod, bool replace)
for (i = 0; i < kpmod->dynrelas_nr; i++) {
dynrela = &kpmod->dynrelas[i];
ret = kpatch_verify_symbol_match(dynrela->name, dynrela->src);
if (ret)
goto err_put;
switch (dynrela->type) {
case R_X86_64_PC32:
loc = (void *)dynrela->dest;
val = (u32)(dynrela->src - dynrela->dest);
val = (u32)(dynrela->src + dynrela->addend -
dynrela->dest);
size = 4;
break;
case R_X86_64_32S:
loc = (void *)dynrela->dest;
val = (s32)dynrela->src;
val = (s32)dynrela->src + dynrela->addend;
size = 4;
break;
default:
@ -488,6 +536,11 @@ int kpatch_register(struct kpatch_module *kpmod, bool replace)
func->kpmod = kpmod;
func->patch = &kpmod->patches[i];
ret = kpatch_verify_symbol_match(func->patch->name,
func->patch->old_addr);
if (ret)
goto err_put;
/*
* If any other modules have also patched this function, it
* already has an ftrace handler.

View File

@ -28,12 +28,15 @@ struct kpatch_patch {
unsigned long new_size;
unsigned long old_addr;
unsigned long old_size;
char *name;
};
struct kpatch_dynrela {
unsigned long dest;
unsigned long src;
unsigned long type;
char *name;
int addend;
};
#ifdef __KERNEL__

View File

@ -133,10 +133,16 @@ struct rela {
char *string;
};
struct string {
struct list_head list;
char *name;
};
struct kpatch_elf {
Elf *elf;
struct list_head sections;
struct list_head symbols;
struct list_head strings;
int fd;
};
@ -219,6 +225,25 @@ struct symbol *find_symbol_by_name(struct list_head *list, const char *name)
list_add_tail(&(_new)->list, (_list)); \
}
/* returns the offset of the string in the string table */
int offset_of_string(struct list_head *list, char *name)
{
struct string *string;
int index = 0;
/* try to find string in the string list */
list_for_each_entry(string, list, list) {
if (!strcmp(string->name, name))
return index;
index += strlen(string->name) + 1;
}
/* allocation a new string */
ALLOC_LINK(string, list);
string->name = name;
return index;
}
/*************
* Functions
* **********/
@ -423,6 +448,7 @@ struct kpatch_elf *kpatch_elf_open(const char *name)
memset(kelf, 0, sizeof(*kelf));
INIT_LIST_HEAD(&kelf->sections);
INIT_LIST_HEAD(&kelf->symbols);
INIT_LIST_HEAD(&kelf->strings);
/* read and store section, symbol entries from file */
kelf->elf = elf;
@ -867,6 +893,7 @@ void kpatch_migrate_included_elements(struct kpatch_elf *kelf, struct kpatch_elf
memset(out, 0, sizeof(*out));
INIT_LIST_HEAD(&out->sections);
INIT_LIST_HEAD(&out->symbols);
INIT_LIST_HEAD(&out->strings);
/* migrate included sections from kelf to out */
list_for_each_entry_safe(sec, safesec, &kelf->sections, list) {
@ -1304,7 +1331,7 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf,
{
int nr, size, index;
struct section *sec, *relasec;
struct symbol *sym;
struct symbol *sym, *strsym;
struct rela *rela;
struct lookup_result result;
struct kpatch_patch *patches;
@ -1358,6 +1385,11 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf,
relasec->sh.sh_entsize = sizeof(GElf_Rela);
relasec->sh.sh_addralign = 8;
/* lookup strings symbol */
strsym = find_symbol_by_name(&kelf->symbols, "__kpatch_strings");
if (!strsym)
ERROR("can't find strsym");
/* populate sections */
index = 0;
list_for_each_entry(sym, &kelf->symbols, list) {
@ -1392,6 +1424,16 @@ void kpatch_create_patches_sections(struct kpatch_elf *kelf,
rela->addend = 0;
rela->offset = index * sizeof(*patches);
/*
* Add a relocation that will populate
* the patches[index].name field.
*/
ALLOC_LINK(rela, &relasec->relas);
rela->sym = strsym;
rela->type = R_X86_64_64;
rela->addend = offset_of_string(&kelf->strings, sym->name);
rela->offset = index * sizeof(*patches) +
offsetof(struct kpatch_patch, name);
index++;
}
}
@ -1408,6 +1450,7 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf,
int nr, size, index;
struct section *sec, *relasec;
struct rela *rela, *dynrela, *safe;
struct symbol *strsym;
struct lookup_result result;
struct kpatch_dynrela *dynrelas;
@ -1469,6 +1512,11 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf,
relasec->sh.sh_entsize = sizeof(GElf_Rela);
relasec->sh.sh_addralign = 8;
/* lookup strings symbol */
strsym = find_symbol_by_name(&kelf->symbols, "__kpatch_strings");
if (!strsym)
ERROR("can't find strsym");
/* populate sections (reuse sec for iterator here) */
index = 0;
list_for_each_entry(sec, &kelf->sections, list) {
@ -1496,9 +1544,11 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf,
rela->sym->name, result.value, result.size);
/* dest filed in by rela entry below */
dynrelas[index].src = result.value + rela->addend;
dynrelas[index].src = result.value;
dynrelas[index].addend = rela->addend;
dynrelas[index].type = rela->type;
/* add rela to fill in dest field */
ALLOC_LINK(dynrela, &relasec->relas);
if (!sec->base->sym)
ERROR("expected bundled symbol");
@ -1507,6 +1557,13 @@ void kpatch_create_dynamic_rela_sections(struct kpatch_elf *kelf,
dynrela->addend = rela->offset;
dynrela->offset = index * sizeof(*dynrelas);
/* add rela to fill in name field */
ALLOC_LINK(dynrela, &relasec->relas);
dynrela->sym = strsym;
dynrela->type = R_X86_64_64;
dynrela->addend = offset_of_string(&kelf->strings, rela->sym->name);
dynrela->offset = index * sizeof(*dynrelas) + offsetof(struct kpatch_dynrela, name);
list_del(&rela->list);
free(rela);
@ -1545,6 +1602,78 @@ void kpatch_strip_unneeded_syms(struct kpatch_elf *kelf,
}
}
void kpatch_create_strings_elements(struct kpatch_elf *kelf)
{
struct section *sec;
struct symbol *sym;
/* create .kpatch.strings */
/* allocate section resources */
ALLOC_LINK(sec, &kelf->sections);
sec->name = ".kpatch.strings";
/* set data */
sec->data = malloc(sizeof(*sec->data));
if (!sec->data)
ERROR("malloc");
sec->data->d_type = ELF_T_BYTE;
/* set section header */
sec->sh.sh_type = SHT_PROGBITS;
sec->sh.sh_entsize = 1;
sec->sh.sh_addralign = 1;
sec->sh.sh_flags = SHF_ALLOC;
/* create __kpatch_strings symbol */
ALLOC_LINK(sym, &kelf->symbols);
sym->sec = sec;
sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_NOTYPE);
sym->type = STT_NOTYPE;
sym->bind = STB_LOCAL;
sym->name = "__kpatch_strings";
/* create .kpatch.strings section symbol (reuse sym variable) */
ALLOC_LINK(sym, &kelf->symbols);
sym->sec = sec;
sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION);
sym->type = STT_SECTION;
sym->bind = STB_LOCAL;
sym->name = ".kpatch.strings";
}
void kpatch_build_strings_section_data(struct kpatch_elf *kelf)
{
struct string *string;
struct section *sec;
int size;
char *strtab;
sec = find_section_by_name(&kelf->sections, ".kpatch.strings");
if (!sec)
ERROR("can't find .kpatch.strings");
/* determine size */
size = 0;
list_for_each_entry(string, &kelf->strings, list)
size += strlen(string->name) + 1;
/* allocate section resources */
strtab = malloc(size);
if (!strtab)
ERROR("malloc");
sec->data->d_buf = strtab;
sec->data->d_size = size;
/* populate strings section data */
list_for_each_entry(string, &kelf->strings, list) {
strcpy(strtab, string->name);
strtab += strlen(string->name) + 1;
}
}
void kpatch_rebuild_rela_section_data(struct section *sec)
{
struct rela *rela;
@ -1815,8 +1944,10 @@ int main(int argc, char *argv[])
break;
}
}
kpatch_create_strings_elements(kelf_out);
kpatch_create_patches_sections(kelf_out, vmlinux, hint);
kpatch_create_dynamic_rela_sections(kelf_out, vmlinux, hint);
kpatch_build_strings_section_data(kelf_out);
/*
* At this point, the set of output sections and symbols is