create-diff-object: fix relocations used for ZERO_PAGE(0)

On x86_64, GCC generates the following instruction to compute
'empty_zero_page - __START_KERNEL_map' (__phys_addr_nodebug(), used in
the implementation of ZERO_PAGE()):

    48 ba 00 00 00 00 00 00 00 00   movabs $0x0,%rdx
          R_X86_64_64  empty_zero_page+0x80000000

__START_KERNEL_map is 0xffffffff80000000.

However, the relocation addend becomes wrong in the patch module:

    48 ba 00 00 00 00 00 00 00 00   movabs $0x0,%rdx
          R_X86_64_64  empty_zero_page-0x80000000

Note the sign of the addend.

As a result, ZERO_PAGE(0) returns a wrong value in any function touched
by the patch, which may lead to memory corruption and difficult-to-debug
kernel crashes.

The cause is that 'struct rela' uses 'int' for the addend, which is not
enough to store such values. r_addend from Elf64_Rela is int64_t
(Elf64_Sxword) for that.

Let us use 'long' instead of 'int' for the addend in 'struct rela'.

v2:
* Moved 'addend' field after 'offset' in struct rela to facilitate
  structure packing (suggested by Kamalesh Babulal).

Fixes https://github.com/dynup/kpatch/issues/1064.

Signed-off-by: Evgenii Shatokhin <eshatokhin@virtuozzo.com>
This commit is contained in:
Evgenii Shatokhin 2020-01-17 18:32:11 +03:00
parent 34a45ba847
commit f5f5479614
3 changed files with 12 additions and 12 deletions

View File

@ -374,14 +374,14 @@ static int rela_equal(struct rela *rela1, struct rela *rela2)
*/ */
memcpy(&toc_data1, rela1->sym->sec->data->d_buf + rela1->addend, sizeof(toc_data1)); memcpy(&toc_data1, rela1->sym->sec->data->d_buf + rela1->addend, sizeof(toc_data1));
if (!toc_data1) if (!toc_data1)
ERROR(".toc entry not found %s + %x", rela1->sym->name, rela1->addend); ERROR(".toc entry not found %s + %lx", rela1->sym->name, rela1->addend);
} }
rela_toc2 = toc_rela(rela2); rela_toc2 = toc_rela(rela2);
if (!rela_toc2) { if (!rela_toc2) {
memcpy(&toc_data2, rela2->sym->sec->data->d_buf + rela2->addend, sizeof(toc_data2)); memcpy(&toc_data2, rela2->sym->sec->data->d_buf + rela2->addend, sizeof(toc_data2));
if (!toc_data2) if (!toc_data2)
ERROR(".toc entry not found %s + %x", rela2->sym->name, rela2->addend); ERROR(".toc entry not found %s + %lx", rela2->sym->name, rela2->addend);
} }
if (!rela_toc1 && !rela_toc2) if (!rela_toc1 && !rela_toc2)
@ -1369,7 +1369,7 @@ static void kpatch_replace_sections_syms(struct kpatch_elf *kelf)
rela->addend + add_off >= end) rela->addend + add_off >= end)
continue; continue;
log_debug("%s: replacing %s+%d reference with %s+%d\n", log_debug("%s: replacing %s+%ld reference with %s+%ld\n",
sec->name, sec->name,
rela->sym->name, rela->addend, rela->sym->name, rela->addend,
sym->name, rela->addend - start); sym->name, rela->addend - start);
@ -2070,7 +2070,7 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf,
if (is_dynamic_debug_symbol(key->sym)) if (is_dynamic_debug_symbol(key->sym))
continue; continue;
ERROR("Found a jump label at %s()+0x%x, using key %s. Jump labels aren't currently supported. Use static_key_enabled() instead.", ERROR("Found a jump label at %s()+0x%lx, using key %s. Jump labels aren't currently supported. Use static_key_enabled() instead.",
code->sym->name, code->addend, key->sym->name); code->sym->name, code->addend, key->sym->name);
continue; continue;
@ -2237,8 +2237,8 @@ static void kpatch_check_relocations(struct kpatch_elf *kelf)
list_for_each_entry(rela, &sec->relas, list) { list_for_each_entry(rela, &sec->relas, list) {
if (rela->sym->sec) { if (rela->sym->sec) {
sdata = rela->sym->sec->data; sdata = rela->sym->sec->data;
if (rela->addend > (int)sdata->d_size) { if (rela->addend > (long)sdata->d_size) {
ERROR("out-of-range relocation %s+%x in %s", rela->sym->sec->name, ERROR("out-of-range relocation %s+%lx in %s", rela->sym->sec->name,
rela->addend, sec->name); rela->addend, sec->name);
} }
} }

View File

@ -194,15 +194,15 @@ void kpatch_create_rela_list(struct kpatch_elf *kelf, struct section *sec)
(rela->sym->sec->sh.sh_flags & SHF_STRINGS)) { (rela->sym->sec->sh.sh_flags & SHF_STRINGS)) {
rela->string = rela->sym->sec->data->d_buf + rela->addend; rela->string = rela->sym->sec->data->d_buf + rela->addend;
if (!rela->string) if (!rela->string)
ERROR("could not lookup rela string for %s+%d", ERROR("could not lookup rela string for %s+%ld",
rela->sym->name, rela->addend); rela->sym->name, rela->addend);
} }
if (skip) if (skip)
continue; continue;
log_debug("offset %d, type %d, %s %s %d", rela->offset, log_debug("offset %d, type %d, %s %s %ld", rela->offset,
rela->type, rela->sym->name, rela->type, rela->sym->name,
(rela->addend < 0)?"-":"+", abs(rela->addend)); (rela->addend < 0)?"-":"+", labs(rela->addend));
if (rela->string) if (rela->string)
log_debug(" (string = %s)", rela->string); log_debug(" (string = %s)", rela->string);
log_debug("\n"); log_debug("\n");
@ -403,11 +403,11 @@ void kpatch_dump_kelf(struct kpatch_elf *kelf)
goto next; goto next;
printf("rela section expansion\n"); printf("rela section expansion\n");
list_for_each_entry(rela, &sec->relas, list) { list_for_each_entry(rela, &sec->relas, list) {
printf("sym %d, offset %d, type %d, %s %s %d\n", printf("sym %d, offset %d, type %d, %s %s %ld\n",
rela->sym->index, rela->offset, rela->sym->index, rela->offset,
rela->type, rela->sym->name, rela->type, rela->sym->name,
(rela->addend < 0)?"-":"+", (rela->addend < 0)?"-":"+",
abs(rela->addend)); labs(rela->addend));
} }
} else { } else {
if (sec->sym) if (sec->sym)

View File

@ -90,8 +90,8 @@ struct rela {
GElf_Rela rela; GElf_Rela rela;
struct symbol *sym; struct symbol *sym;
unsigned int type; unsigned int type;
int addend;
unsigned int offset; unsigned int offset;
long addend;
char *string; char *string;
bool need_dynrela; bool need_dynrela;
}; };