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>
This commit is contained in:
Alexander Mikhalitsyn 2021-12-24 18:57:54 +03:00 committed by Andrei Vagin
parent e1799e5305
commit f70ddab24e
4 changed files with 62 additions and 0 deletions

View file

@ -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.
*/

View file

@ -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

View file

@ -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);
/*

View file

@ -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;