]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blobdiff - kernel/events/uprobes.c
uretprobes: Reserve the first slot in xol_vma for trampoline
[mirror_ubuntu-hirsute-kernel.git] / kernel / events / uprobes.c
index a567c8c7ef31fa8c7d9416c8a255717f6b978932..d345b7c6cb2dce5d435fece4474def421c1bfe88 100644 (file)
@@ -173,10 +173,31 @@ bool __weak is_swbp_insn(uprobe_opcode_t *insn)
        return *insn == UPROBE_SWBP_INSN;
 }
 
-static void copy_opcode(struct page *page, unsigned long vaddr, uprobe_opcode_t *opcode)
+/**
+ * is_trap_insn - check if instruction is breakpoint instruction.
+ * @insn: instruction to be checked.
+ * Default implementation of is_trap_insn
+ * Returns true if @insn is a breakpoint instruction.
+ *
+ * This function is needed for the case where an architecture has multiple
+ * trap instructions (like powerpc).
+ */
+bool __weak is_trap_insn(uprobe_opcode_t *insn)
+{
+       return is_swbp_insn(insn);
+}
+
+static void copy_from_page(struct page *page, unsigned long vaddr, void *dst, int len)
+{
+       void *kaddr = kmap_atomic(page);
+       memcpy(dst, kaddr + (vaddr & ~PAGE_MASK), len);
+       kunmap_atomic(kaddr);
+}
+
+static void copy_to_page(struct page *page, unsigned long vaddr, const void *src, int len)
 {
        void *kaddr = kmap_atomic(page);
-       memcpy(opcode, kaddr + (vaddr & ~PAGE_MASK), UPROBE_SWBP_INSN_SIZE);
+       memcpy(kaddr + (vaddr & ~PAGE_MASK), src, len);
        kunmap_atomic(kaddr);
 }
 
