create-diff-object: fix use after free in kpatch-check-relocations()

Building data-read-mostly.patch on rhel-9.0-beta for ppc64le leads to a
segmentation fault:

    Program received signal SIGSEGV, Segmentation fault.
    kpatch_check_relocations (kelf=0x10040490) at create-diff-object.c:2571
    2571                                    sdata = rela->sym->sec->data;
    (gdb) bt
    (gdb) p rela->sym->sec->data
    Cannot access memory at address 0x160000007e

Valgrind narrows the problem down to invalid reads through rela->sym in
kpatch-check-relocations().

The culprits are kpatch_create_intermediate_sections(), which marks
symbols referenced by rela sections that are now dynrelas to be
stripped, and kpatch_strip_unneeded_syms(), which removes and frees
them.

The problem with the symbol stripping is that multiple relas may
reference the same ELF symbol.  If any remaining relocation references a
shared symbol, we must keep it.

Replace the symbol->strip boolean with an enumeration:

  SYMBOL_DEFAULT - initial value, symbol usage unknown
  SYMBOL_USED    - symbol is definitely used by a rela
  SYMBOL_STRIP   - symbol was only referenced by dynrela(s)

Allow transitions from SYMBOL_DEFAULT to SYMBOL_* and SYMBOL_STRIP to
SYMBOL_USED, but _not_ SYMBOL_USED to SYMBOL_*.

Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
This commit is contained in:
Joe Lawrence 2021-08-22 13:16:52 -04:00
parent 6de60e79b7
commit ef0ce9715a
3 changed files with 15 additions and 7 deletions

View File

@ -1940,7 +1940,7 @@ static void kpatch_migrate_included_elements(struct kpatch_elf *kelf, struct kpa
list_del(&sym->list); list_del(&sym->list);
list_add_tail(&sym->list, &out->symbols); list_add_tail(&sym->list, &out->symbols);
sym->index = 0; sym->index = 0;
sym->strip = 0; sym->strip = SYMBOL_DEFAULT;
if (sym->sec && !sym->sec->include) if (sym->sec && !sym->sec->include)
/* break link to non-included section */ /* break link to non-included section */
sym->sec = NULL; sym->sec = NULL;
@ -3234,8 +3234,10 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
special = true; special = true;
list_for_each_entry_safe(rela, safe, &sec->relas, list) { list_for_each_entry_safe(rela, safe, &sec->relas, list) {
if (!rela->need_dynrela) if (!rela->need_dynrela) {
rela->sym->strip = SYMBOL_USED;
continue; continue;
}
/* /*
* Starting with Linux 5.8, .klp.arch sections are no * Starting with Linux 5.8, .klp.arch sections are no
@ -3332,8 +3334,8 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf,
* later (for example, they may have relocations * later (for example, they may have relocations
* of their own which should be processed). * of their own which should be processed).
*/ */
if (!rela->sym->sec) if (!rela->sym->sec && rela->sym->strip != SYMBOL_USED)
rela->sym->strip = 1; rela->sym->strip = SYMBOL_STRIP;
list_del(&rela->list); list_del(&rela->list);
free(rela); free(rela);
@ -3519,7 +3521,7 @@ static void kpatch_strip_unneeded_syms(struct kpatch_elf *kelf,
struct symbol *sym, *safe; struct symbol *sym, *safe;
list_for_each_entry_safe(sym, safe, &kelf->symbols, list) { list_for_each_entry_safe(sym, safe, &kelf->symbols, list) {
if (sym->strip) { if (sym->strip == SYMBOL_STRIP) {
list_del(&sym->list); list_del(&sym->list);
free(sym); free(sym);
} }

View File

@ -67,6 +67,12 @@ struct section {
}; };
}; };
enum symbol_strip {
SYMBOL_DEFAULT,
SYMBOL_USED,
SYMBOL_STRIP,
};
struct symbol { struct symbol {
struct list_head list; struct list_head list;
struct symbol *twin; struct symbol *twin;
@ -82,7 +88,7 @@ struct symbol {
enum status status; enum status status;
union { union {
int include; /* used in the patched elf */ int include; /* used in the patched elf */
int strip; /* used in the output elf */ enum symbol_strip strip; /* used in the output elf */
}; };
int has_func_profiling; int has_func_profiling;
}; };

@ -1 +1 @@
Subproject commit 0d75b8137a08d62d89acf3358df8f44b1b86ff7a Subproject commit 365ce3af2ba7258d43982028b22d148f5c4686ac