kernel - Remove double unlock in elf core dumping code
[dragonfly.git] / sys / kern / imgact_elf.c
index fa6ec29..4c84950 100644 (file)
@@ -83,10 +83,14 @@ static int __elfN(load_section)(struct proc *p,
     vm_offset_t offset, caddr_t vmaddr, size_t memsz, size_t filsz,
     vm_prot_t prot);
 static int __CONCAT(exec_, __elfN(imgact))(struct image_params *imgp);
+static boolean_t __elfN(bsd_trans_osrel)(const Elf_Note *note,
+    int32_t *osrel);
 static boolean_t __elfN(check_note)(struct image_params *imgp,
     Elf_Brandnote *checknote, int32_t *osrel);
 static boolean_t check_PT_NOTE(struct image_params *imgp,
     Elf_Brandnote *checknote, int32_t *osrel, const Elf_Phdr * pnote);
+static boolean_t extract_interpreter(struct image_params *imgp,
+    const Elf_Phdr *pinterpreter, char *data);
 
 static int elf_legacy_coredump = 0;
 static int __elfN(fallback_brand) = -1;
@@ -116,7 +120,8 @@ Elf_Brandnote __elfN(dragonfly_brandnote) = {
        .hdr.n_descsz   = sizeof(int32_t),
        .hdr.n_type     = 1,
        .vendor         = DRAGONFLY_ABI_VENDOR,
-       .flags          = BN_CAN_FETCH_OSREL,
+       .flags          = BN_TRANSLATE_OSREL,
+       .trans_osrel    = __elfN(bsd_trans_osrel),
 };
 
 Elf_Brandnote __elfN(freebsd_brandnote) = {
@@ -124,7 +129,8 @@ Elf_Brandnote __elfN(freebsd_brandnote) = {
        .hdr.n_descsz   = sizeof(int32_t),
        .hdr.n_type     = 1,
        .vendor         = FREEBSD_ABI_VENDOR,
-       .flags          = BN_CAN_FETCH_OSREL,
+       .flags          = BN_TRANSLATE_OSREL,
+       .trans_osrel    = __elfN(bsd_trans_osrel),
 };
 
 int
@@ -245,6 +251,8 @@ __elfN(load_section)(struct proc *p, struct vmspace *vmspace, struct vnode *vp,
        object = vp->v_object;
        error = 0;
 
+       vm_object_hold(object);
+
        /*
         * It's necessary to fail if the filsz + offset taken from the
         * header is greater than the actual file pager object's size.
@@ -256,6 +264,7 @@ __elfN(load_section)(struct proc *p, struct vmspace *vmspace, struct vnode *vp,
         */
        if ((off_t)filsz + offset > vp->v_filesize || filsz > memsz) {
                uprintf("elf_load_section: truncated ELF file\n");
+               vm_object_drop(object);
                return (ENOEXEC);
        }
 
@@ -274,7 +283,7 @@ __elfN(load_section)(struct proc *p, struct vmspace *vmspace, struct vnode *vp,
                map_len = round_page(offset+filsz) - file_addr;
 
        if (map_len != 0) {
-               vm_object_reference(object);
+               vm_object_reference_locked(object);
 
                /* cow flags: don't dump readonly sections in core */
                cow = MAP_COPY_ON_WRITE | MAP_PREFAULT |
@@ -294,11 +303,13 @@ __elfN(load_section)(struct proc *p, struct vmspace *vmspace, struct vnode *vp,
                vm_map_entry_release(count);
                if (rv != KERN_SUCCESS) {
                        vm_object_deallocate(object);
+                       vm_object_drop(object);
                        return (EINVAL);
                }
 
                /* we can stop now if we've covered it all */
                if (memsz == filsz) {
+                       vm_object_drop(object);
                        return (0);
                }
        }
@@ -327,6 +338,7 @@ __elfN(load_section)(struct proc *p, struct vmspace *vmspace, struct vnode *vp,
                vm_map_unlock(&vmspace->vm_map);
                vm_map_entry_release(count);
                if (rv != KERN_SUCCESS) {
+                       vm_object_drop(object);
                        return (EINVAL);
                }
        }
@@ -346,15 +358,17 @@ __elfN(load_section)(struct proc *p, struct vmspace *vmspace, struct vnode *vp,
                        vm_page_unhold(m);
                }
                if (error) {
+                       vm_object_drop(object);
                        return (error);
                }
        }
 
+       vm_object_drop(object);
        /*
         * set it to the specified protection
         */
-       vm_map_protect(&vmspace->vm_map, map_addr, map_addr + map_len,  prot,
-                      FALSE);
+       vm_map_protect(&vmspace->vm_map, map_addr, map_addr + map_len,
+                      prot, FALSE);
 
        return (error);
 }
@@ -587,7 +601,9 @@ __CONCAT(exec_,__elfN(imgact))(struct image_params *imgp)
        u_long addr, baddr, et_dyn_addr, entry = 0, proghdr = 0;
        int32_t osrel = 0;
        int error = 0, i, n;
-       const char *interp = NULL, *newinterp = NULL;
+       boolean_t failure;
+       char *interp = NULL;
+       const char *newinterp = NULL;
        Elf_Brandinfo *brand_info;
        char *path;
 
@@ -624,11 +640,25 @@ __CONCAT(exec_,__elfN(imgact))(struct image_params *imgp)
                        continue;
                }
                if (phdr[i].p_type == PT_INTERP) {
-                       /* Path to interpreter */
-                       if (phdr[i].p_filesz > MAXPATHLEN ||
-                           phdr[i].p_offset + phdr[i].p_filesz > PAGE_SIZE)
+                       /*
+                        * If interp is already defined there are more than
+                        * one PT_INTERP program headers present.  Take only
+                        * the first one and ignore the rest.
+                        */
+                       if (interp != NULL)
+                               continue;
+
+                       if (phdr[i].p_filesz == 0 ||
+                           phdr[i].p_filesz > PAGE_SIZE ||
+                           phdr[i].p_filesz > MAXPATHLEN)
+                               return (ENOEXEC);
+
+                       interp = kmalloc(phdr[i].p_filesz, M_TEMP, M_WAITOK);
+                       failure = extract_interpreter(imgp, &phdr[i], interp);
+                       if (failure) {
+                               kfree(interp, M_TEMP);
                                return (ENOEXEC);
-                       interp = imgp->image_header + phdr[i].p_offset;
+                       }
                        continue;
                }
        }