@@ -185,7 +206,16 @@ static int verify_opcode(struct page *page, unsigned long vaddr, uprobe_opcode_t
        uprobe_opcode_t old_opcode;
        bool is_swbp;
 
-       copy_opcode(page, vaddr, &old_opcode);
+       /*
+        * Note: We only check if the old_opcode is UPROBE_SWBP_INSN here.
+        * We do not check if it is any other 'trap variant' which could
+        * be conditional trap instruction such as the one powerpc supports.
+        *
+        * The logic is that we do not care if the underlying instruction
+        * is a trap variant; uprobes always wins over any other (gdb)
+        * breakpoint.
+        */
+       copy_from_page(page, vaddr, &old_opcode, UPROBE_SWBP_INSN_SIZE);
        is_swbp = is_swbp_insn(&old_opcode);
 
        if (is_swbp_insn(new_opcode)) {
@@ -204,7 +234,7 @@ static int verify_opcode(struct page *page, unsigned long vaddr, uprobe_opcode_t
  * Expect the breakpoint instruction to be the smallest size instruction for
  * the architecture. If an arch has variable length instruction and the
  * breakpoint instruction is not of the smallest length instruction
- * supported by that architecture then we need to modify is_swbp_at_addr and
+ * supported by that architecture then we need to modify is_trap_at_addr and
  * write_opcode accordingly. This would never be a problem for archs that
  * have fixed length instructions.
  */
@@ -225,7 +255,6 @@ static int write_opcode(struct mm_struct *mm, unsigned long vaddr,
                        uprobe_opcode_t opcode)
 {
        struct page *old_page, *new_page;
-       void *vaddr_old, *vaddr_new;
        struct vm_area_struct *vma;
        int ret;
 
@@ -246,15 +275,8 @@ retry:
 
        __SetPageUptodate(new_page);
 
-       /* copy the page now that we've got it stable */
-       vaddr_old = kmap_atomic(old_page);
-       vaddr_new = kmap_atomic(new_page);
-
-       memcpy(vaddr_new, vaddr_old, PAGE_SIZE);
-       memcpy(vaddr_new + (vaddr & ~PAGE_MASK), &opcode, UPROBE_SWBP_INSN_SIZE);
-
-       kunmap_atomic(vaddr_new);
-       kunmap_atomic(vaddr_old);
+       copy_highpage(new_page, old_page);
+       copy_to_page(new_page, vaddr, &opcode, UPROBE_SWBP_INSN_SIZE);
 
        ret = anon_vma_prepare(vma);
        if (ret)
@@ -477,30 +499,18 @@ __copy_insn(struct address_space *mapping, struct file *filp, char *insn,
                        unsigned long nbytes, loff_t offset)
 {
        struct page *page;
-       void *vaddr;
-       unsigned long off;
-       pgoff_t idx;
-
-       if (!filp)
-               return -EINVAL;
 
        if (!mapping->a_ops->readpage)
                return -EIO;
-
-       idx = offset >> PAGE_CACHE_SHIFT;
-       off = offset & ~PAGE_MASK;
-
        /*
         * Ensure that the page that has the original instruction is
         * populated and in page-cache.
         */
-       page = read_mapping_page(mapping, idx, filp);
+       page = read_mapping_page(mapping, offset >> PAGE_CACHE_SHIFT, filp);
        if (IS_ERR(page))
                return PTR_ERR(page);
 
-       vaddr = kmap_atomic(page);
-       memcpy(insn, vaddr + off, nbytes);
-       kunmap_atomic(vaddr);
+       copy_from_page(page, offset, insn, nbytes);
        page_cache_release(page);
 
        return 0;
@@ -550,7 +560,7 @@ static int prepare_uprobe(struct uprobe *uprobe, struct file *file,
                goto out;
 
        ret = -ENOTSUPP;
-       if (is_swbp_insn((uprobe_opcode_t *)uprobe->arch.insn))
+       if (is_trap_insn((uprobe_opcode_t *)uprobe->arch.insn))
                goto out;
 
        ret = arch_uprobe_analyze_insn(&uprobe->arch, mm, vaddr);
@@ -758,7 +768,7 @@ register_for_each_vma(struct uprobe *uprobe, struct uprobe_consumer *new)
                down_write(&mm->mmap_sem);
                vma = find_vma(mm, info->vaddr);
                if (!vma || !valid_vma(vma, is_register) ||
-                   vma->vm_file->f_mapping->host != uprobe->inode)
+                   file_inode(vma->vm_file) != uprobe->inode)
                        goto unlock;
 
                if (vma->vm_start > info->vaddr ||
@@ -828,6 +838,14 @@ int uprobe_register(struct inode *inode, loff_t offset, struct uprobe_consumer *
        struct uprobe *uprobe;
        int ret;
 
+       /* Uprobe must have at least one set consumer */
+       if (!uc->handler && !uc->ret_handler)
+               return -EINVAL;
+
+       /* TODO: Implement return probes */
+       if (uc->ret_handler)
+               return -ENOSYS;
+
        /* Racy, just to catch the obvious mistakes */
        if (offset > i_size_read(inode))
                return -EINVAL;
@@ -917,7 +935,7 @@ static int unapply_uprobe(struct uprobe *uprobe, struct mm_struct *mm)
                loff_t offset;
 
                if (!valid_vma(vma, false) ||
-                   vma->vm_file->f_mapping->host != uprobe->inode)
+                   file_inode(vma->vm_file) != uprobe->inode)
                        continue;
 
                offset = (loff_t)vma->vm_pgoff << PAGE_SHIFT;
@@ -1010,7 +1028,7 @@ int uprobe_mmap(struct vm_area_struct *vma)
        if (no_uprobe_events() || !valid_vma(vma, true))
                return 0;
 
-       inode = vma->vm_file->f_mapping->host;
+       inode = file_inode(vma->vm_file);
        if (!inode)
                return 0;
 
@@ -1041,7 +1059,7 @@ vma_has_uprobes(struct vm_area_struct *vma, unsigned long start, unsigned long e
        struct inode *inode;
        struct rb_node *n;
 
-       inode = vma->vm_file->f_mapping->host;
+       inode = file_inode(vma->vm_file);
 
        min = vaddr_to_offset(vma, start);
        max = min + (end - start) - 1;
@@ -1114,6 +1132,7 @@ static struct xol_area *get_xol_area(void)
 {
        struct mm_struct *mm = current->mm;
        struct xol_area *area;
+       uprobe_opcode_t insn = UPROBE_SWBP_INSN;
 
        area = mm->uprobes_state.xol_area;
        if (area)
@@ -1131,7 +1150,12 @@ static struct xol_area *get_xol_area(void)
        if (!area->page)
                goto free_bitmap;
 
+       /* allocate first slot of task's xol_area for the return probes */
+       set_bit(0, area->bitmap);
+       copy_to_page(area->page, 0, &insn, UPROBE_SWBP_INSN_SIZE);
+       atomic_set(&area->slot_count, 1);
        init_waitqueue_head(&area->wq);
+
        if (!xol_add_vma(area))
                return area;
 
@@ -1216,9 +1240,7 @@ static unsigned long xol_take_insn_slot(struct xol_area *area)
 static unsigned long xol_get_insn_slot(struct uprobe *uprobe)
 {
        struct xol_area *area;
-       unsigned long offset;
        unsigned long xol_vaddr;
-       void *vaddr;
 
        area = get_xol_area();
        if (!area)
@@ -1229,10 +1251,7 @@ static unsigned long xol_get_insn_slot(struct uprobe *uprobe)
                return 0;
 
        /* Initialize the slot */
-       offset = xol_vaddr & ~PAGE_MASK;
-       vaddr = kmap_atomic(area->page);
-       memcpy(vaddr + offset, uprobe->arch.insn, MAX_UINSN_BYTES);
-       kunmap_atomic(vaddr);
+       copy_to_page(area->page, xol_vaddr, uprobe->arch.insn, MAX_UINSN_BYTES);
        /*
         * We probably need flush_icache_user_range() but it needs vma.
         * This should work on supported architectures too.
@@ -1333,6 +1352,25 @@ static struct uprobe_task *get_utask(void)
        return current->utask;
 }
 
+/*
+ * Current area->vaddr notion assume the trampoline address is always
+ * equal area->vaddr.
+ *
+ * Returns -1 in case the xol_area is not allocated.
+ */
+static unsigned long get_trampoline_vaddr(void)
+{
+       struct xol_area *area;
+       unsigned long trampoline_vaddr = -1;
+
+       area = current->mm->uprobes_state.xol_area;
+       smp_read_barrier_depends();
+       if (area)
+               trampoline_vaddr = area->vaddr;
+
+       return trampoline_vaddr;
+}
+
 /* Prepare to single-step probed instruction out of line. */
 static int
 pre_ssout(struct uprobe *uprobe, struct pt_regs *regs, unsigned long bp_vaddr)
@@ -1431,7 +1469,7 @@ static void mmf_recalc_uprobes(struct mm_struct *mm)
        clear_bit(MMF_HAS_UPROBES, &mm->flags);
 }
 
-static int is_swbp_at_addr(struct mm_struct *mm, unsigned long vaddr)
+static int is_trap_at_addr(struct mm_struct *mm, unsigned long vaddr)
 {
        struct page *page;
        uprobe_opcode_t opcode;
@@ -1449,10 +1487,11 @@ static int is_swbp_at_addr(struct mm_struct *mm, unsigned long vaddr)
        if (result < 0)
                return result;
 
-       copy_opcode(page, vaddr, &opcode);
+       copy_from_page(page, vaddr, &opcode, UPROBE_SWBP_INSN_SIZE);
        put_page(page);
  out:
-       return is_swbp_insn(&opcode);
+       /* This needs to return true for any variant of the trap insn */
+       return is_trap_insn(&opcode);
 }
 
 static struct uprobe *find_active_uprobe(unsigned long bp_vaddr, int *is_swbp)
@@ -1465,14 +1504,14 @@ static struct uprobe *find_active_uprobe(unsigned long bp_vaddr, int *is_swbp)
        vma = find_vma(mm, bp_vaddr);
        if (vma && vma->vm_start <= bp_vaddr) {
                if (valid_vma(vma, false)) {
-                       struct inode *inode = vma->vm_file->f_mapping->host;
+                       struct inode *inode = file_inode(vma->vm_file);
                        loff_t offset = vaddr_to_offset(vma, bp_vaddr);
 
                        uprobe = find_uprobe(inode, offset);
                }
 
                if (!uprobe)
-                       *is_swbp = is_swbp_at_addr(mm, bp_vaddr);
+                       *is_swbp = is_trap_at_addr(mm, bp_vaddr);
        } else {
                *is_swbp = -EFAULT;
        }
@@ -1491,10 +1530,13 @@ static void handler_chain(struct uprobe *uprobe, struct pt_regs *regs)
 
        down_read(&uprobe->register_rwsem);
        for (uc = uprobe->consumers; uc; uc = uc->next) {
-               int rc = uc->handler(uc, regs);
+               int rc = 0;
 
-               WARN(rc & ~UPROBE_HANDLER_MASK,
-                       "bad rc=0x%x from %pf()\n", rc, uc->handler);
+               if (uc->handler) {
+                       rc = uc->handler(uc, regs);
+                       WARN(rc & ~UPROBE_HANDLER_MASK,
+                               "bad rc=0x%x from %pf()\n", rc, uc->handler);
+               }
                remove &= rc;
        }