]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - arch/powerpc/lib/code-patching.c
powerpc: Avoid code patching freed init sections
[mirror_ubuntu-bionic-kernel.git] / arch / powerpc / lib / code-patching.c
index d469224c4ada8c23b923dc077b6249fa79ae0583..d61c3e2575789a745a095d92533d6658bc83493f 100644 (file)
 #include <asm/tlbflush.h>
 #include <asm/page.h>
 #include <asm/code-patching.h>
+#include <asm/sections.h>
 #include <asm/setup.h>
 
-static int __patch_instruction(unsigned int *addr, unsigned int instr)
+static int __patch_instruction(unsigned int *exec_addr, unsigned int instr,
+                              unsigned int *patch_addr)
 {
        int err;
 
-       __put_user_size(instr, addr, 4, err);
+       /* Make sure we aren't patching a freed init section */
+       if (init_mem_is_free && init_section_contains(exec_addr, 4)) {
+               pr_debug("Skipping init section patching addr: 0x%px\n", exec_addr);
+               return 0;
+       }
+
+       __put_user_size(instr, patch_addr, 4, err);
        if (err)
                return err;
 
-       asm ("dcbst 0, %0; sync; icbi 0,%0; sync; isync" :: "r" (addr));
+       asm ("dcbst 0, %0; sync; icbi 0,%1; sync; isync" :: "r" (patch_addr),
+                                                           "r" (exec_addr));
 
        return 0;
 }
 
+int raw_patch_instruction(unsigned int *addr, unsigned int instr)
+{
+       return __patch_instruction(addr, instr, addr);
+}
+
 #ifdef CONFIG_STRICT_KERNEL_RWX
 static DEFINE_PER_CPU(struct vm_struct *, text_poke_area);
 
@@ -138,7 +152,7 @@ static inline int unmap_patch_area(unsigned long addr)
 int patch_instruction(unsigned int *addr, unsigned int instr)
 {
        int err;
-       unsigned int *dest = NULL;
+       unsigned int *patch_addr = NULL;
        unsigned long flags;
        unsigned long text_poke_addr;
        unsigned long kaddr = (unsigned long)addr;
@@ -148,8 +162,8 @@ int patch_instruction(unsigned int *addr, unsigned int instr)
         * when text_poke_area is not ready, but we still need
         * to allow patching. We just do the plain old patching
         */
-       if (!this_cpu_read(*PTRRELOC(&text_poke_area)))
-               return __patch_instruction(addr, instr);
+       if (!this_cpu_read(text_poke_area))
+               return raw_patch_instruction(addr, instr);
 
        local_irq_save(flags);
 
@@ -159,17 +173,10 @@ int patch_instruction(unsigned int *addr, unsigned int instr)
                goto out;
        }
 
-       dest = (unsigned int *)(text_poke_addr) +
+       patch_addr = (unsigned int *)(text_poke_addr) +
                        ((kaddr & ~PAGE_MASK) / sizeof(unsigned int));
 
-       /*
-        * We use __put_user_size so that we can handle faults while
-        * writing to dest and return err to handle faults gracefully
-        */
-       __put_user_size(instr, dest, 4, err);
-       if (!err)
-               asm ("dcbst 0, %0; sync; icbi 0,%0; icbi 0,%1; sync; isync"
-                       ::"r" (dest), "r"(addr));
+       __patch_instruction(addr, instr, patch_addr);
 
        err = unmap_patch_area(text_poke_addr);
        if (err)
@@ -184,7 +191,7 @@ out:
 
 int patch_instruction(unsigned int *addr, unsigned int instr)
 {
-       return __patch_instruction(addr, instr);
+       return raw_patch_instruction(addr, instr);
 }
 
 #endif /* CONFIG_STRICT_KERNEL_RWX */
@@ -195,6 +202,22 @@ int patch_branch(unsigned int *addr, unsigned long target, int flags)
        return patch_instruction(addr, create_branch(addr, target, flags));
 }
 
+int patch_branch_site(s32 *site, unsigned long target, int flags)
+{
+       unsigned int *addr;
+
+       addr = (unsigned int *)((unsigned long)site + *site);
+       return patch_instruction(addr, create_branch(addr, target, flags));
+}
+
+int patch_instruction_site(s32 *site, unsigned int instr)
+{
+       unsigned int *addr;
+
+       addr = (unsigned int *)((unsigned long)site + *site);
+       return patch_instruction(addr, instr);
+}
+
 bool is_offset_in_branch_range(long offset)
 {
        /*
@@ -302,6 +325,11 @@ int instr_is_relative_branch(unsigned int instr)
        return instr_is_branch_iform(instr) || instr_is_branch_bform(instr);
 }
 
+int instr_is_relative_link_branch(unsigned int instr)
+{
+       return instr_is_relative_branch(instr) && (instr & BRANCH_SET_LINK);
+}
+
 static unsigned long branch_iform_target(const unsigned int *instr)
 {
        signed long imm;