Skip to content

Commit

Permalink
pie/restorer: unregister (g)libc rseq before memory restoration
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
mihalicyn authored and avagin committed Apr 29, 2022
1 parent e1799e5 commit f70ddab
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 0 deletions.
29 changes: 29 additions & 0 deletions criu/cr-restore.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "common/compiler.h"

#include "linux/mount.h"
#include "linux/rseq.h"

#include "clone-noasan.h"
#include "cr_options.h"
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand Down
9 changes: 9 additions & 0 deletions criu/include/linux/rseq.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions criu/include/restorer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/*
Expand Down
18 changes: 18 additions & 0 deletions criu/pie/restorer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit f70ddab

Please sign in to comment.