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> 18*e7bf1ba9SPeter Zijlstra #include <asm/insn.h> 19d9f5ab7bSJason Baron 20fa5e5dc3SPeter Zijlstra #define JUMP_LABEL_NOP_SIZE JMP32_INSN_SIZE 21fa5e5dc3SPeter Zijlstra 22fa5e5dc3SPeter Zijlstra int arch_jump_entry_size(struct jump_entry *entry) 23fa5e5dc3SPeter Zijlstra { 24*e7bf1ba9SPeter Zijlstra struct insn insn = {}; 25*e7bf1ba9SPeter Zijlstra 26*e7bf1ba9SPeter Zijlstra insn_decode_kernel(&insn, (void *)jump_entry_code(entry)); 27*e7bf1ba9SPeter Zijlstra BUG_ON(insn.length != 2 && insn.length != 5); 28*e7bf1ba9SPeter Zijlstra 29*e7bf1ba9SPeter Zijlstra return insn.length; 30fa5e5dc3SPeter Zijlstra } 31fa5e5dc3SPeter Zijlstra 32001951beSPeter Zijlstra struct jump_label_patch { 33001951beSPeter Zijlstra const void *code; 34001951beSPeter Zijlstra int size; 35001951beSPeter Zijlstra }; 36001951beSPeter Zijlstra 37001951beSPeter Zijlstra static struct jump_label_patch 38001951beSPeter Zijlstra __jump_label_patch(struct jump_entry *entry, enum jump_label_type type) 39d9f5ab7bSJason Baron { 40001951beSPeter Zijlstra const void *expect, *code, *nop; 4163f62addSPeter Zijlstra const void *addr, *dest; 42001951beSPeter Zijlstra int size; 439fc0f798SArd Biesheuvel 4463f62addSPeter Zijlstra addr = (void *)jump_entry_code(entry); 4563f62addSPeter Zijlstra dest = (void *)jump_entry_target(entry); 4618cbc8beSPeter Zijlstra 47001951beSPeter Zijlstra size = arch_jump_entry_size(entry); 48001951beSPeter Zijlstra switch (size) { 49001951beSPeter Zijlstra case JMP8_INSN_SIZE: 50001951beSPeter Zijlstra code = text_gen_insn(JMP8_INSN_OPCODE, addr, dest); 51001951beSPeter Zijlstra nop = x86_nops[size]; 52001951beSPeter Zijlstra break; 53001951beSPeter Zijlstra 54001951beSPeter Zijlstra case JMP32_INSN_SIZE: 5563f62addSPeter Zijlstra code = text_gen_insn(JMP32_INSN_OPCODE, addr, dest); 56001951beSPeter Zijlstra nop = x86_nops[size]; 57001951beSPeter Zijlstra break; 58001951beSPeter Zijlstra 59001951beSPeter Zijlstra default: BUG(); 60001951beSPeter Zijlstra } 61d9f5ab7bSJason Baron 62f9510fa9SPeter Zijlstra if (type == JUMP_LABEL_JMP) 63001951beSPeter Zijlstra expect = nop; 64f9510fa9SPeter Zijlstra else 65f9510fa9SPeter Zijlstra expect = code; 66e71a5be1SJeremy Fitzhardinge 67001951beSPeter Zijlstra if (memcmp(addr, expect, size)) { 68f9510fa9SPeter Zijlstra /* 69f9510fa9SPeter Zijlstra * The location is not an op that we were expecting. 70f9510fa9SPeter Zijlstra * Something went wrong. Crash the box, as something could be 71f9510fa9SPeter Zijlstra * corrupting the kernel. 72f9510fa9SPeter Zijlstra */ 73001951beSPeter Zijlstra pr_crit("jump_label: Fatal kernel bug, unexpected op at %pS [%p] (%5ph != %5ph)) size:%d type:%d\n", 74001951beSPeter Zijlstra addr, addr, addr, expect, size, type); 75f9510fa9SPeter Zijlstra BUG(); 76f9510fa9SPeter Zijlstra } 779fc0f798SArd Biesheuvel 784cc6620bSDaniel Bristot de Oliveira if (type == JUMP_LABEL_NOP) 79001951beSPeter Zijlstra code = nop; 8018cbc8beSPeter Zijlstra 81001951beSPeter Zijlstra return (struct jump_label_patch){.code = code, .size = size}; 824cc6620bSDaniel Bristot de Oliveira } 834cc6620bSDaniel Bristot de Oliveira 844de4952cSRandy Dunlap static inline void __jump_label_transform(struct jump_entry *entry, 854cc6620bSDaniel Bristot de Oliveira enum jump_label_type type, 864cc6620bSDaniel Bristot de Oliveira int init) 874cc6620bSDaniel Bristot de Oliveira { 88001951beSPeter Zijlstra const struct jump_label_patch jlp = __jump_label_patch(entry, type); 894cc6620bSDaniel Bristot de Oliveira 9051b2c07bSJiri Kosina /* 91bb0a008dSNadav Amit * As long as only a single processor is running and the code is still 92bb0a008dSNadav Amit * not marked as RO, text_poke_early() can be used; Checking that 93bb0a008dSNadav Amit * system_state is SYSTEM_BOOTING guarantees it. It will be set to 94bb0a008dSNadav Amit * SYSTEM_SCHEDULING before other cores are awaken and before the 95bb0a008dSNadav Amit * code is write-protected. 9651b2c07bSJiri Kosina * 9751b2c07bSJiri Kosina * At the time the change is being done, just ignore whether we 9851b2c07bSJiri Kosina * are doing nop -> jump or jump -> nop transition, and assume 9951b2c07bSJiri Kosina * always nop being the 'currently valid' instruction 10051b2c07bSJiri Kosina */ 101bb0a008dSNadav Amit if (init || system_state == SYSTEM_BOOTING) { 102001951beSPeter Zijlstra text_poke_early((void *)jump_entry_code(entry), jlp.code, jlp.size); 1039fc0f798SArd Biesheuvel return; 1049fc0f798SArd Biesheuvel } 1059fc0f798SArd Biesheuvel 106001951beSPeter Zijlstra text_poke_bp((void *)jump_entry_code(entry), jlp.code, jlp.size, NULL); 10718cbc8beSPeter Zijlstra } 10818cbc8beSPeter Zijlstra 10918cbc8beSPeter Zijlstra static void __ref jump_label_transform(struct jump_entry *entry, 11018cbc8beSPeter Zijlstra enum jump_label_type type, 11118cbc8beSPeter Zijlstra int init) 11218cbc8beSPeter Zijlstra { 11318cbc8beSPeter Zijlstra mutex_lock(&text_mutex); 11418cbc8beSPeter Zijlstra __jump_label_transform(entry, type, init); 11518cbc8beSPeter Zijlstra mutex_unlock(&text_mutex); 116e71a5be1SJeremy Fitzhardinge } 117e71a5be1SJeremy Fitzhardinge 118e71a5be1SJeremy Fitzhardinge void arch_jump_label_transform(struct jump_entry *entry, 119e71a5be1SJeremy Fitzhardinge enum jump_label_type type) 120e71a5be1SJeremy Fitzhardinge { 12118cbc8beSPeter Zijlstra jump_label_transform(entry, type, 0); 122d9f5ab7bSJason Baron } 123d9f5ab7bSJason Baron 124ba54f0c3SDaniel Bristot de Oliveira bool arch_jump_label_transform_queue(struct jump_entry *entry, 125ba54f0c3SDaniel Bristot de Oliveira enum jump_label_type type) 126ba54f0c3SDaniel Bristot de Oliveira { 127001951beSPeter Zijlstra struct jump_label_patch jlp; 128ba54f0c3SDaniel Bristot de Oliveira 129ba54f0c3SDaniel Bristot de Oliveira if (system_state == SYSTEM_BOOTING) { 130ba54f0c3SDaniel Bristot de Oliveira /* 131ba54f0c3SDaniel Bristot de Oliveira * Fallback to the non-batching mode. 132ba54f0c3SDaniel Bristot de Oliveira */ 133ba54f0c3SDaniel Bristot de Oliveira arch_jump_label_transform(entry, type); 134ba54f0c3SDaniel Bristot de Oliveira return true; 135ba54f0c3SDaniel Bristot de Oliveira } 136ba54f0c3SDaniel Bristot de Oliveira 13718cbc8beSPeter Zijlstra mutex_lock(&text_mutex); 138001951beSPeter Zijlstra jlp = __jump_label_patch(entry, type); 139001951beSPeter Zijlstra text_poke_queue((void *)jump_entry_code(entry), jlp.code, jlp.size, NULL); 14018cbc8beSPeter Zijlstra mutex_unlock(&text_mutex); 141ba54f0c3SDaniel Bristot de Oliveira return true; 142ba54f0c3SDaniel Bristot de Oliveira } 143ba54f0c3SDaniel Bristot de Oliveira 144ba54f0c3SDaniel Bristot de Oliveira void arch_jump_label_transform_apply(void) 145ba54f0c3SDaniel Bristot de Oliveira { 146ba54f0c3SDaniel Bristot de Oliveira mutex_lock(&text_mutex); 14718cbc8beSPeter Zijlstra text_poke_finish(); 148ba54f0c3SDaniel Bristot de Oliveira mutex_unlock(&text_mutex); 149ba54f0c3SDaniel Bristot de Oliveira } 150ba54f0c3SDaniel Bristot de Oliveira 15111570da1SSteven Rostedt static enum { 15211570da1SSteven Rostedt JL_STATE_START, 15311570da1SSteven Rostedt JL_STATE_NO_UPDATE, 15411570da1SSteven Rostedt JL_STATE_UPDATE, 15511570da1SSteven Rostedt } jlstate __initdata_or_module = JL_STATE_START; 15611570da1SSteven Rostedt 1579cdbe1cbSPeter Zijlstra __init_or_module void arch_jump_label_transform_static(struct jump_entry *entry, 158e71a5be1SJeremy Fitzhardinge enum jump_label_type type) 159e71a5be1SJeremy Fitzhardinge { 16011570da1SSteven Rostedt if (jlstate == JL_STATE_UPDATE) 16118cbc8beSPeter Zijlstra jump_label_transform(entry, type, 1); 162e71a5be1SJeremy Fitzhardinge } 163