1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2d9f5ab7bSJason Baron /* 3d9f5ab7bSJason Baron * jump label x86 support 4d9f5ab7bSJason Baron * 5d9f5ab7bSJason Baron * Copyright (C) 2009 Jason Baron <jbaron@redhat.com> 6d9f5ab7bSJason Baron * 7d9f5ab7bSJason Baron */ 8d9f5ab7bSJason Baron #include <linux/jump_label.h> 9d9f5ab7bSJason Baron #include <linux/memory.h> 10d9f5ab7bSJason Baron #include <linux/uaccess.h> 11d9f5ab7bSJason Baron #include <linux/module.h> 12d9f5ab7bSJason Baron #include <linux/list.h> 13d9f5ab7bSJason Baron #include <linux/jhash.h> 14d9f5ab7bSJason Baron #include <linux/cpu.h> 15d9f5ab7bSJason Baron #include <asm/kprobes.h> 16d9f5ab7bSJason Baron #include <asm/alternative.h> 1735de5b06SAndy Lutomirski #include <asm/text-patching.h> 18e7bf1ba9SPeter Zijlstra #include <asm/insn.h> 19d9f5ab7bSJason Baron 20fa5e5dc3SPeter Zijlstra int arch_jump_entry_size(struct jump_entry *entry) 21fa5e5dc3SPeter Zijlstra { 22e7bf1ba9SPeter Zijlstra struct insn insn = {}; 23e7bf1ba9SPeter Zijlstra 24e7bf1ba9SPeter Zijlstra insn_decode_kernel(&insn, (void *)jump_entry_code(entry)); 25e7bf1ba9SPeter Zijlstra BUG_ON(insn.length != 2 && insn.length != 5); 26e7bf1ba9SPeter Zijlstra 27e7bf1ba9SPeter Zijlstra return insn.length; 28fa5e5dc3SPeter Zijlstra } 29fa5e5dc3SPeter Zijlstra 30001951beSPeter Zijlstra struct jump_label_patch { 31001951beSPeter Zijlstra const void *code; 32001951beSPeter Zijlstra int size; 33001951beSPeter Zijlstra }; 34001951beSPeter Zijlstra 35001951beSPeter Zijlstra static struct jump_label_patch 36001951beSPeter Zijlstra __jump_label_patch(struct jump_entry *entry, enum jump_label_type type) 37d9f5ab7bSJason Baron { 38001951beSPeter Zijlstra const void *expect, *code, *nop; 3963f62addSPeter Zijlstra const void *addr, *dest; 40001951beSPeter Zijlstra int size; 419fc0f798SArd Biesheuvel 4263f62addSPeter Zijlstra addr = (void *)jump_entry_code(entry); 4363f62addSPeter Zijlstra dest = (void *)jump_entry_target(entry); 4418cbc8beSPeter Zijlstra 45001951beSPeter Zijlstra size = arch_jump_entry_size(entry); 46001951beSPeter Zijlstra switch (size) { 47001951beSPeter Zijlstra case JMP8_INSN_SIZE: 48001951beSPeter Zijlstra code = text_gen_insn(JMP8_INSN_OPCODE, addr, dest); 49001951beSPeter Zijlstra nop = x86_nops[size]; 50001951beSPeter Zijlstra break; 51001951beSPeter Zijlstra 52001951beSPeter Zijlstra case JMP32_INSN_SIZE: 5363f62addSPeter Zijlstra code = text_gen_insn(JMP32_INSN_OPCODE, addr, dest); 54001951beSPeter Zijlstra nop = x86_nops[size]; 55001951beSPeter Zijlstra break; 56001951beSPeter Zijlstra 57001951beSPeter Zijlstra default: BUG(); 58001951beSPeter Zijlstra } 59d9f5ab7bSJason Baron 60f9510fa9SPeter Zijlstra if (type == JUMP_LABEL_JMP) 61001951beSPeter Zijlstra expect = nop; 62f9510fa9SPeter Zijlstra else 63f9510fa9SPeter Zijlstra expect = code; 64e71a5be1SJeremy Fitzhardinge 65001951beSPeter Zijlstra if (memcmp(addr, expect, size)) { 66f9510fa9SPeter Zijlstra /* 67f9510fa9SPeter Zijlstra * The location is not an op that we were expecting. 68f9510fa9SPeter Zijlstra * Something went wrong. Crash the box, as something could be 69f9510fa9SPeter Zijlstra * corrupting the kernel. 70f9510fa9SPeter Zijlstra */ 71001951beSPeter Zijlstra pr_crit("jump_label: Fatal kernel bug, unexpected op at %pS [%p] (%5ph != %5ph)) size:%d type:%d\n", 72001951beSPeter Zijlstra addr, addr, addr, expect, size, type); 73f9510fa9SPeter Zijlstra BUG(); 74f9510fa9SPeter Zijlstra } 759fc0f798SArd Biesheuvel 764cc6620bSDaniel Bristot de Oliveira if (type == JUMP_LABEL_NOP) 77001951beSPeter Zijlstra code = nop; 7818cbc8beSPeter Zijlstra 79001951beSPeter Zijlstra return (struct jump_label_patch){.code = code, .size = size}; 804cc6620bSDaniel Bristot de Oliveira } 814cc6620bSDaniel Bristot de Oliveira 82*e48a12e5SIngo Molnar static __always_inline void 83*e48a12e5SIngo Molnar __jump_label_transform(struct jump_entry *entry, 844cc6620bSDaniel Bristot de Oliveira enum jump_label_type type, 854cc6620bSDaniel Bristot de Oliveira int init) 864cc6620bSDaniel Bristot de Oliveira { 87001951beSPeter Zijlstra const struct jump_label_patch jlp = __jump_label_patch(entry, type); 884cc6620bSDaniel Bristot de Oliveira 8951b2c07bSJiri Kosina /* 90bb0a008dSNadav Amit * As long as only a single processor is running and the code is still 91bb0a008dSNadav Amit * not marked as RO, text_poke_early() can be used; Checking that 92bb0a008dSNadav Amit * system_state is SYSTEM_BOOTING guarantees it. It will be set to 93bb0a008dSNadav Amit * SYSTEM_SCHEDULING before other cores are awaken and before the 94bb0a008dSNadav Amit * code is write-protected. 9551b2c07bSJiri Kosina * 9651b2c07bSJiri Kosina * At the time the change is being done, just ignore whether we 9751b2c07bSJiri Kosina * are doing nop -> jump or jump -> nop transition, and assume 9851b2c07bSJiri Kosina * always nop being the 'currently valid' instruction 9951b2c07bSJiri Kosina */ 100bb0a008dSNadav Amit if (init || system_state == SYSTEM_BOOTING) { 101001951beSPeter Zijlstra text_poke_early((void *)jump_entry_code(entry), jlp.code, jlp.size); 1029fc0f798SArd Biesheuvel return; 1039fc0f798SArd Biesheuvel } 1049fc0f798SArd Biesheuvel 105001951beSPeter Zijlstra text_poke_bp((void *)jump_entry_code(entry), jlp.code, jlp.size, NULL); 10618cbc8beSPeter Zijlstra } 10718cbc8beSPeter Zijlstra 10818cbc8beSPeter Zijlstra static void __ref jump_label_transform(struct jump_entry *entry, 10918cbc8beSPeter Zijlstra enum jump_label_type type, 11018cbc8beSPeter Zijlstra int init) 11118cbc8beSPeter Zijlstra { 11218cbc8beSPeter Zijlstra mutex_lock(&text_mutex); 11318cbc8beSPeter Zijlstra __jump_label_transform(entry, type, init); 11418cbc8beSPeter Zijlstra mutex_unlock(&text_mutex); 115e71a5be1SJeremy Fitzhardinge } 116e71a5be1SJeremy Fitzhardinge 117e71a5be1SJeremy Fitzhardinge void arch_jump_label_transform(struct jump_entry *entry, 118e71a5be1SJeremy Fitzhardinge enum jump_label_type type) 119e71a5be1SJeremy Fitzhardinge { 12018cbc8beSPeter Zijlstra jump_label_transform(entry, type, 0); 121d9f5ab7bSJason Baron } 122d9f5ab7bSJason Baron 123ba54f0c3SDaniel Bristot de Oliveira bool arch_jump_label_transform_queue(struct jump_entry *entry, 124ba54f0c3SDaniel Bristot de Oliveira enum jump_label_type type) 125ba54f0c3SDaniel Bristot de Oliveira { 126001951beSPeter Zijlstra struct jump_label_patch jlp; 127ba54f0c3SDaniel Bristot de Oliveira 128ba54f0c3SDaniel Bristot de Oliveira if (system_state == SYSTEM_BOOTING) { 129ba54f0c3SDaniel Bristot de Oliveira /* 130ba54f0c3SDaniel Bristot de Oliveira * Fallback to the non-batching mode. 131ba54f0c3SDaniel Bristot de Oliveira */ 132ba54f0c3SDaniel Bristot de Oliveira arch_jump_label_transform(entry, type); 133ba54f0c3SDaniel Bristot de Oliveira return true; 134ba54f0c3SDaniel Bristot de Oliveira } 135ba54f0c3SDaniel Bristot de Oliveira 13618cbc8beSPeter Zijlstra mutex_lock(&text_mutex); 137001951beSPeter Zijlstra jlp = __jump_label_patch(entry, type); 138001951beSPeter Zijlstra text_poke_queue((void *)jump_entry_code(entry), jlp.code, jlp.size, NULL); 13918cbc8beSPeter Zijlstra mutex_unlock(&text_mutex); 140ba54f0c3SDaniel Bristot de Oliveira return true; 141ba54f0c3SDaniel Bristot de Oliveira } 142ba54f0c3SDaniel Bristot de Oliveira 143ba54f0c3SDaniel Bristot de Oliveira void arch_jump_label_transform_apply(void) 144ba54f0c3SDaniel Bristot de Oliveira { 145ba54f0c3SDaniel Bristot de Oliveira mutex_lock(&text_mutex); 14618cbc8beSPeter Zijlstra text_poke_finish(); 147ba54f0c3SDaniel Bristot de Oliveira mutex_unlock(&text_mutex); 148ba54f0c3SDaniel Bristot de Oliveira } 149ba54f0c3SDaniel Bristot de Oliveira 15011570da1SSteven Rostedt static enum { 15111570da1SSteven Rostedt JL_STATE_START, 15211570da1SSteven Rostedt JL_STATE_NO_UPDATE, 15311570da1SSteven Rostedt JL_STATE_UPDATE, 15411570da1SSteven Rostedt } jlstate __initdata_or_module = JL_STATE_START; 15511570da1SSteven Rostedt 1569cdbe1cbSPeter Zijlstra __init_or_module void arch_jump_label_transform_static(struct jump_entry *entry, 157e71a5be1SJeremy Fitzhardinge enum jump_label_type type) 158e71a5be1SJeremy Fitzhardinge { 15911570da1SSteven Rostedt if (jlstate == JL_STATE_UPDATE) 16018cbc8beSPeter Zijlstra jump_label_transform(entry, type, 1); 161e71a5be1SJeremy Fitzhardinge } 162