Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 1 | /* |
| 2 | * jump label x86 support |
| 3 | * |
| 4 | * Copyright (C) 2009 Jason Baron <jbaron@redhat.com> |
| 5 | * |
| 6 | */ |
| 7 | #include <linux/jump_label.h> |
| 8 | #include <linux/memory.h> |
| 9 | #include <linux/uaccess.h> |
| 10 | #include <linux/module.h> |
| 11 | #include <linux/list.h> |
| 12 | #include <linux/jhash.h> |
| 13 | #include <linux/cpu.h> |
| 14 | #include <asm/kprobes.h> |
| 15 | #include <asm/alternative.h> |
Andy Lutomirski | 35de5b0 | 2016-04-26 12:23:24 -0700 | [diff] [blame] | 16 | #include <asm/text-patching.h> |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 17 | |
| 18 | #ifdef HAVE_JUMP_LABEL |
| 19 | |
| 20 | union jump_code_union { |
| 21 | char code[JUMP_LABEL_NOP_SIZE]; |
| 22 | struct { |
| 23 | char jump; |
| 24 | int offset; |
| 25 | } __attribute__((packed)); |
| 26 | }; |
| 27 | |
Steven Rostedt | fb40d7a | 2012-02-01 09:59:24 -0500 | [diff] [blame] | 28 | static void bug_at(unsigned char *ip, int line) |
| 29 | { |
| 30 | /* |
| 31 | * The location is not an op that we were expecting. |
| 32 | * Something went wrong. Crash the box, as something could be |
| 33 | * corrupting the kernel. |
| 34 | */ |
Andy Shevchenko | 6e03f66 | 2017-01-10 18:43:54 +0200 | [diff] [blame] | 35 | pr_crit("jump_label: Fatal kernel bug, unexpected op at %pS [%p] (%5ph) %d\n", ip, ip, ip, line); |
Steven Rostedt | fb40d7a | 2012-02-01 09:59:24 -0500 | [diff] [blame] | 36 | BUG(); |
| 37 | } |
| 38 | |
Jeremy Fitzhardinge | e71a5be | 2011-09-29 11:11:09 -0700 | [diff] [blame] | 39 | static void __jump_label_transform(struct jump_entry *entry, |
| 40 | enum jump_label_type type, |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 41 | void *(*poker)(void *, const void *, size_t), |
| 42 | int init) |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 43 | { |
| 44 | union jump_code_union code; |
Hannes Frederic Sowa | a8fab07 | 2013-10-19 21:48:54 +0200 | [diff] [blame] | 45 | const unsigned char default_nop[] = { STATIC_KEY_INIT_NOP }; |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 46 | const unsigned char *ideal_nop = ideal_nops[NOP_ATOMIC5]; |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 47 | |
Peter Zijlstra | 76b235c | 2015-07-24 14:45:44 +0200 | [diff] [blame] | 48 | if (type == JUMP_LABEL_JMP) { |
Hannes Frederic Sowa | a8fab07 | 2013-10-19 21:48:54 +0200 | [diff] [blame] | 49 | if (init) { |
| 50 | /* |
| 51 | * Jump label is enabled for the first time. |
| 52 | * So we expect a default_nop... |
| 53 | */ |
| 54 | if (unlikely(memcmp((void *)entry->code, default_nop, 5) |
| 55 | != 0)) |
| 56 | bug_at((void *)entry->code, __LINE__); |
| 57 | } else { |
| 58 | /* |
| 59 | * ...otherwise expect an ideal_nop. Otherwise |
| 60 | * something went horribly wrong. |
| 61 | */ |
| 62 | if (unlikely(memcmp((void *)entry->code, ideal_nop, 5) |
| 63 | != 0)) |
| 64 | bug_at((void *)entry->code, __LINE__); |
| 65 | } |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 66 | |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 67 | code.jump = 0xe9; |
| 68 | code.offset = entry->target - |
| 69 | (entry->code + JUMP_LABEL_NOP_SIZE); |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 70 | } else { |
| 71 | /* |
| 72 | * We are disabling this jump label. If it is not what |
| 73 | * we think it is, then something must have gone wrong. |
| 74 | * If this is the first initialization call, then we |
| 75 | * are converting the default nop to the ideal nop. |
| 76 | */ |
| 77 | if (init) { |
Steven Rostedt | fb40d7a | 2012-02-01 09:59:24 -0500 | [diff] [blame] | 78 | if (unlikely(memcmp((void *)entry->code, default_nop, 5) != 0)) |
| 79 | bug_at((void *)entry->code, __LINE__); |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 80 | } else { |
| 81 | code.jump = 0xe9; |
| 82 | code.offset = entry->target - |
| 83 | (entry->code + JUMP_LABEL_NOP_SIZE); |
Steven Rostedt | fb40d7a | 2012-02-01 09:59:24 -0500 | [diff] [blame] | 84 | if (unlikely(memcmp((void *)entry->code, &code, 5) != 0)) |
| 85 | bug_at((void *)entry->code, __LINE__); |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 86 | } |
H. Peter Anvin | dc326fc | 2011-04-18 15:19:51 -0700 | [diff] [blame] | 87 | memcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE); |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 88 | } |
Jeremy Fitzhardinge | e71a5be | 2011-09-29 11:11:09 -0700 | [diff] [blame] | 89 | |
Jiri Kosina | 51b2c07 | 2013-07-12 11:22:09 +0200 | [diff] [blame] | 90 | /* |
| 91 | * Make text_poke_bp() a default fallback poker. |
| 92 | * |
| 93 | * At the time the change is being done, just ignore whether we |
| 94 | * are doing nop -> jump or jump -> nop transition, and assume |
| 95 | * always nop being the 'currently valid' instruction |
| 96 | * |
| 97 | */ |
| 98 | if (poker) |
| 99 | (*poker)((void *)entry->code, &code, JUMP_LABEL_NOP_SIZE); |
| 100 | else |
| 101 | text_poke_bp((void *)entry->code, &code, JUMP_LABEL_NOP_SIZE, |
| 102 | (void *)entry->code + JUMP_LABEL_NOP_SIZE); |
Jeremy Fitzhardinge | e71a5be | 2011-09-29 11:11:09 -0700 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | void arch_jump_label_transform(struct jump_entry *entry, |
| 106 | enum jump_label_type type) |
| 107 | { |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 108 | get_online_cpus(); |
| 109 | mutex_lock(&text_mutex); |
Linus Torvalds | 442e097 | 2013-09-10 19:43:23 -0700 | [diff] [blame] | 110 | __jump_label_transform(entry, type, NULL, 0); |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 111 | mutex_unlock(&text_mutex); |
| 112 | put_online_cpus(); |
| 113 | } |
| 114 | |
Steven Rostedt | 11570da | 2012-01-26 18:16:15 -0500 | [diff] [blame] | 115 | static enum { |
| 116 | JL_STATE_START, |
| 117 | JL_STATE_NO_UPDATE, |
| 118 | JL_STATE_UPDATE, |
| 119 | } jlstate __initdata_or_module = JL_STATE_START; |
| 120 | |
Peter Zijlstra | 9cdbe1c | 2011-12-06 17:27:29 +0100 | [diff] [blame] | 121 | __init_or_module void arch_jump_label_transform_static(struct jump_entry *entry, |
Jeremy Fitzhardinge | e71a5be | 2011-09-29 11:11:09 -0700 | [diff] [blame] | 122 | enum jump_label_type type) |
| 123 | { |
Steven Rostedt | 11570da | 2012-01-26 18:16:15 -0500 | [diff] [blame] | 124 | /* |
| 125 | * This function is called at boot up and when modules are |
| 126 | * first loaded. Check if the default nop, the one that is |
| 127 | * inserted at compile time, is the ideal nop. If it is, then |
| 128 | * we do not need to update the nop, and we can leave it as is. |
| 129 | * If it is not, then we need to update the nop to the ideal nop. |
| 130 | */ |
| 131 | if (jlstate == JL_STATE_START) { |
| 132 | const unsigned char default_nop[] = { STATIC_KEY_INIT_NOP }; |
| 133 | const unsigned char *ideal_nop = ideal_nops[NOP_ATOMIC5]; |
| 134 | |
| 135 | if (memcmp(ideal_nop, default_nop, 5) != 0) |
| 136 | jlstate = JL_STATE_UPDATE; |
| 137 | else |
| 138 | jlstate = JL_STATE_NO_UPDATE; |
| 139 | } |
| 140 | if (jlstate == JL_STATE_UPDATE) |
Steven Rostedt | 9c85f3b | 2012-01-26 18:38:07 -0500 | [diff] [blame] | 141 | __jump_label_transform(entry, type, text_poke_early, 1); |
Jeremy Fitzhardinge | e71a5be | 2011-09-29 11:11:09 -0700 | [diff] [blame] | 142 | } |
| 143 | |
Jason Baron | d9f5ab7 | 2010-09-17 11:09:22 -0400 | [diff] [blame] | 144 | #endif |