proc/kcore: clean up ELF header generation
[sfrench/cifs-2.6.git] / fs / proc / kcore.c
index e64ecb9f272090bf6b23772a0d36e56b63b8106a..808ef9afd084004928f64327151b8fcae02aa9a5 100644 (file)
@@ -49,32 +49,23 @@ static struct proc_dir_entry *proc_root_kcore;
 #define        kc_offset_to_vaddr(o) ((o) + PAGE_OFFSET)
 #endif
 
-/* An ELF note in memory */
-struct memelfnote
-{
-       const char *name;
-       int type;
-       unsigned int datasz;
-       void *data;
-};
-
 static LIST_HEAD(kclist_head);
-static DEFINE_RWLOCK(kclist_lock);
+static DECLARE_RWSEM(kclist_lock);
 static int kcore_need_update = 1;
 
-void
-kclist_add(struct kcore_list *new, void *addr, size_t size, int type)
+/* This doesn't grab kclist_lock, so it should only be used at init time. */
+void __init kclist_add(struct kcore_list *new, void *addr, size_t size,
+                      int type)
 {
        new->addr = (unsigned long)addr;
        new->size = size;
        new->type = type;
 
-       write_lock(&kclist_lock);
        list_add_tail(&new->list, &kclist_head);
-       write_unlock(&kclist_lock);
 }
 
-static size_t get_kcore_size(int *nphdr, size_t *elf_buflen)
+static size_t get_kcore_size(int *nphdr, size_t *phdrs_len, size_t *notes_len,
+                            size_t *data_offset)
 {
        size_t try, size;
        struct kcore_list *m;
@@ -88,65 +79,26 @@ static size_t get_kcore_size(int *nphdr, size_t *elf_buflen)
                        size = try;
                *nphdr = *nphdr + 1;
        }
-       *elf_buflen =   sizeof(struct elfhdr) + 
-                       (*nphdr + 2)*sizeof(struct elf_phdr) + 
-                       3 * ((sizeof(struct elf_note)) +
-                            roundup(sizeof(CORE_STR), 4)) +
-                       roundup(sizeof(struct elf_prstatus), 4) +
-                       roundup(sizeof(struct elf_prpsinfo), 4) +
-                       roundup(arch_task_struct_size, 4);
-       *elf_buflen = PAGE_ALIGN(*elf_buflen);
-       return size + *elf_buflen;
-}
-
-static void free_kclist_ents(struct list_head *head)
-{
-       struct kcore_list *tmp, *pos;
-
-       list_for_each_entry_safe(pos, tmp, head, list) {
-               list_del(&pos->list);
-               kfree(pos);
-       }
-}
-/*
- * Replace all KCORE_RAM/KCORE_VMEMMAP information with passed list.
- */
-static void __kcore_update_ram(struct list_head *list)
-{
-       int nphdr;
-       size_t size;
-       struct kcore_list *tmp, *pos;
-       LIST_HEAD(garbage);
 
-       write_lock(&kclist_lock);
-       if (kcore_need_update) {
-               list_for_each_entry_safe(pos, tmp, &kclist_head, list) {
-                       if (pos->type == KCORE_RAM
-                               || pos->type == KCORE_VMEMMAP)
-                               list_move(&pos->list, &garbage);
-               }
-               list_splice_tail(list, &kclist_head);
-       } else
-               list_splice(list, &garbage);
-       kcore_need_update = 0;
-       proc_root_kcore->size = get_kcore_size(&nphdr, &size);
-       write_unlock(&kclist_lock);
-
-       free_kclist_ents(&garbage);
+       *phdrs_len = *nphdr * sizeof(struct elf_phdr);
+       *notes_len = (3 * (sizeof(struct elf_note) + ALIGN(sizeof(CORE_STR), 4)) +
+                     ALIGN(sizeof(struct elf_prstatus), 4) +
+                     ALIGN(sizeof(struct elf_prpsinfo), 4) +
+                     ALIGN(arch_task_struct_size, 4));
+       *data_offset = PAGE_ALIGN(sizeof(struct elfhdr) + *phdrs_len +
+                                 *notes_len);
+       return *data_offset + size;
 }
 
