diff options
author | Alexander Mikhalitsyn <alexander.mikhalitsyn@virtuozzo.com> | 2021-12-24 18:57:54 +0300 |
---|---|---|
committer | Andrei Vagin <avagin@gmail.com> | 2022-04-29 03:53:52 +0300 |
commit | f70ddab24eeb99fcbab0015c8338b673d002d5e6 (patch) | |
tree | 4d9e45d273361f01c305b9114a3d0f9cec31ad6f | |
parent | e1799e530584e9d47acb4700ef7a7a44c4af1443 (diff) |
pie/restorer: unregister (g)libc rseq before memory restoration
Fresh glibc does rseq registration by default during start_thread().
[ see https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=95e114a0919d844d8fe07839cb6538b7f5ee920e ]
This cause process crashes during memory restore procedure, because
memory which corresponds to the struct rseq will be unmapped and overriden
in __export_restore_task.
Let's perform rseq unregistration just before unmap_old_vmas(). To achieve
that we need to determine (struct rseq) address at first while we are in Glibc
(we do that in prep_libc_rseq_info using Glibc exported symbols).
See also
("nptl: Add public rseq symbols and <sys/rseq.h>")
https://sourceware.org/git?p=glibc.git;a=commit;h=c901c3e764d7c7079f006b4e21e877d5036eb4f5
("nptl: Add <thread_pointer.h> for defining __thread_pointer")
https://sourceware.org/git?p=glibc.git;a=commit;h=8dbeb0561eeb876f557ac9eef5721912ec074ea5
TODO: do the same for musl-libc if it will start to register rseq by default
Signed-off-by: Alexander Mikhalitsyn <alexander.mikhalitsyn@virtuozzo.com>
-rw-r--r-- | criu/cr-restore.c | 29 | ||||
-rw-r--r-- | criu/include/linux/rseq.h | 9 | ||||
-rw-r--r-- | criu/include/restorer.h | 6 | ||||
-rw-r--r-- | criu/pie/restorer.c | 18 |
4 files changed, 62 insertions, 0 deletions
diff --git a/criu/cr-restore.c b/criu/cr-restore.c index ed576fc55..0751c5b8d 100644 --- a/criu/cr-restore.c +++ b/criu/cr-restore.c @@ -23,6 +23,7 @@ #include "common/compiler.h" #include "linux/mount.h" +#include "linux/rseq.h" #include "clone-noasan.h" #include "cr_options.h" @@ -3012,6 +3013,32 @@ static int prep_rseq(struct rst_rseq_param *rseq, ThreadCoreEntry *tc) return 0; } +#if defined(__GLIBC__) && defined(RSEQ_SIG) +static void prep_libc_rseq_info(struct rst_rseq_param *rseq) +{ + if (!kdat.has_rseq) { + rseq->rseq_abi_pointer = 0; + return; + } + + rseq->rseq_abi_pointer = encode_pointer(__criu_thread_pointer() + __rseq_offset); + rseq->rseq_abi_size = __rseq_size; + rseq->signature = RSEQ_SIG; +} +#else +static void prep_libc_rseq_info(struct rst_rseq_param *rseq) +{ + /* + * TODO: handle built-in rseq on other libc'ies like musl + * We can do that using get_rseq_conf kernel feature. + * + * For now we just assume that other libc libraries are + * not registering rseq by default. + */ + rseq->rseq_abi_pointer = 0; +} +#endif + static rlim_t decode_rlim(rlim_t ival) { return ival == -1 ? RLIM_INFINITY : ival; @@ -3665,6 +3692,8 @@ static int sigreturn_restore(pid_t pid, struct task_restore_args *task_args, uns strncpy(task_args->comm, core->tc->comm, TASK_COMM_LEN - 1); task_args->comm[TASK_COMM_LEN - 1] = 0; + prep_libc_rseq_info(&task_args->libc_rseq); + /* * Fill up per-thread data. */ diff --git a/criu/include/linux/rseq.h b/criu/include/linux/rseq.h index b227aefdf..a47876e66 100644 --- a/criu/include/linux/rseq.h +++ b/criu/include/linux/rseq.h @@ -2,6 +2,14 @@ #ifndef _UAPI_LINUX_RSEQ_H #define _UAPI_LINUX_RSEQ_H +#ifdef __has_include +#if __has_include("sys/rseq.h") +#include <sys/rseq.h> +#include "asm/thread_pointer.h" +#endif +#endif + +#ifndef __GLIBC_HAVE_KERNEL_RSEQ /* * linux/rseq.h * @@ -49,6 +57,7 @@ struct rseq_cs { __u64 post_commit_offset; __u64 abort_ip; } __attribute__((aligned(4 * sizeof(__u64)))); +#endif /* __GLIBC_HAVE_KERNEL_RSEQ */ /* * We have to have our own copy of struct rseq definition because diff --git a/criu/include/restorer.h b/criu/include/restorer.h index 2e21da522..325804e44 100644 --- a/criu/include/restorer.h +++ b/criu/include/restorer.h @@ -229,6 +229,12 @@ struct task_restore_args { int lsm_type; int child_subreaper; bool has_clone3_set_tid; + + /* + * info about rseq from libc used to + * unregister it before memory restoration procedure + */ + struct rst_rseq_param libc_rseq; } __aligned(64); /* diff --git a/criu/pie/restorer.c b/criu/pie/restorer.c index 376a5025d..f80b68359 100644 --- a/criu/pie/restorer.c +++ b/criu/pie/restorer.c @@ -1122,6 +1122,15 @@ void __export_unmap(void) sys_munmap(bootstrap_start, bootstrap_len - vdso_rt_size); } +static void unregister_libc_rseq(struct rst_rseq_param *rseq) +{ + if (!rseq->rseq_abi_pointer) + return; + + /* can't fail if rseq is registered */ + sys_rseq(decode_pointer(rseq->rseq_abi_pointer), rseq->rseq_abi_size, 1, rseq->signature); +} + /* * This function unmaps all VMAs, which don't belong to * the restored process or the restorer. @@ -1461,6 +1470,15 @@ long __export_restore_task(struct task_restore_args *args) goto core_restore_end; } + /* + * We may have rseq registered already if CRIU compiled against + * a fresh Glibc with rseq support. Anyway, we need to unregister it + * before doing unmap_old_vmas or we will get SIGSEGV from the kernel, + * for instance once the kernel will want to update (struct rseq).cpu_id field: + * https://github.com/torvalds/linux/blob/ce522ba9ef7e/kernel/rseq.c#L89 + */ + unregister_libc_rseq(&args->libc_rseq); + if (unmap_old_vmas((void *)args->premmapped_addr, args->premmapped_len, bootstrap_start, bootstrap_len, args->task_size)) goto core_restore_end; |