@@ -637,11 +667,16 @@ __CONCAT(exec_,__elfN(imgact))(struct image_params *imgp)
        if (brand_info == NULL) {
                uprintf("ELF binary type \"%u\" not known.\n",
                    hdr->e_ident[EI_OSABI]);
+               if (interp != NULL)
+                       kfree(interp, M_TEMP);
                return (ENOEXEC);
        }
        if (hdr->e_type == ET_DYN) {
-               if ((brand_info->flags & BI_CAN_EXEC_DYN) == 0)
+               if ((brand_info->flags & BI_CAN_EXEC_DYN) == 0) {
+                       if (interp != NULL)
+                               kfree(interp, M_TEMP);
                        return (ENOEXEC);
+                }
                /*
                 * Honour the base load address from the dso if it is
                 * non-zero for some reason.
@@ -690,8 +725,11 @@ __CONCAT(exec_,__elfN(imgact))(struct image_params *imgp)
                                        (caddr_t)phdr[i].p_vaddr + et_dyn_addr,
                                        phdr[i].p_memsz,
                                        phdr[i].p_filesz,
-                                       prot)) != 0)
+                                       prot)) != 0) {
+                                if (interp != NULL)
+                                        kfree (interp, M_TEMP);
                                return (error);
+                        }
 
                        /*
                         * If this segment contains the program headers,
@@ -746,8 +784,10 @@ __CONCAT(exec_,__elfN(imgact))(struct image_params *imgp)
                            text_size > maxtsiz ||
                            total_size >
                            imgp->proc->p_rlimit[RLIMIT_VMEM].rlim_cur) {
+                               if (interp != NULL)
+                                       kfree(interp, M_TEMP);
                                error = ENOMEM;
-                                return (error);
+                               return (error);
                        }
                        break;
                case PT_PHDR:   /* Program header table info */
@@ -795,8 +835,10 @@ __CONCAT(exec_,__elfN(imgact))(struct image_params *imgp)
                }
                if (error != 0) {
                        uprintf("ELF interpreter %s not found\n", interp);
+                       kfree(interp, M_TEMP);
                        return (error);
                }
+               kfree(interp, M_TEMP);
        } else
                addr = et_dyn_addr;
 
@@ -841,6 +883,7 @@ __elfN(dragonfly_fixup)(register_t **stack_base, struct image_params *imgp)
        AUXARGS_ENTRY(pos, AT_BASE, args->base);
        if (imgp->execpathp != 0)
                AUXARGS_ENTRY(pos, AT_EXECPATH, imgp->execpathp);
+       AUXARGS_ENTRY(pos, AT_OSRELDATE, osreldate);
        AUXARGS_ENTRY(pos, AT_NULL, 0);
 
        kfree(imgp->auxargs, M_TEMP);
@@ -923,7 +966,6 @@ __elfN(coredump)(struct lwp *lp, int sig, struct vnode *vp, off_t limit)
        fp->f_flag = O_CREAT|O_WRONLY|O_NOFOLLOW;
        fp->f_ops = &vnode_fileops;
        fp->f_data = vp;
-       vn_unlock(vp);
        
        error = generic_elf_coredump(lp, sig, fp, limit);
 