-
 #ifdef CONFIG_HIGHMEM
 /*
  * If no highmem, we can assume [0...max_low_pfn) continuous range of memory
  * because memory hole is not as big as !HIGHMEM case.
  * (HIGHMEM is special because part of memory is _invisible_ from the kernel.)
  */
-static int kcore_update_ram(void)
+static int kcore_ram_list(struct list_head *head)
 {
-       LIST_HEAD(head);
        struct kcore_list *ent;
-       int ret = 0;
 
        ent = kmalloc(sizeof(*ent), GFP_KERNEL);
        if (!ent)
@@ -154,9 +106,8 @@ static int kcore_update_ram(void)
        ent->addr = (unsigned long)__va(0);
        ent->size = max_low_pfn << PAGE_SHIFT;
        ent->type = KCORE_RAM;
-       list_add(&ent->list, &head);
-       __kcore_update_ram(&head);
-       return ret;
+       list_add(&ent->list, head);
+       return 0;
 }
 
 #else /* !CONFIG_HIGHMEM */
@@ -255,11 +206,10 @@ free_out:
        return 1;
 }
 
-static int kcore_update_ram(void)
+static int kcore_ram_list(struct list_head *list)
 {
        int nid, ret;
        unsigned long end_pfn;
-       LIST_HEAD(head);
 
        /* Not inialized....update now */
        /* find out "max pfn" */
@@ -271,258 +221,237 @@ static int kcore_update_ram(void)
                        end_pfn = node_end;
        }
        /* scan 0 to max_pfn */
-       ret = walk_system_ram_range(0, end_pfn, &head, kclist_add_private);
-       if (ret) {
-               free_kclist_ents(&head);
+       ret = walk_system_ram_range(0, end_pfn, list, kclist_add_private);
+       if (ret)
                return -ENOMEM;
-       }
-       __kcore_update_ram(&head);
-       return ret;
+       return 0;
 }
 #endif /* CONFIG_HIGHMEM */
 
-/*****************************************************************************/
-/*
- * determine size of ELF note
- */
-static int notesize(struct memelfnote *en)
-{
-       int sz;
-
-       sz = sizeof(struct elf_note);
-       sz += roundup((strlen(en->name) + 1), 4);
-       sz += roundup(en->datasz, 4);
-
-       return sz;
-} /* end notesize() */
-
-/*****************************************************************************/
-/*
- * store a note in the header buffer
- */
-static char *storenote(struct memelfnote *men, char *bufp)
+static int kcore_update_ram(void)
 {
-       struct elf_note en;
-
-#define DUMP_WRITE(addr,nr) do { memcpy(bufp,addr,nr); bufp += nr; } while(0)
-
-       en.n_namesz = strlen(men->name) + 1;
-       en.n_descsz = men->datasz;
-       en.n_type = men->type;
-
-       DUMP_WRITE(&en, sizeof(en));
-       DUMP_WRITE(men->name, en.n_namesz);
-
-       /* XXX - cast from long long to long to avoid need for libgcc.a */
-       bufp = (char*) roundup((unsigned long)bufp,4);
-       DUMP_WRITE(men->data, men->datasz);
-       bufp = (char*) roundup((unsigned long)bufp,4);
-
-#undef DUMP_WRITE
-
-       return bufp;
-} /* end storenote() */
+       LIST_HEAD(list);
+       LIST_HEAD(garbage);
+       int nphdr;
+       size_t phdrs_len, notes_len, data_offset;
+       struct kcore_list *tmp, *pos;
+       int ret = 0;
 
