diff --git a/criu/include/kerndat.h b/criu/include/kerndat.h index d34ffa1e2..950755b5e 100644 --- a/criu/include/kerndat.h +++ b/criu/include/kerndat.h @@ -56,6 +56,7 @@ struct kerndat_s { unsigned long uffd_features; bool has_thp_disable; bool can_map_vdso; + bool vdso_hint_reliable; #ifdef CONFIG_VDSO struct vdso_symtable vdso_sym; #ifdef CONFIG_COMPAT diff --git a/criu/include/vdso.h b/criu/include/vdso.h index b6110e442..3c4c0ecbd 100644 --- a/criu/include/vdso.h +++ b/criu/include/vdso.h @@ -16,6 +16,7 @@ extern struct vdso_maps vdso_maps_compat; extern int vdso_init_dump(void); extern int vdso_init_restore(void); extern int kerndat_vdso_fill_symtable(void); +extern int kerndat_vdso_preserves_hint(void); extern int parasite_fixup_vdso(struct parasite_ctl *ctl, pid_t pid, struct vm_area_list *vma_area_list); @@ -30,6 +31,7 @@ extern void compat_vdso_helper(struct vdso_maps *native, int pipe_fd, #define vdso_init_dump() (0) #define vdso_init_restore() (0) #define kerndat_vdso_fill_symtable() (0) +#define kerndat_vdso_preserves_hint() (0) #define parasite_fixup_vdso(ctl, pid, vma_area_list) (0) #endif /* CONFIG_VDSO */ diff --git a/criu/kerndat.c b/criu/kerndat.c index cad2597b6..de33b049d 100644 --- a/criu/kerndat.c +++ b/criu/kerndat.c @@ -870,6 +870,9 @@ int kerndat_init(void) /* Needs kdat.compat_cr filled before */ if (!ret) ret = kerndat_vdso_fill_symtable(); + /* Depends on kerndat_vdso_fill_symtable() */ + if (!ret) + ret = kerndat_vdso_preserves_hint(); kerndat_lsm(); kerndat_mmap_min_addr(); diff --git a/criu/vdso.c b/criu/vdso.c index 64046bc16..013a9a6c2 100644 --- a/criu/vdso.c +++ b/criu/vdso.c @@ -621,3 +621,78 @@ int kerndat_vdso_fill_symtable(void) return 0; } + +/* + * On x86 pre-v3.16 kernels can lose "[vdso]" hint + * in /proc/.../maps file after mremap()'ing vdso vma. + * Depends on kerndat_vdso_fill_symtable() - assuming that + * vdso_maps and vdso_maps_compat are filled. + */ +int kerndat_vdso_preserves_hint(void) +{ + struct vdso_maps vdso_maps_after; + int status, ret = -1; + pid_t child; + + kdat.vdso_hint_reliable = 0; + + if (vdso_maps.vdso_start == VDSO_BAD_ADDR) + return 0; + + child = fork(); + if (child < 0) { + pr_perror("fork() failed"); + return -1; + } + + if (child == 0) { + unsigned long vdso_addr = vdso_maps.vdso_start; + unsigned long vdso_size = vdso_maps.sym.vdso_size; + void *new_addr; + + new_addr = mmap(0, vdso_size, PROT_NONE, + MAP_ANON | MAP_PRIVATE, -1, 0); + if (new_addr == MAP_FAILED) + exit(1); + + child = getpid(); + new_addr = (void *)syscall(SYS_mremap, vdso_addr, vdso_size, + vdso_size, MREMAP_MAYMOVE | MREMAP_FIXED, new_addr); + if (new_addr == MAP_FAILED) + syscall(SYS_exit, 2); + syscall(SYS_kill, child, SIGSTOP); + syscall(SYS_exit, 3); + } + + waitpid(child, &status, WUNTRACED); + if (WIFEXITED(status)) { + int ret = WEXITSTATUS(status); + + pr_err("Child unexpectedly exited with %d\n", ret); + goto out; + } else if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + + pr_err("Child unexpectedly signaled with %d: %s\n", + sig, strsignal(sig)); + goto out; + } else if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { + pr_err("Child is unstoppable or was stopped by other means\n"); + goto out_kill; + } + + if (vdso_parse_maps(child, &vdso_maps_after)) { + pr_err("Failed parsing maps for child helper\n"); + goto out_kill; + } + + if (vdso_maps_after.vdso_start != VDSO_BAD_ADDR) + kdat.vdso_hint_reliable = 1; + + ret = 0; +out_kill: + kill(child, SIGKILL); + waitpid(child, &status, 0); +out: + return ret; +}