@@ -1145,6 +1187,8 @@ each_segment(struct proc *p, segment_callback func, void *closure, int writable)
        for (entry = map->header.next; error == 0 && entry != &map->header;
            entry = entry->next) {
                vm_object_t obj;
+               vm_object_t lobj;
+               vm_object_t tobj;
 
                /*
                 * Don't dump inaccessible mappings, deal with legacy
@@ -1177,17 +1221,40 @@ each_segment(struct proc *p, segment_callback func, void *closure, int writable)
                if ((obj = entry->object.vm_object) == NULL)
                        continue;
 
-               /* Find the deepest backing object. */
-               while (obj->backing_object != NULL)
-                       obj = obj->backing_object;
-
-               /* Ignore memory-mapped devices and such things. */
-               if (obj->type != OBJT_DEFAULT &&
-                   obj->type != OBJT_SWAP &&
-                   obj->type != OBJT_VNODE)
-                       continue;
+               /*
+                * Find the bottom-most object, leaving the base object
+                * and the bottom-most object held (but only one hold
+                * if they happen to be the same).
+                */
+               vm_object_hold(obj);
+
+               lobj = obj;
+               while (lobj && (tobj = lobj->backing_object) != NULL) {
+                       KKASSERT(tobj != obj);
+                       vm_object_hold(tobj);
+                       if (tobj == lobj->backing_object) {
+                               if (lobj != obj) {
+                                       vm_object_lock_swap();
+                                       vm_object_drop(lobj);
+                               }
+                               lobj = tobj;
+                       } else {
+                               vm_object_drop(tobj);
+                       }
+               }
 
-               error = (*func)(entry, closure);
+               /*
+                * The callback only applies to default, swap, or vnode
+                * objects.  Other types of objects such as memory-mapped
+                * devices are ignored.
+                */
+               if (lobj->type == OBJT_DEFAULT || lobj->type == OBJT_SWAP ||
+                   lobj->type == OBJT_VNODE) {
+                       error = (*func)(entry, closure);
+               }
+               if (lobj != obj)
+                       vm_object_drop(lobj);
+               vm_object_drop(obj);
        }
        return (error);
 }
@@ -1694,11 +1761,9 @@ check_PT_NOTE(struct image_params *imgp, Elf_Brandnote *checknote,
                    && (strncmp(checknote->vendor, note_name,
                        checknote->hdr.n_namesz) == 0)) {
                        /* Fetch osreldata from ABI.note-tag */
-                       if ((checknote->flags & BN_CAN_FETCH_OSREL) != 0 &&
-                               osrel != NULL)
-                               *osrel = *(const int32_t *) (note_name +
-                                       roundup2(checknote->hdr.n_namesz,
-                                       sizeof(Elf32_Addr)));
+                       if ((checknote->flags & BN_TRANSLATE_OSREL) != 0 &&
+                           checknote->trans_osrel != NULL)
+                               checknote->trans_osrel(note, osrel);
                        found = TRUE;
                        break;
                }
@@ -1715,6 +1780,69 @@ check_PT_NOTE(struct image_params *imgp, Elf_Brandnote *checknote,
        return (found);
 }
 
+/*
+ * The interpreter program header may be located beyond the first page, so
+ * regardless of its location, a copy of the interpreter path is created so
+ * that it may be safely referenced by the calling function in all case.  The
+ * memory is allocated by calling function, and the copying is done here.
+ */
+static boolean_t
+extract_interpreter(struct image_params *imgp, const Elf_Phdr *pinterpreter,
+    char *data)
+{
+       boolean_t limited_to_first_page;
+       const boolean_t result_success = FALSE;
+       const boolean_t result_failure = TRUE;
+       __ElfN(Off) pathloc, firstloc;
+       __ElfN(Size) pathsz, firstlen, endbyte;
+       struct lwbuf *lwb;
+       struct lwbuf lwb_cache;
+       const char *page;
+
+       pathsz  = pinterpreter->p_filesz;
+       pathloc = pinterpreter->p_offset;
+       endbyte = pathloc + pathsz;
+
+       limited_to_first_page = pathloc < PAGE_SIZE && endbyte < PAGE_SIZE;
+       if (limited_to_first_page) {
+               bcopy(imgp->image_header + pathloc, data, pathsz);
+               return (result_success);
+       }
+
+       firstloc = pathloc & PAGE_MASK;
+       firstlen = PAGE_SIZE - firstloc;
+
+       lwb = &lwb_cache;
+       if (exec_map_page(imgp, pathloc >> PAGE_SHIFT, &lwb, &page))
+               return (result_failure);
+
+       if (firstlen < pathsz) {         /* crosses page boundary */
+               bcopy(page + firstloc, data, firstlen);
+
+               exec_unmap_page(lwb);
+               lwb = &lwb_cache;
+               if (exec_map_page(imgp, (pathloc >> PAGE_SHIFT) + 1, &lwb,
+                       &page))
+                       return (result_failure);
+               bcopy(page, data + firstlen, pathsz - firstlen);
+       } else
+               bcopy(page + firstloc, data, pathsz);
+
+       exec_unmap_page(lwb);
+       return (result_success);
+}
+
+static boolean_t
+__elfN(bsd_trans_osrel)(const Elf_Note *note, int32_t *osrel)
+{
+       uintptr_t p;
+
+       p = (uintptr_t)(note + 1);
+       p += roundup2(note->n_namesz, sizeof(Elf32_Addr));
+       *osrel = *(const int32_t *)(p);
+
+       return (TRUE);
+}
 
 /*
  * Tell kern_execve.c about it, with a little help from the linker.