Binary code patching on amd64
authorsf <sf@openbsd.org>
Fri, 16 Jan 2015 10:17:51 +0000 (10:17 +0000)
committersf <sf@openbsd.org>
Fri, 16 Jan 2015 10:17:51 +0000 (10:17 +0000)
This commit adds generic infrastructure to do binary code patching on amd64.
The existing code patching for SMAP is converted to the new infrastruture.

More consumers and support for i386 will follow later.

This version of the diff has some simplifications in codepatch_fill_nop()
compared to a version that was:

OK @kettenis @mlarkin @jsg

sys/arch/amd64/amd64/codepatch.c [new file with mode: 0644]
sys/arch/amd64/amd64/copy.S
sys/arch/amd64/amd64/cpu.c
sys/arch/amd64/amd64/locore.S
sys/arch/amd64/conf/files.amd64
sys/arch/amd64/include/codepatch.h [new file with mode: 0644]

diff --git a/sys/arch/amd64/amd64/codepatch.c b/sys/arch/amd64/amd64/codepatch.c
new file mode 100644 (file)
index 0000000..4e9ed6b
--- /dev/null
@@ -0,0 +1,178 @@
+/*      $OpenBSD: codepatch.c,v 1.1 2015/01/16 10:17:51 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 <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;
+
+       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;
+       }
+}
+
+/*
+ * 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)
+               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 32e18fc..c638e2d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: copy.S,v 1.5 2012/10/31 03:30:22 jsg Exp $    */
+/*     $OpenBSD: copy.S,v 1.6 2015/01/16 10:17:51 sf Exp $     */
 /*     $NetBSD: copy.S,v 1.1 2003/04/26 18:39:26 fvdl Exp $    */
 
 /*
@@ -42,6 +42,7 @@
 #include <sys/syscall.h>
 
 #include <machine/asm.h>
+#include <machine/codepatch.h>
 
 /*
  * As stac/clac SMAP instructions are 3 bytes, we want the fastest
  * on all family 0x6 and 0xf processors (ie 686+)
  */
 #define SMAP_NOP       .byte 0x0f, 0x1f, 0x00
+#define SMAP_STAC      CODEPATCH_START                 ;\
+                       SMAP_NOP                        ;\
+                       CODEPATCH_END(CPTAG_STAC)
+#define SMAP_CLAC      CODEPATCH_START                 ;\
+                       SMAP_NOP                        ;\
+                       CODEPATCH_END(CPTAG_CLAC)
 
 /*
  * Copy routines from and to userland, plus a few more. See the
@@ -110,7 +117,6 @@ ENTRY(kcopy)
        xorq    %rax,%rax
        ret
 
-.globl _C_LABEL(_copyout_stac), _C_LABEL(_copyout_clac)
 ENTRY(copyout)
        pushq   $0
 
@@ -127,9 +133,7 @@ ENTRY(copyout)
        movq    CPUVAR(CURPCB),%rdx
        leaq    _C_LABEL(copy_fault)(%rip),%r11
        movq    %r11,PCB_ONFAULT(%rdx)
-_C_LABEL(_copyout_stac):
-       SMAP_NOP
-
+       SMAP_STAC
        cld
        movq    %rax,%rcx
        shrq    $3,%rcx
@@ -139,22 +143,17 @@ _C_LABEL(_copyout_stac):
        andb    $7,%cl
        rep
        movsb
-
-_C_LABEL(_copyout_clac):
-       SMAP_NOP
+       SMAP_CLAC
        popq    PCB_ONFAULT(%rdx)
        xorl    %eax,%eax
        ret
 
-.globl _C_LABEL(_copyin_stac), _C_LABEL(_copyin_clac)
 ENTRY(copyin)
        movq    CPUVAR(CURPCB),%rax
        pushq   $0
        leaq    _C_LABEL(copy_fault)(%rip),%r11
        movq    %r11,PCB_ONFAULT(%rax)
-_C_LABEL(_copyin_stac):
-       SMAP_NOP
-
+       SMAP_STAC
        xchgq   %rdi,%rsi
        movq    %rdx,%rax
 
@@ -176,8 +175,7 @@ _C_LABEL(_copyin_stac):
        rep
        movsb
 
-_C_LABEL(_copyin_clac):
-       SMAP_NOP
+       SMAP_CLAC
        movq    CPUVAR(CURPCB),%rdx
        popq    PCB_ONFAULT(%rdx)
        xorl    %eax,%eax
@@ -186,15 +184,12 @@ _C_LABEL(_copyin_clac):
 NENTRY(copy_efault)
        movq    $EFAULT,%rax
 
-.globl _C_LABEL(_copy_fault_clac)
 NENTRY(copy_fault)
-_C_LABEL(_copy_fault_clac):
-       SMAP_NOP
+       SMAP_CLAC
        movq    CPUVAR(CURPCB),%rdx
        popq    PCB_ONFAULT(%rdx)
        ret
 
-.globl _C_LABEL(_copyoutstr_stac)
 ENTRY(copyoutstr)
        xchgq   %rdi,%rsi
        movq    %rdx,%r8
@@ -203,8 +198,7 @@ ENTRY(copyoutstr)
 5:     movq    CPUVAR(CURPCB),%rax
        leaq    _C_LABEL(copystr_fault)(%rip),%r11
        movq    %r11,PCB_ONFAULT(%rax)
-_C_LABEL(_copyoutstr_stac):
-       SMAP_NOP
+       SMAP_STAC
        /*
         * Get min(%rdx, VM_MAXUSER_ADDRESS-%rdi).
         */
