diff --git a/cr-dump.c b/cr-dump.c index 91e565cb1..9c4666469 100644 --- a/cr-dump.c +++ b/cr-dump.c @@ -61,6 +61,7 @@ #include "kerndat.h" #include "stats.h" #include "mem.h" +#include "vdso.h" #include "page-pipe.h" #include "vdso.h" @@ -1484,6 +1485,12 @@ static int dump_one_task(struct pstree_item *item) } } + ret = parasite_fixup_vdso(parasite_ctl, pid, &vmas); + if (ret) { + pr_err("Can't fixup vdso VMAs (pid: %d)\n", pid); + goto err_cure_fdset; + } + ret = parasite_dump_misc_seized(parasite_ctl, &misc); if (ret) { pr_err("Can't dump misc (pid: %d)\n", pid); diff --git a/include/parasite-syscall.h b/include/parasite-syscall.h index 44415926a..be05e6fc4 100644 --- a/include/parasite-syscall.h +++ b/include/parasite-syscall.h @@ -84,4 +84,7 @@ int syscall_seized(struct parasite_ctl *ctl, int nr, unsigned long *ret, extern int __parasite_execute(struct parasite_ctl *ctl, pid_t pid, user_regs_struct_t *regs); extern bool arch_can_dump_task(pid_t pid); +extern int parasite_fixup_vdso(struct parasite_ctl *ctl, pid_t pid, + struct vm_area_list *vma_area_list); + #endif /* __CR_PARASITE_SYSCALL_H__ */ diff --git a/include/parasite.h b/include/parasite.h index d3fa28a4a..0e516e050 100644 --- a/include/parasite.h +++ b/include/parasite.h @@ -35,6 +35,7 @@ enum { PARASITE_CMD_DRAIN_FDS, PARASITE_CMD_GET_PROC_FD, PARASITE_CMD_DUMP_TTY, + PARASITE_CMD_CHECK_VDSO_MARK, PARASITE_CMD_MAX, }; @@ -60,6 +61,13 @@ struct parasite_vma_entry int prot; }; +struct parasite_vdso_vma_entry { + unsigned long start; + unsigned long len; + unsigned long proxy_addr; + int is_marked; +}; + struct parasite_dump_pages_args { unsigned int nr_vmas; unsigned int add_prot; diff --git a/include/vdso.h b/include/vdso.h index 6e1bcc4f6..88ef7133b 100644 --- a/include/vdso.h +++ b/include/vdso.h @@ -99,6 +99,13 @@ struct vdso_mark { #define INIT_VDSO_MARK(m) \ *(m) = (struct vdso_mark)VDSO_MARK_INIT +static inline bool is_vdso_mark(void *addr) +{ + struct vdso_mark *m = addr; + + return m->signature == VDSO_MARK_SIGNATURE && + m->proxy_addr != VDSO_BAD_ADDR; +} extern struct vdso_symtable vdso_sym_rt; extern u64 vdso_pfn; diff --git a/parasite-syscall.c b/parasite-syscall.c index c02f426cf..d668e9cde 100644 --- a/parasite-syscall.c +++ b/parasite-syscall.c @@ -20,9 +20,11 @@ #include "parasite.h" #include "crtools.h" #include "namespaces.h" +#include "kerndat.h" #include "pstree.h" #include "net.h" #include "mem.h" +#include "vdso.h" #include #include @@ -511,6 +513,125 @@ err: return ret; } +/* + * Find out proxy vdso vma and drop it from the list. Also + * fix vdso status on vmas if wrong status found. + */ +int parasite_fixup_vdso(struct parasite_ctl *ctl, pid_t pid, + struct vm_area_list *vma_area_list) +{ + unsigned long proxy_addr = VDSO_BAD_ADDR; + struct parasite_vdso_vma_entry *args; + struct vma_area *marked = NULL; + struct vma_area *vma; + int fd, ret = -1; + off_t off; + u64 pfn; + + args = parasite_args(ctl, struct parasite_vdso_vma_entry); + fd = open_proc(pid, "pagemap"); + if (fd < 0) + return -1; + + list_for_each_entry(vma, &vma_area_list->h, list) { + if (!vma_area_is(vma, VMA_AREA_REGULAR)) + continue; + + if ((vma->vma.prot & VDSO_PROT) != VDSO_PROT) + continue; + + /* + * I need to poke every potentially marked vma, + * otherwise if task never called for vdso functions + * page frame number won't be reported. + */ + args->start = vma->vma.start; + args->len = vma_area_len(vma); + + if (parasite_execute(PARASITE_CMD_CHECK_VDSO_MARK, ctl)) { + pr_err("vdso: Parasite failed to poke for mark\n"); + ret = -1; + goto err; + } + + off = (vma->vma.start / PAGE_SIZE) * sizeof(u64); + if (lseek(fd, off, SEEK_SET) != off) { + pr_perror("Failed to seek address %lx\n", vma->vma.start); + ret = -1; + goto err; + } + + ret = read(fd, &pfn, sizeof(pfn)); + if (ret < 0 || ret != sizeof(pfn)) { + pr_perror("Can't read pme for pid %d", pid); + ret = -1; + goto err; + } + + pfn = PME_PFRAME(pfn); + BUG_ON(!pfn); + + /* + * Defer handling marked vdso. + */ + if (unlikely(args->is_marked)) { + BUG_ON(args->proxy_addr == VDSO_BAD_ADDR); + BUG_ON(marked); + marked = vma; + proxy_addr = args->proxy_addr; + continue; + } + + /* + * Set proper VMA statuses. + */ + if (pfn == vdso_pfn) { + if (!vma_area_is(vma, VMA_AREA_VDSO)) { + pr_debug("vdso: Restore status by pfn at %lx\n", + (long)vma->vma.start); + vma->vma.status |= VMA_AREA_VDSO; + } + } else { + if (vma_area_is(vma, VMA_AREA_VDSO)) { + pr_debug("vdso: Drop mishinted status at %lx\n", + (long)vma->vma.start); + vma->vma.status &= ~VMA_AREA_VDSO; + } + } + } + + /* + * There is marked vdso, it means such vdso is autogenerated + * and must be dropped from vma list. + */ + if (marked) { + pr_debug("vdso: Found marked at %lx (proxy at %lx)\n", + (long)marked->vma.start, (long)proxy_addr); + + /* + * Don't forget to restore the proxy vdso status, since + * it's being not recognized by the kernel as vdso. + */ + list_for_each_entry(vma, &vma_area_list->h, list) { + if (vma->vma.start == proxy_addr) { + vma->vma.status |= VMA_AREA_REGULAR | VMA_AREA_VDSO; + pr_debug("vdso: Restore proxy status at %lx\n", + (long)vma->vma.start); + break; + } + } + + pr_debug("vdso: Droppping marked vdso at %lx\n", + (long)vma->vma.start); + list_del(&marked->list); + xfree(marked); + } + ret = 0; +err: + close(fd); + return ret; +} + int parasite_get_proc_fd_seized(struct parasite_ctl *ctl) { int ret = -1, fd; diff --git a/pie/parasite.c b/pie/parasite.c index 7d505ecdc..50ddf56b0 100644 --- a/pie/parasite.c +++ b/pie/parasite.c @@ -8,6 +8,7 @@ #include "syscall.h" #include "parasite.h" +#include "vdso.h" #include "log.h" #include @@ -415,6 +416,21 @@ static int parasite_cfg_log(struct parasite_log_args *args) return ret; } +static int parasite_check_vdso_mark(struct parasite_vdso_vma_entry *args) +{ + struct vdso_mark *m = (void *)args->start; + + if (is_vdso_mark(m)) { + args->is_marked = 1; + args->proxy_addr = m->proxy_addr; + } else { + args->is_marked = 0; + args->proxy_addr = VDSO_BAD_ADDR; + } + + return 0; +} + static int fini(void) { int ret; @@ -463,6 +479,8 @@ int __used parasite_service(unsigned int cmd, void *args) return parasite_get_proc_fd(); case PARASITE_CMD_DUMP_TTY: return parasite_dump_tty(args); + case PARASITE_CMD_CHECK_VDSO_MARK: + return parasite_check_vdso_mark(args); } pr_err("Unknown command to parasite: %d\n", cmd);