-/*
- * store an ELF coredump header in the supplied buffer
- * nphdr is the number of elf_phdr to insert
- */
-static void elf_kcore_store_hdr(char *bufp, int nphdr, int dataoff)
-{
-       struct elf_prstatus prstatus;   /* NT_PRSTATUS */
-       struct elf_prpsinfo prpsinfo;   /* NT_PRPSINFO */
-       struct elf_phdr *nhdr, *phdr;
-       struct elfhdr *elf;
-       struct memelfnote notes[3];
-       off_t offset = 0;
-       struct kcore_list *m;
+       down_write(&kclist_lock);
+       if (!xchg(&kcore_need_update, 0))
+               goto out;
 
-       /* setup ELF header */
-       elf = (struct elfhdr *) bufp;
-       bufp += sizeof(struct elfhdr);
-       offset += sizeof(struct elfhdr);
-       memcpy(elf->e_ident, ELFMAG, SELFMAG);
-       elf->e_ident[EI_CLASS]  = ELF_CLASS;
-       elf->e_ident[EI_DATA]   = ELF_DATA;
-       elf->e_ident[EI_VERSION]= EV_CURRENT;
-       elf->e_ident[EI_OSABI] = ELF_OSABI;
-       memset(elf->e_ident+EI_PAD, 0, EI_NIDENT-EI_PAD);
-       elf->e_type     = ET_CORE;
-       elf->e_machine  = ELF_ARCH;
-       elf->e_version  = EV_CURRENT;
-       elf->e_entry    = 0;
-       elf->e_phoff    = sizeof(struct elfhdr);
-       elf->e_shoff    = 0;
-       elf->e_flags    = ELF_CORE_EFLAGS;
-       elf->e_ehsize   = sizeof(struct elfhdr);
-       elf->e_phentsize= sizeof(struct elf_phdr);
-       elf->e_phnum    = nphdr;
-       elf->e_shentsize= 0;
-       elf->e_shnum    = 0;
-       elf->e_shstrndx = 0;
-
-       /* setup ELF PT_NOTE program header */
-       nhdr = (struct elf_phdr *) bufp;
-       bufp += sizeof(struct elf_phdr);
-       offset += sizeof(struct elf_phdr);
-       nhdr->p_type    = PT_NOTE;
-       nhdr->p_offset  = 0;
-       nhdr->p_vaddr   = 0;
-       nhdr->p_paddr   = 0;
-       nhdr->p_filesz  = 0;
-       nhdr->p_memsz   = 0;
-       nhdr->p_flags   = 0;
-       nhdr->p_align   = 0;
-
-       /* setup ELF PT_LOAD program header for every area */
-       list_for_each_entry(m, &kclist_head, list) {
-               phdr = (struct elf_phdr *) bufp;
-               bufp += sizeof(struct elf_phdr);
-               offset += sizeof(struct elf_phdr);
-
-               phdr->p_type    = PT_LOAD;
-               phdr->p_flags   = PF_R|PF_W|PF_X;
-               phdr->p_offset  = kc_vaddr_to_offset(m->addr) + dataoff;
-               phdr->p_vaddr   = (size_t)m->addr;
-               if (m->type == KCORE_RAM || m->type == KCORE_TEXT)
-                       phdr->p_paddr   = __pa(m->addr);
-               else
-                       phdr->p_paddr   = (elf_addr_t)-1;
-               phdr->p_filesz  = phdr->p_memsz = m->size;
-               phdr->p_align   = PAGE_SIZE;
+       ret = kcore_ram_list(&list);
+       if (ret) {
+               /* Couldn't get the RAM list, try again next time. */
+               WRITE_ONCE(kcore_need_update, 1);
+               list_splice_tail(&list, &garbage);
+               goto out;
        }
 
-       /*
-        * Set up the notes in similar form to SVR4 core dumps made
-        * with info from their /proc.
-        */
-       nhdr->p_offset  = offset;
-
-       /* set up the process status */
-       notes[0].name = CORE_STR;
-       notes[0].type = NT_PRSTATUS;
-       notes[0].datasz = sizeof(struct elf_prstatus);
-       notes[0].data = &prstatus;
-
-       memset(&prstatus, 0, sizeof(struct elf_prstatus));
-
-       nhdr->p_filesz  = notesize(&notes[0]);
-       bufp = storenote(&notes[0], bufp);
-
-       /* set up the process info */
-       notes[1].name   = CORE_STR;
-       notes[1].type   = NT_PRPSINFO;
-       notes[1].datasz = sizeof(struct elf_prpsinfo);
-       notes[1].data   = &prpsinfo;
-
-       memset(&prpsinfo, 0, sizeof(struct elf_prpsinfo));
-       prpsinfo.pr_state       = 0;
-       prpsinfo.pr_sname       = 'R';
-       prpsinfo.pr_zomb        = 0;
-
-       strcpy(prpsinfo.pr_fname, "vmlinux");
-       strlcpy(prpsinfo.pr_psargs, saved_command_line, sizeof(prpsinfo.pr_psargs));
-
-       nhdr->p_filesz  += notesize(&notes[1]);
-       bufp = storenote(&notes[1], bufp);
+       list_for_each_entry_safe(pos, tmp, &kclist_head, list) {
+               if (pos->type == KCORE_RAM || pos->type == KCORE_VMEMMAP)
+                       list_move(&pos->list, &garbage);
+       }
+       list_splice_tail(&list, &kclist_head);
 
-       /* set up the task structure */
-       notes[2].name   = CORE_STR;
-       notes[2].type   = NT_TASKSTRUCT;
-       notes[2].datasz = arch_task_struct_size;
-       notes[2].data   = current;
+       proc_root_kcore->size = get_kcore_size(&nphdr, &phdrs_len, &notes_len,
+                                              &data_offset);
 
-       nhdr->p_filesz  += notesize(&notes[2]);
-       bufp = storenote(&notes[2], bufp);
+out:
+       up_write(&kclist_lock);
+       list_for_each_entry_safe(pos, tmp, &garbage, list) {
+               list_del(&pos->list);
+               kfree(pos);
+       }
+       return ret;
+}
 
-} /* end elf_kcore_store_hdr() */
+static void append_kcore_note(char *notes, size_t *i, const char *name,
+                             unsigned int type, const void *desc,
+                             size_t descsz)
+{
+       struct elf_note *note = (struct elf_note *)&notes[*i];
+
+       note->n_namesz = strlen(name) + 1;
+       note->n_descsz = descsz;
+       note->n_type = type;
+       *i += sizeof(*note);
+       memcpy(&notes[*i], name, note->n_namesz);
+       *i = ALIGN(*i + note->n_namesz, 4);
+       memcpy(&notes[*i], desc, descsz);
+       *i = ALIGN(*i + descsz, 4);
+}
 
