From 4f4870dd058f3f34b0c4c8e9504c0b52b37165ee Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 29 Jan 2019 13:08:21 -0600 Subject: [PATCH] create-diff-object: Don't allow jump labels Create an error if a patched function uses a jump label. We need this until upstream livepatch supports them with a .klp.arch section. Fixes #946. Signed-off-by: Josh Poimboeuf --- kpatch-build/create-diff-object.c | 56 +++++++++++++++++++++++++++++++ kpatch-build/kpatch-build | 25 +++++++++++--- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index 6bf2b17..cfb5bbd 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -1773,6 +1773,21 @@ static int ex_table_group_size(struct kpatch_elf *kelf, int offset) return size; } +static int jump_table_group_size(struct kpatch_elf *kelf, int offset) +{ + static int size = 0; + char *str; + + if (!size) { + str = getenv("JUMP_STRUCT_SIZE"); + if (!str) + ERROR("JUMP_STRUCT_SIZE not set"); + size = atoi(str); + } + + return size; +} + #ifdef __x86_64__ static int parainstructions_group_size(struct kpatch_elf *kelf, int offset) { @@ -1897,6 +1912,10 @@ static struct special_section special_sections[] = { .name = "__ex_table", /* must come after .fixup */ .group_size = ex_table_group_size, }, + { + .name = "__jump_table", + .group_size = jump_table_group_size, + }, #ifdef __x86_64__ { .name = ".smp_locks", @@ -1994,6 +2013,7 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf, struct rela *rela, *safe; char *src, *dest; unsigned int group_size, src_offset, dest_offset, include; + int jump_table = !strcmp(special->name, "__jump_table"); LIST_HEAD(newrelas); @@ -2037,6 +2057,42 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf, if (special->unsupported) DIFF_FATAL("unsupported reference to special section %s", sec->base->name); + /* + * Jump labels (aka static keys or static branches) aren't + * actually supported for the time being. Warn on all + * non-tracepoint jump labels when they occur in a replacement + * function. An inert tracepoint is harmless enough, but a + * broken static key can cause unexpected behavior. + * + * Here we hard-code knowledge about the contents of the + * jump_label struct. It has three fields: code, target, and + * key. + */ + if (jump_table) { + struct rela *code, *key; + int i = 0; + + list_for_each_entry(rela, &sec->relas, list) { + if (rela->offset >= src_offset && + rela->offset < src_offset + group_size) { + if (i == 0) + code = rela; + else if (i == 2) + key = rela; + i++; + } + } + + if (i != 3) + ERROR("BUG: __jump_table has an unexpected format"); + + if (strncmp(key->sym->name, "__tracepoint_", 13)) + ERROR("Found a jump label at %s()+0x%x, using key %s. Jump labels aren't currently supported. Use static_key_enabled() instead.", + code->sym->name, code->addend, key->sym->name); + + continue; + } + /* * Copy all relas in the group. It's possible that the relas * aren't sorted (e.g. .rela.fixup), so go through the entire diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index 84d502b..bacd4c2 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -227,33 +227,40 @@ gcc_version_check() { } find_special_section_data_ppc64le() { + + [[ "$CONFIG_JUMP_LABEL" -eq 0 ]] && AWK_OPTIONS="-vskip_j=1" + SPECIAL_VARS="$(readelf -wi "$VMLINUX" | gawk --non-decimal-data ' - BEGIN { f = b = e = 0 } + BEGIN { f = b = e = j = 0 } # Set state if name matches f == 0 && /DW_AT_name.* fixup_entry[[:space:]]*$/ {f = 1; next} b == 0 && /DW_AT_name.* bug_entry[[:space:]]*$/ {b = 1; next} e == 0 && /DW_AT_name.* exception_table_entry[[:space:]]*$/ {e = 1; next} + j == 0 && /DW_AT_name.* jump_entry[[:space:]]*$/ {j = 1; next} # Reset state unless this abbrev describes the struct size f == 1 && !/DW_AT_byte_size/ { f = 0; next } b == 1 && !/DW_AT_byte_size/ { b = 0; next } e == 1 && !/DW_AT_byte_size/ { e = 0; next } + j == 1 && !/DW_AT_byte_size/ { j = 0; next } # Now that we know the size, stop parsing for it f == 1 {printf("export FIXUP_STRUCT_SIZE=%d\n", $4); f = 2} b == 1 {printf("export BUG_STRUCT_SIZE=%d\n", $4); b = 2} e == 1 {printf("export EX_STRUCT_SIZE=%d\n", $4); e = 2} + j == 1 {printf("export JUMP_STRUCT_SIZE=%d\n", $4); j = 2} # Bail out once we have everything - f == 2 && b == 2 && e == 2 {exit}')" + f == 2 && b == 2 && e == 2 && (j == 2 || skip_j) {exit}')" [[ -n "$SPECIAL_VARS" ]] && eval "$SPECIAL_VARS" [[ -z "$FIXUP_STRUCT_SIZE" ]] && die "can't find special struct fixup_entry size" [[ -z "$BUG_STRUCT_SIZE" ]] && die "can't find special struct bug_entry size" [[ -z "$EX_STRUCT_SIZE" ]] && die "can't find special struct exception_table_entry size" + [[ -z "$JUMP_STRUCT_SIZE" && "$CONFIG_JUMP_LABEL" -ne 0 ]] && die "can't find special struct jump_entry size" return } @@ -266,12 +273,13 @@ find_special_section_data() { [[ "$CONFIG_PARAVIRT" -eq 0 ]] && AWK_OPTIONS="-vskip_p=1" [[ "$CONFIG_UNWINDER_ORC" -eq 0 ]] && AWK_OPTIONS="$AWK_OPTIONS -vskip_o=1" + [[ "$CONFIG_JUMP_LABEL" -eq 0 ]] && AWK_OPTIONS="$AWK_OPTIONS -vskip_j=1" # If $AWK_OPTIONS are blank gawk would treat "" as a blank script # shellcheck disable=SC2086 SPECIAL_VARS="$(readelf -wi "$VMLINUX" | gawk --non-decimal-data $AWK_OPTIONS ' - BEGIN { a = b = p = e = o = 0 } + BEGIN { a = b = p = e = o = j = 0 } # Set state if name matches a == 0 && /DW_AT_name.* alt_instr[[:space:]]*$/ {a = 1; next} @@ -279,6 +287,7 @@ find_special_section_data() { p == 0 && /DW_AT_name.* paravirt_patch_site[[:space:]]*$/ {p = 1; next} e == 0 && /DW_AT_name.* exception_table_entry[[:space:]]*$/ {e = 1; next} o == 0 && /DW_AT_name.* orc_entry[[:space:]]*$/ {o = 1; next} + j == 0 && /DW_AT_name.* jump_entry[[:space:]]*$/ {j = 1; next} # Reset state unless this abbrev describes the struct size a == 1 && !/DW_AT_byte_size/ { a = 0; next } @@ -286,6 +295,7 @@ find_special_section_data() { p == 1 && !/DW_AT_byte_size/ { p = 0; next } e == 1 && !/DW_AT_byte_size/ { e = 0; next } o == 1 && !/DW_AT_byte_size/ { o = 0; next } + j == 1 && !/DW_AT_byte_size/ { j = 0; next } # Now that we know the size, stop parsing for it a == 1 {printf("export ALT_STRUCT_SIZE=%d\n", $4); a = 2} @@ -293,9 +303,10 @@ find_special_section_data() { p == 1 {printf("export PARA_STRUCT_SIZE=%d\n", $4); p = 2} e == 1 {printf("export EX_STRUCT_SIZE=%d\n", $4); e = 2} o == 1 {printf("export ORC_STRUCT_SIZE=%d\n", $4); o = 2} + j == 1 {printf("export JUMP_STRUCT_SIZE=%d\n", $4); j = 2} # Bail out once we have everything - a == 2 && b == 2 && (p == 2 || skip_p) && e == 2 && (o == 2 || skip_o) {exit}')" + a == 2 && b == 2 && (p == 2 || skip_p) && e == 2 && (o == 2 || skip_o) && (j == 2 || skip_j) {exit}')" [[ -n "$SPECIAL_VARS" ]] && eval "$SPECIAL_VARS" @@ -304,6 +315,7 @@ find_special_section_data() { [[ -z "$EX_STRUCT_SIZE" ]] && die "can't find special struct paravirt_patch_site size" [[ -z "$PARA_STRUCT_SIZE" && "$CONFIG_PARAVIRT" -ne 0 ]] && die "can't find special struct paravirt_patch_site size" [[ -z "$ORC_STRUCT_SIZE" && "$CONFIG_UNWINDER_ORC" -ne 0 ]] && die "can't find special struct orc_entry size" + [[ -z "$JUMP_STRUCT_SIZE" && "$CONFIG_JUMP_LABEL" -ne 0 ]] && die "can't find special struct jump_entry size" return } @@ -714,6 +726,11 @@ if grep -q "CONFIG_UNWINDER_ORC=y" "$CONFIGFILE"; then else CONFIG_UNWINDER_ORC=0 fi +if grep -q "CONFIG_JUMP_LABEL=y" "$CONFIGFILE"; then + CONFIG_JUMP_LABEL=1 +else + CONFIG_JUMP_LABEL=0 +fi # unsupported kernel option checking grep -q "CONFIG_DEBUG_INFO_SPLIT=y" "$CONFIGFILE" && die "kernel option 'CONFIG_DEBUG_INFO_SPLIT' not supported"