From 64e2e8001cb24b423e5ca4634f7ff8ccd2f1661d Mon Sep 17 00:00:00 2001 From: sf Date: Sun, 19 Apr 2015 06:27:17 +0000 Subject: [PATCH] Binary code patching on i386 This commit ports the infrastructure to do binary code patching from amd64. The existing code patching for SMAP is converted to the new infrastruture. ok kettenis@ "should go in" deraadt@ --- sys/arch/i386/conf/files.i386 | 4 +- sys/arch/i386/i386/codepatch.c | 208 ++++++++++++++++++++++++++++++ sys/arch/i386/i386/cpu.c | 55 +------- sys/arch/i386/i386/locore.s | 69 +++++----- sys/arch/i386/include/codepatch.h | 52 ++++++++ 5 files changed, 308 insertions(+), 80 deletions(-) create mode 100644 sys/arch/i386/i386/codepatch.c create mode 100644 sys/arch/i386/include/codepatch.h diff --git a/sys/arch/i386/conf/files.i386 b/sys/arch/i386/conf/files.i386 index b4679e6bea7..85a0f840b96 100644 --- a/sys/arch/i386/conf/files.i386 +++ b/sys/arch/i386/conf/files.i386 @@ -1,4 +1,4 @@ -# $OpenBSD: files.i386,v 1.221 2015/04/12 18:37:53 mlarkin Exp $ +# $OpenBSD: files.i386,v 1.222 2015/04/19 06:27:17 sf Exp $ # # new style config file for i386 architecture # @@ -86,6 +86,8 @@ device mainbus: isabus, eisabus, pcibus, mainbus attach mainbus at root file arch/i386/i386/mainbus.c mainbus +file arch/i386/i386/codepatch.c + #device mca at root {...} # diff --git a/sys/arch/i386/i386/codepatch.c b/sys/arch/i386/i386/codepatch.c new file mode 100644 index 00000000000..b1648548d1b --- /dev/null +++ b/sys/arch/i386/i386/codepatch.c @@ -0,0 +1,208 @@ +/* $OpenBSD: codepatch.c,v 1.1 2015/04/19 06:27:17 sf Exp $ */ +/* + * Copyright (c) 2014-2015 Stefan Fritsch + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#ifdef CODEPATCH_DEBUG +#define DBGPRINT(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) +#else +#define DBGPRINT(fmt, args...) do {} while (0) +#endif + +struct codepatch { + uint32_t offset; + uint16_t len; + uint16_t tag; +}; + +extern struct codepatch codepatch_begin; +extern struct codepatch codepatch_end; + +#define NOP_LEN_MAX 9 + +static const unsigned char nops[][NOP_LEN_MAX] = { + { 0x90 }, + { 0x66, 0x90 }, + { 0x0F, 0x1F, 0x00 }, + { 0x0F, 0x1F, 0x40, 0x00 }, + { 0x0F, 0x1F, 0x44, 0x00, 0x00 }, + { 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 }, + { 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 }, + { 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 }, + { 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00}, +}; + +void +codepatch_fill_nop(void *caddr, uint16_t len) +{ + unsigned char *addr = caddr; + uint16_t nop_len; + + if ((strcmp(cpu_vendor, "GenuineIntel") != 0) && + (strcmp(cpu_vendor, "AuthenticAMD") != 0)) { + /* + * Others don't support multi-byte NOPs. + * Except maybe some Via C3's, but I couldn't find + * definitive information, so better be safe. + */ + goto singlebyte; + } + /* + * Intel says family 0x6 or 0xf. + * AMD says "Athlon or newer", which happen to be the same families. + */ + switch (cpu_id & 0xf00) { + case 0x600: + case 0xf00: + /* Multi-byte NOP supported */ + break; + default: + goto singlebyte; + } + + while (len > 0) { + if (len <= NOP_LEN_MAX) + nop_len = len; + else + nop_len = NOP_LEN_MAX; + memcpy(addr, nops[nop_len-1], nop_len); + addr += nop_len; + len -= nop_len; + } + return; + +singlebyte: + /* Use single-byte NOP */ + memset(caddr, 0x90, len); +} + +/* + * Create writeable aliases of memory we need + * to write to as kernel is mapped read-only + */ +void *codepatch_maprw(vaddr_t *nva, vaddr_t dest) +{ + paddr_t kva = trunc_page((paddr_t)dest); + paddr_t po = (paddr_t)dest & PAGE_MASK; + paddr_t pa1, pa2; + + if (*nva == 0) + *nva = (vaddr_t)km_alloc(2 * PAGE_SIZE, &kv_any, &kp_none, + &kd_waitok); + + pmap_extract(pmap_kernel(), kva, &pa1); + pmap_extract(pmap_kernel(), kva + PAGE_SIZE, &pa2); + pmap_kenter_pa(*nva, pa1, PROT_READ | PROT_WRITE); + pmap_kenter_pa(*nva + PAGE_SIZE, pa2, PROT_READ | PROT_WRITE); + pmap_update(pmap_kernel()); + + return (void *)(*nva + po); +} + +void codepatch_unmaprw(vaddr_t nva) +{ + if (nva == 0) + return; + pmap_kremove(nva, 2 * PAGE_SIZE); + km_free((void *)nva, 2 * PAGE_SIZE, &kv_any, &kp_none); +} + +/* Patch with NOPs */ +void +codepatch_nop(uint16_t tag) +{ + struct codepatch *patch; + unsigned char *rwaddr; + vaddr_t addr, rwmap = 0; + int i = 0; + + DBGPRINT("patching tag %u", tag); + + for (patch = &codepatch_begin; patch < &codepatch_end; patch++) { + if (patch->tag != tag) + continue; + addr = KERNBASE + patch->offset; + rwaddr = codepatch_maprw(&rwmap, addr); + codepatch_fill_nop(rwaddr, patch->len); + i++; + } + codepatch_unmaprw(rwmap); + DBGPRINT("patched %d places", i); +} + +/* Patch with alternative code */ +void +codepatch_replace(uint16_t tag, void *code, size_t len) +{ + struct codepatch *patch; + unsigned char *rwaddr; + vaddr_t addr, rwmap = 0; + int i = 0; + + DBGPRINT("patching tag %u with %p", tag, code); + + for (patch = &codepatch_begin; patch < &codepatch_end; patch++) { + if (patch->tag != tag) + continue; + addr = KERNBASE + patch->offset; + + if (len > patch->len) { + panic("%s: can't replace len %u with %zu at %#lx", + __func__, patch->len, len, addr); + } + rwaddr = codepatch_maprw(&rwmap, addr); + memcpy(rwaddr, code, len); + codepatch_fill_nop(rwaddr + len, patch->len - len); + i++; + } + codepatch_unmaprw(rwmap); + DBGPRINT("patched %d places", i); +} + +/* Patch with calls to func */ +void +codepatch_call(uint16_t tag, void *func) +{ + struct codepatch *patch; + unsigned char *rwaddr; + int32_t offset; + int i = 0; + vaddr_t addr, rwmap = 0; + + DBGPRINT("patching tag %u with call %p", tag, func); + + for (patch = &codepatch_begin; patch < &codepatch_end; patch++) { + if (patch->tag != tag) + continue; + addr = KERNBASE + patch->offset; + if (patch->len < 5) + panic("%s: can't replace len %u with call at %#lx", + __func__, patch->len, addr); + + offset = (vaddr_t)func - (addr + 5); + rwaddr = codepatch_maprw(&rwmap, addr); + rwaddr[0] = 0xe8; /* call near */ + memcpy(rwaddr + 1, &offset, sizeof(offset)); + codepatch_fill_nop(rwaddr + 5, patch->len - 5); + i++; + } + codepatch_unmaprw(rwmap); + DBGPRINT("patched %d places", i); +} diff --git a/sys/arch/i386/i386/cpu.c b/sys/arch/i386/i386/cpu.c index 78cf7fd033f..a58e811cd31 100644 --- a/sys/arch/i386/i386/cpu.c +++ b/sys/arch/i386/i386/cpu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cpu.c,v 1.62 2015/04/18 22:16:21 kettenis Exp $ */ +/* $OpenBSD: cpu.c,v 1.63 2015/04/19 06:27:17 sf Exp $ */ /* $NetBSD: cpu.c,v 1.1.2.7 2000/06/26 02:04:05 sommerfeld Exp $ */ /*- @@ -77,6 +77,7 @@ #include +#include #include #include #include @@ -167,69 +168,23 @@ struct cfdriver cpu_cd = { #ifndef SMALL_KERNEL void replacesmap(void); -extern int _copyout_stac; -extern int _copyout_clac; -extern int _copyin_stac; -extern int _copyin_clac; -extern int _copy_fault_clac; -extern int _copyoutstr_stac; -extern int _copyinstr_stac; -extern int _copystr_fault_clac; -extern int _ucas_32_stac; -extern int _ucas_32_clac; extern int _stac; extern int _clac; -static const struct { - void *daddr; - void *saddr; -} ireplace[] = { - { &_copyout_stac, &_stac }, - { &_copyout_clac, &_clac }, - { &_copyin_stac, &_stac }, - { &_copyin_clac, &_clac }, - { &_copy_fault_clac, &_clac }, - { &_copyoutstr_stac, &_stac }, - { &_copyinstr_stac, &_stac }, - { &_copystr_fault_clac, &_clac }, - { &_ucas_32_stac, &_stac }, - { &_ucas_32_clac, &_clac }, -}; - void replacesmap(void) { static int replacedone = 0; - int i, s; - vaddr_t nva; + int s; if (replacedone) return; replacedone = 1; s = splhigh(); - /* - * Create writeable aliases of memory we need - * to write to as kernel is mapped read-only - */ - nva = (vaddr_t)km_alloc(2 * PAGE_SIZE, &kv_any, &kp_none, &kd_waitok); - - for (i = 0; i < nitems(ireplace); i++) { - paddr_t kva = trunc_page((paddr_t)ireplace[i].daddr); - paddr_t po = (paddr_t)ireplace[i].daddr & PAGE_MASK; - paddr_t pa1, pa2; - - pmap_extract(pmap_kernel(), kva, &pa1); - pmap_extract(pmap_kernel(), kva + PAGE_SIZE, &pa2); - pmap_kenter_pa(nva, pa1, PROT_READ | PROT_WRITE); - pmap_kenter_pa(nva + PAGE_SIZE, pa2, PROT_READ | PROT_WRITE); - pmap_update(pmap_kernel()); - - /* replace 3 byte nops with stac/clac instructions */ - bcopy(ireplace[i].saddr, (void *)(nva + po), 3); - } - km_free((void *)nva, 2 * PAGE_SIZE, &kv_any, &kp_none); + codepatch_replace(CPTAG_STAC, &_stac, 3); + codepatch_replace(CPTAG_CLAC, &_clac, 3); splx(s); } diff --git a/sys/arch/i386/i386/locore.s b/sys/arch/i386/i386/locore.s index 81e61c70cdb..7413d2bc29a 100644 --- a/sys/arch/i386/i386/locore.s +++ b/sys/arch/i386/i386/locore.s @@ -1,4 +1,4 @@ -/* $OpenBSD: locore.s,v 1.153 2015/04/18 05:14:05 guenther Exp $ */ +/* $OpenBSD: locore.s,v 1.154 2015/04/19 06:27:17 sf Exp $ */ /* $NetBSD: locore.s,v 1.145 1996/05/03 19:41:19 christos Exp $ */ /*- @@ -51,6 +51,7 @@ #include #endif +#include #include #include #include @@ -64,6 +65,7 @@ #include #endif +#ifndef SMALL_KERNEL /* * As stac/clac SMAP instructions are 3 bytes, we want the fastest * 3 byte nop sequence possible here. This will be replaced by @@ -73,7 +75,21 @@ * on all family 0x6 and 0xf processors (ie 686+) * So use 3 of the single byte nops for compatibility */ -#define SMAP_NOP .byte 0x90, 0x90, 0x90 +#define SMAP_NOP .byte 0x90, 0x90, 0x90 +#define SMAP_STAC CODEPATCH_START ;\ + SMAP_NOP ;\ + CODEPATCH_END(CPTAG_STAC) +#define SMAP_CLAC CODEPATCH_START ;\ + SMAP_NOP ;\ + CODEPATCH_END(CPTAG_CLAC) + +#else + +#define SMAP_STAC +#define SMAP_CLAC + +#endif + /* * override user-land alignment before including asm.h @@ -662,6 +678,18 @@ NENTRY(proc_trampoline) INTRFASTEXIT /* NOTREACHED */ + /* This must come before any use of the CODEPATCH macros */ + .section .codepatch,"a" + .align 8 + .globl _C_LABEL(codepatch_begin) +_C_LABEL(codepatch_begin): + .previous + + .section .codepatchend,"a" + .globl _C_LABEL(codepatch_end) +_C_LABEL(codepatch_end): + .previous + /*****************************************************************************/ /* @@ -793,7 +821,6 @@ ENTRY(kcopy) * copyout(caddr_t from, caddr_t to, size_t len); * Copy len bytes into the user's address space. */ -.globl _C_LABEL(_copyout_stac), _C_LABEL(_copyout_clac) ENTRY(copyout) #ifdef DDB pushl %ebp @@ -822,8 +849,7 @@ ENTRY(copyout) GET_CURPCB(%edx) movl $_C_LABEL(copy_fault),PCB_ONFAULT(%edx) -_C_LABEL(_copyout_stac): - SMAP_NOP + SMAP_STAC /* bcopy(%esi, %edi, %eax); */ cld @@ -836,8 +862,7 @@ _C_LABEL(_copyout_stac): rep movsb -_C_LABEL(_copyout_clac): - SMAP_NOP + SMAP_CLAC popl PCB_ONFAULT(%edx) popl %edi popl %esi @@ -851,7 +876,6 @@ _C_LABEL(_copyout_clac): * copyin(caddr_t from, caddr_t to, size_t len); * Copy len bytes from the user's address space. */ -.globl _C_LABEL(_copyin_stac), _C_LABEL(_copyin_clac) ENTRY(copyin) #ifdef DDB pushl %ebp @@ -862,8 +886,7 @@ ENTRY(copyin) GET_CURPCB(%eax) pushl $0 movl $_C_LABEL(copy_fault),PCB_ONFAULT(%eax) -_C_LABEL(_copyin_stac): - SMAP_NOP + SMAP_STAC movl 16+FPADD(%esp),%esi movl 20+FPADD(%esp),%edi @@ -891,8 +914,7 @@ _C_LABEL(_copyin_stac): rep movsb -_C_LABEL(_copyin_clac): - SMAP_NOP + SMAP_CLAC GET_CURPCB(%edx) popl PCB_ONFAULT(%edx) popl %edi @@ -903,10 +925,8 @@ _C_LABEL(_copyin_clac): #endif ret -.globl _C_LABEL(_copy_fault_clac) ENTRY(copy_fault) -_C_LABEL(_copy_fault_clac): - SMAP_NOP + SMAP_CLAC GET_CURPCB(%edx) popl PCB_ONFAULT(%edx) popl %edi @@ -924,7 +944,6 @@ _C_LABEL(_copy_fault_clac): * NUL) in *lencopied. If the string is too long, return ENAMETOOLONG; else * return 0 or EFAULT. */ -.globl _C_LABEL(_copyoutstr_stac) ENTRY(copyoutstr) #ifdef DDB pushl %ebp @@ -939,8 +958,7 @@ ENTRY(copyoutstr) 5: GET_CURPCB(%eax) movl $_C_LABEL(copystr_fault),PCB_ONFAULT(%eax) -_C_LABEL(_copyoutstr_stac): - SMAP_NOP + SMAP_STAC /* * Get min(%edx, VM_MAXUSER_ADDRESS-%edi). */ @@ -983,7 +1001,6 @@ _C_LABEL(_copyoutstr_stac): * NUL) in *lencopied. If the string is too long, return ENAMETOOLONG; else * return 0 or EFAULT. */ -.globl _C_LABEL(_copyinstr_stac) ENTRY(copyinstr) #ifdef DDB pushl %ebp @@ -993,8 +1010,7 @@ ENTRY(copyinstr) pushl %edi GET_CURPCB(%ecx) movl $_C_LABEL(copystr_fault),PCB_ONFAULT(%ecx) -_C_LABEL(_copyinstr_stac): - SMAP_NOP + SMAP_STAC movl 12+FPADD(%esp),%esi # %esi = from movl 16+FPADD(%esp),%edi # %edi = to @@ -1034,13 +1050,11 @@ _C_LABEL(_copyinstr_stac): movl $ENAMETOOLONG,%eax jmp copystr_return -.globl _C_LABEL(_copystr_fault_clac) ENTRY(copystr_fault) movl $EFAULT,%eax copystr_return: -_C_LABEL(_copystr_fault_clac): - SMAP_NOP + SMAP_CLAC /* Set *lencopied and return %eax. */ GET_CURPCB(%ecx) movl $0,PCB_ONFAULT(%ecx) @@ -1686,7 +1700,6 @@ ENTRY(cpu_paenable) /* * ucas_32(volatile int32_t *uptr, int32_t old, int32_t new); */ -.global _C_LABEL(_ucas_32_stac), _C_LABEL(_ucas_32_clac) ENTRY(ucas_32) #ifdef DDB pushl %ebp @@ -1705,14 +1718,12 @@ ENTRY(ucas_32) GET_CURPCB(%edx) movl $_C_LABEL(copy_fault),PCB_ONFAULT(%edx) -_C_LABEL(_ucas_32_stac): - SMAP_NOP + SMAP_STAC lock cmpxchgl %edi, (%esi) -_C_LABEL(_ucas_32_clac): - SMAP_NOP + SMAP_CLAC popl PCB_ONFAULT(%edx) popl %edi popl %esi diff --git a/sys/arch/i386/include/codepatch.h b/sys/arch/i386/include/codepatch.h new file mode 100644 index 00000000000..cf9ad7abaca --- /dev/null +++ b/sys/arch/i386/include/codepatch.h @@ -0,0 +1,52 @@ +/* $OpenBSD: codepatch.h,v 1.1 2015/04/19 06:27:17 sf Exp $ */ +/* + * Copyright (c) 2014-2015 Stefan Fritsch + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _MACHINE_CODEPATCH_H_ +#define _MACHINE_CODEPATCH_H_ + +#include + +#ifndef _LOCORE + +void *codepatch_maprw(vaddr_t *nva, vaddr_t dest); +void codepatch_unmaprw(vaddr_t nva); +void codepatch_fill_nop(void *caddr, uint16_t len); +void codepatch_nop(uint16_t tag); +void codepatch_replace(uint16_t tag, void *code, size_t len); +void codepatch_call(uint16_t tag, void *func); + +#endif /* !_LOCORE */ + +/* + * Mark the start of some code snippet to be patched. + */ +#define CODEPATCH_START 998: +/* + * Mark the end of some code to be patched, and assign the given tag. + */ +#define CODEPATCH_END(tag) \ + 999: \ + .section .codepatch, "a" ;\ + .int (998b - KERNBASE) ;\ + .short (999b - 998b) ;\ + .short tag ;\ + .previous + +#define CPTAG_STAC 1 +#define CPTAG_CLAC 2 + +#endif /* _MACHINE_CODEPATCH_H_ */ -- 2.20.1