@@ -238,7 +232,6 @@ _C_LABEL(_copyoutstr_stac):
        movq    $ENAMETOOLONG,%rax
        jmp     copystr_return
 
-.globl _C_LABEL(_copyinstr_stac)
 ENTRY(copyinstr)
        xchgq   %rdi,%rsi
        movq    %rdx,%r8
@@ -247,8 +240,7 @@ ENTRY(copyinstr)
        movq    CPUVAR(CURPCB),%rcx
        leaq    _C_LABEL(copystr_fault)(%rip),%r11
        movq    %r11,PCB_ONFAULT(%rcx)
-_C_LABEL(_copyinstr_stac):
-       SMAP_NOP
+       SMAP_STAC
 
        /*
         * Get min(%rdx, VM_MAXUSER_ADDRESS-%rsi).
@@ -286,11 +278,9 @@ _C_LABEL(_copyinstr_stac):
 ENTRY(copystr_efault)
        movl    $EFAULT,%eax
 
-.globl _C_LABEL(_copystr_fault_clac)
 ENTRY(copystr_fault)
 copystr_return:
-_C_LABEL(_copystr_fault_clac):
-       SMAP_NOP
+       SMAP_CLAC
        /* Set *lencopied and return %eax. */
        movq    CPUVAR(CURPCB),%rcx
        movq    $0,PCB_ONFAULT(%rcx)
index 6bebf80..6a47542 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpu.c,v 1.77 2015/01/06 12:50:47 dlg Exp $    */
+/*     $OpenBSD: cpu.c,v 1.78 2015/01/16 10:17:51 sf Exp $     */
 /* $NetBSD: cpu.c,v 1.1 2003/04/26 18:39:26 fvdl 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>
@@ -121,65 +122,23 @@ struct cpu_softc {
 #ifndef SMALL_KERNEL
 void   replacesmap(void);
 
-extern long _copyout_stac;
-extern long _copyout_clac;
-extern long _copyin_stac;
-extern long _copyin_clac;
-extern long _copy_fault_clac;
-extern long _copyoutstr_stac;
-extern long _copyinstr_stac;
-extern long _copystr_fault_clac;
 extern long _stac;
 extern long _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 },
-};
-
 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 */
-               memcpy((void *)(nva + po), ireplace[i].saddr, 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 022cd07..4620364 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: locore.S,v 1.61 2014/12/21 16:27:07 mlarkin Exp $     */
+/*     $OpenBSD: locore.S,v 1.62 2015/01/16 10:17:51 sf Exp $  */
 /*     $NetBSD: locore.S,v 1.13 2004/03/25 18:33:17 drochner Exp $     */
 
 /*
@@ -1168,3 +1168,13 @@ ENTRY(pagezero)
        sfence
        ret
 
+       .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
index d5a1c6e..a39ed72 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files.amd64,v 1.76 2014/12/10 05:42:25 jsg Exp $
+#      $OpenBSD: files.amd64,v 1.77 2015/01/16 10:17:51 sf Exp $
 
 maxpartitions 16
 maxusers 2 16 128
@@ -80,6 +80,8 @@ device        mainbus: isabus, pcibus, mainbus
 attach mainbus at root
 file   arch/amd64/amd64/mainbus.c              mainbus
 
+file   arch/amd64/amd64/codepatch.c
+
 device bios {}
 attach bios at mainbus
 file   arch/amd64/amd64/bios.c                 bios needs-flag
diff --git a/sys/arch/amd64/include/codepatch.h b/sys/arch/amd64/include/codepatch.h
new file mode 100644 (file)
index 0000000..7ff3216
--- /dev/null
@@ -0,0 +1,52 @@
+/*      $OpenBSD: codepatch.h,v 1.1 2015/01/16 10:17:51 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_ */