-/*****************************************************************************/
-/*
- * read from the ELF header and then kernel memory
- */
 static ssize_t
 read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
 {
        char *buf = file->private_data;
-       ssize_t acc = 0;
-       size_t size, tsz;
-       size_t elf_buflen;
+       size_t phdrs_offset, notes_offset, data_offset;
+       size_t phdrs_len, notes_len;
+       struct kcore_list *m;
+       size_t tsz;
        int nphdr;
        unsigned long start;
+       size_t orig_buflen = buflen;
+       int ret = 0;
 
-       read_lock(&kclist_lock);
-       size = get_kcore_size(&nphdr, &elf_buflen);
+       down_read(&kclist_lock);
+
+       get_kcore_size(&nphdr, &phdrs_len, &notes_len, &data_offset);
+       phdrs_offset = sizeof(struct elfhdr);
+       notes_offset = phdrs_offset + phdrs_len;
+
+       /* ELF file header. */
+       if (buflen && *fpos < sizeof(struct elfhdr)) {
+               struct elfhdr ehdr = {
+                       .e_ident = {
+                               [EI_MAG0] = ELFMAG0,
+                               [EI_MAG1] = ELFMAG1,
+                               [EI_MAG2] = ELFMAG2,
+                               [EI_MAG3] = ELFMAG3,
+                               [EI_CLASS] = ELF_CLASS,
+                               [EI_DATA] = ELF_DATA,
+                               [EI_VERSION] = EV_CURRENT,
+                               [EI_OSABI] = ELF_OSABI,
+                       },
+                       .e_type = ET_CORE,
+                       .e_machine = ELF_ARCH,
+                       .e_version = EV_CURRENT,
+                       .e_phoff = sizeof(struct elfhdr),
+                       .e_flags = ELF_CORE_EFLAGS,
+                       .e_ehsize = sizeof(struct elfhdr),
+                       .e_phentsize = sizeof(struct elf_phdr),
+                       .e_phnum = nphdr,
+               };
+
+               tsz = min_t(size_t, buflen, sizeof(struct elfhdr) - *fpos);
+               if (copy_to_user(buffer, (char *)&ehdr + *fpos, tsz)) {
+                       ret = -EFAULT;
+                       goto out;
+               }
 
-       if (buflen == 0 || *fpos >= size) {
-               read_unlock(&kclist_lock);
-               return 0;
+               buffer += tsz;
+               buflen -= tsz;
+               *fpos += tsz;
        }
 
-       /* trim buflen to not go beyond EOF */
-       if (buflen > size - *fpos)
-               buflen = size - *fpos;
-
-       /* construct an ELF core header if we'll need some of it */
-       if (*fpos < elf_buflen) {
-               char * elf_buf;
-
-               tsz = elf_buflen - *fpos;
-               if (buflen < tsz)
-                       tsz = buflen;
-               elf_buf = kzalloc(elf_buflen, GFP_ATOMIC);
-               if (!elf_buf) {
-                       read_unlock(&kclist_lock);
-                       return -ENOMEM;
+       /* ELF program headers. */
+       if (buflen && *fpos < phdrs_offset + phdrs_len) {
+               struct elf_phdr *phdrs, *phdr;
+
+               phdrs = kzalloc(phdrs_len, GFP_KERNEL);
+               if (!phdrs) {
+                       ret = -ENOMEM;
+                       goto out;
                }
-               elf_kcore_store_hdr(elf_buf, nphdr, elf_buflen);
-               read_unlock(&kclist_lock);
-               if (copy_to_user(buffer, elf_buf + *fpos, tsz)) {
-                       kfree(elf_buf);
-                       return -EFAULT;
+
+               phdrs[0].p_type = PT_NOTE;
+               phdrs[0].p_offset = notes_offset;
+               phdrs[0].p_filesz = notes_len;
+
+               phdr = &phdrs[1];
+               list_for_each_entry(m, &kclist_head, list) {
+                       phdr->p_type = PT_LOAD;
+                       phdr->p_flags = PF_R | PF_W | PF_X;
+                       phdr->p_offset = kc_vaddr_to_offset(m->addr) + data_offset;
+                       phdr->p_vaddr = (size_t)m->addr;
+                       if (m->type == KCORE_RAM)
+                               phdr->p_paddr = __pa(m->addr);
+                       else if (m->type == KCORE_TEXT)
+                               phdr->p_paddr = __pa_symbol(m->addr);
+                       else
+                               phdr->p_paddr = (elf_addr_t)-1;
+                       phdr->p_filesz = phdr->p_memsz = m->size;
+                       phdr->p_align = PAGE_SIZE;
+                       phdr++;
+               }
+
+               tsz = min_t(size_t, buflen, phdrs_offset + phdrs_len - *fpos);
+               if (copy_to_user(buffer, (char *)phdrs + *fpos - phdrs_offset,
+                                tsz)) {
+                       kfree(phdrs);
+                       ret = -EFAULT;
+                       goto out;
                }
-               kfree(elf_buf);
+               kfree(phdrs);
+
+               buffer += tsz;
                buflen -= tsz;
                *fpos += tsz;
-               buffer += tsz;
-               acc += tsz;
+       }
+
+       /* ELF note segment. */
+       if (buflen && *fpos < notes_offset + notes_len) {
+               struct elf_prstatus prstatus = {};
+               struct elf_prpsinfo prpsinfo = {
+                       .pr_sname = 'R',
+                       .pr_fname = "vmlinux",
+               };
+               char *notes;
+               size_t i = 0;
+
+               strlcpy(prpsinfo.pr_psargs, saved_command_line,
+                       sizeof(prpsinfo.pr_psargs));
+
+               notes = kzalloc(notes_len, GFP_KERNEL);
+               if (!notes) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
 
-               /* leave now if filled buffer already */
-               if (buflen == 0)
-                       return acc;
-       } else
-               read_unlock(&kclist_lock);
+               append_kcore_note(notes, &i, CORE_STR, NT_PRSTATUS, &prstatus,
+                                 sizeof(prstatus));
+               append_kcore_note(notes, &i, CORE_STR, NT_PRPSINFO, &prpsinfo,
+                                 sizeof(prpsinfo));
+               append_kcore_note(notes, &i, CORE_STR, NT_TASKSTRUCT, current,
+                                 arch_task_struct_size);
+
+               tsz = min_t(size_t, buflen, notes_offset + notes_len - *fpos);
+               if (copy_to_user(buffer, notes + *fpos - notes_offset, tsz)) {
+                       kfree(notes);
+                       ret = -EFAULT;
+                       goto out;
+               }
+               kfree(notes);
+
+               buffer += tsz;
+               buflen -= tsz;
+               *fpos += tsz;
+       }
 
        /*
         * Check to see if our file offset matches with any of
         * the addresses in the elf_phdr on our list.
         */
-       start = kc_offset_to_vaddr(*fpos - elf_buflen);
+       start = kc_offset_to_vaddr(*fpos - data_offset);
        if ((tsz = (PAGE_SIZE - (start & ~PAGE_MASK))) > buflen)
                tsz = buflen;
-               
-       while (buflen) {
-               struct kcore_list *m;
 
-               read_lock(&kclist_lock);
+       while (buflen) {
                list_for_each_entry(m, &kclist_head, list) {
                        if (start >= m->addr && start < (m->addr+m->size))
                                break;
                }
-               read_unlock(&kclist_lock);
 
                if (&m->list == &kclist_head) {
-                       if (clear_user(buffer, tsz))
-                               return -EFAULT;
+                       if (clear_user(buffer, tsz)) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
                } else if (m->type == KCORE_VMALLOC) {
                        vread(buf, (char *)start, tsz);
                        /* we have to zero-fill user buffer even if no read */
-                       if (copy_to_user(buffer, buf, tsz))
-                               return -EFAULT;
+                       if (copy_to_user(buffer, buf, tsz)) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
                } else if (m->type == KCORE_USER) {
                        /* User page is handled prior to normal kernel page: */
-                       if (copy_to_user(buffer, (char *)start, tsz))
-                               return -EFAULT;
+                       if (copy_to_user(buffer, (char *)start, tsz)) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
                } else {
                        if (kern_addr_valid(start)) {
                                /*
@@ -530,29 +459,37 @@ read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos)
                                 * hardened user copy kernel text checks.
                                 */
                                if (probe_kernel_read(buf, (void *) start, tsz)) {
-                                       if (clear_user(buffer, tsz))
-                                               return -EFAULT;
+                                       if (clear_user(buffer, tsz)) {
+                                               ret = -EFAULT;
+                                               goto out;
+                                       }
                                } else {
-                                       if (copy_to_user(buffer, buf, tsz))
-                                               return -EFAULT;
+                                       if (copy_to_user(buffer, buf, tsz)) {
+                                               ret = -EFAULT;
+                                               goto out;
+                                       }
                                }
                        } else {
-                               if (clear_user(buffer, tsz))
-                                       return -EFAULT;
+                               if (clear_user(buffer, tsz)) {
+                                       ret = -EFAULT;
+                                       goto out;
+                               }
                        }
                }
                buflen -= tsz;
                *fpos += tsz;
                buffer += tsz;
-               acc += tsz;
                start += tsz;
                tsz = (buflen > PAGE_SIZE ? PAGE_SIZE : buflen);
        }
 
-       return acc;
+out:
+       up_read(&kclist_lock);
+       if (ret)
+               return ret;
+       return orig_buflen - buflen;
 }
 
-
 static int open_kcore(struct inode *inode, struct file *filp)
 {
        if (!capable(CAP_SYS_RAWIO))
@@ -592,9 +529,8 @@ static int __meminit kcore_callback(struct notifier_block *self,
        switch (action) {
        case MEM_ONLINE:
        case MEM_OFFLINE:
-               write_lock(&kclist_lock);
                kcore_need_update = 1;
-               write_unlock(&kclist_lock);
+               break;
        }
        return NOTIFY_OK;
 }