Binary code patching on i386
authorsf <sf@openbsd.org>
Sun, 19 Apr 2015 06:27:17 +0000 (06:27 +0000)
committersf <sf@openbsd.org>
Sun, 19 Apr 2015 06:27:17 +0000 (06:27 +0000)
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
sys/arch/i386/i386/codepatch.c [new file with mode: 0644]
sys/arch/i386/i386/cpu.c
sys/arch/i386/i386/locore.s
sys/arch/i386/include/codepatch.h [new file with mode: 0644]

index b4679e6..85a0f84 100644 (file)
@@ -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 (file)
index 0000000..b164854
--- /dev/null
@@ -0,0 +1,208 @@
+/*      $OpenBSD: codepatch.c,v 1.1 2015/04/19 06:27:17 sf Exp $    */
+/*
+ * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de>
+ *
+ * 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 <sys/param.h>
+#include <machine/codepatch.h>
+#include <machine/cpu.h>
+#include <uvm/uvm_extern.h>
+
+#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);
+}
index 78cf7fd..a58e811 100644 (file)
@@ -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 <uvm/uvm_extern.h>
 
+#include <machine/codepatch.h>
 #include <machine/cpu.h>
 #include <machine/cpufunc.h>
 #include <machine/cpuvar.h>
@@ -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);
 }
index 81e61c7..7413d2b 100644 (file)
@@ -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 <compat/linux/linux_syscall.h>
 #endif
 
+#include <machine/codepatch.h>
 #include <machine/cputypes.h>
 #include <machine/param.h>
 #include <machine/pte.h>
@@ -64,6 +65,7 @@
 #include <machine/i82489reg.h>
 #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
  * 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 (file)
index 0000000..cf9ad7a
--- /dev/null
@@ -0,0 +1,52 @@
+/*      $OpenBSD: codepatch.h,v 1.1 2015/04/19 06:27:17 sf Exp $    */
+/*
+ * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de>
+ *
+ * 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 <machine/param.h>
+
+#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_ */