From c7858ba42bed2e072ef3842046f4a6335e70ee44 Mon Sep 17 00:00:00 2001 From: Yuriy Vasiliev Date: Thu, 20 Jan 2022 17:13:59 +0100 Subject: [PATCH] infect: add SIGTSTP support Add SIGTSTP signal dump and restore. Add a corresponding field in the image, save it only if a task is in the stopped state. Restore task state by sending desired stop signal if it is present in the image. Fallback to SIGSTOP if it's absent. Signed-off-by: Yuriy Vasiliev --- Documentation/compel.txt | 5 ++- compel/include/uapi/infect.h | 3 ++ compel/src/lib/infect.c | 87 +++++++++++++++++++++++++++++------- criu/cr-dump.c | 5 +++ criu/cr-restore.c | 11 ++++- criu/include/pid.h | 4 ++ criu/proc_parse.c | 15 ++++++- criu/pstree.c | 1 + criu/seize.c | 8 +++- images/core.proto | 2 + 10 files changed, 121 insertions(+), 20 deletions(-) diff --git a/Documentation/compel.txt b/Documentation/compel.txt index a44ca22c6..506228f59 100644 --- a/Documentation/compel.txt +++ b/Documentation/compel.txt @@ -97,7 +97,10 @@ Following steps are performed to infect the victim process: - execute system call: *int compel_syscall(ctl, int syscall_nr, long *ret, int arg ...);* - infect victim: *int compel_infect(ctl, nr_thread, size_of_args_area);* - cure the victim: *int compel_cure(ctl);* //ctl pointer is freed by this call - - Resume victim: *int compel_resume_task(pid, orig_state, state);* + - Resume victim: *int compel_resume_task(pid, orig_state, state)* or + *int compel_resume_task_sig(pid, orig_state, state, stop_signo).* + //compel_resume_task_sig() could be used in case when victim is in stopped state. + stop_signo could be read by calling compel_parse_stop_signo(). *ctl* must be configured with blob information by calling *PREFIX_setup_c_header()*, with ctl as its argument. *PREFIX* is the argument given to *-p* when calling hgen, else it is deduced from file name. diff --git a/compel/include/uapi/infect.h b/compel/include/uapi/infect.h index 3040a67a7..7073f343f 100644 --- a/compel/include/uapi/infect.h +++ b/compel/include/uapi/infect.h @@ -18,6 +18,7 @@ extern int __must_check compel_interrupt_task(int pid); struct seize_task_status { unsigned long long sigpnd; unsigned long long shdpnd; + unsigned long long sigblk; char state; int vpid; int ppid; @@ -30,7 +31,9 @@ extern int __must_check compel_wait_task(int pid, int ppid, struct seize_task_status *st, void *data); extern int __must_check compel_stop_task(int pid); +extern int __must_check compel_parse_stop_signo(int pid); extern int compel_resume_task(pid_t pid, int orig_state, int state); +extern int compel_resume_task_sig(pid_t pid, int orig_state, int state, int stop_signo); struct parasite_ctl; struct parasite_thread_ctl; diff --git a/compel/src/lib/infect.c b/compel/src/lib/infect.c index c78c02a6a..b99f23b36 100644 --- a/compel/src/lib/infect.c +++ b/compel/src/lib/infect.c @@ -92,6 +92,12 @@ static int parse_pid_status(int pid, struct seize_task_status *ss, void *data) continue; } + if (!strncmp(aux, "SigBlk:", 7)) { + if (sscanf(aux + 7, "%llx", &ss->sigblk) != 1) + goto err_parse; + + continue; + } } fclose(f); @@ -186,6 +192,29 @@ static int skip_sigstop(int pid, int nr_signals) return 0; } +#define SIG_MASK(sig) (1ULL << ((sig)-1)) + +#define SIG_IN_MASK(sig, mask) ((sig) > 0 && (sig) <= SIGMAX && (SIG_MASK(sig) & (mask))) + +#define SUPPORTED_STOP_MASK ((1ULL << (SIGSTOP - 1)) | (1ULL << (SIGTSTP - 1))) + +static inline int sig_stop(int sig) +{ + return SIG_IN_MASK(sig, SUPPORTED_STOP_MASK); +} + +int compel_parse_stop_signo(int pid) +{ + siginfo_t si; + + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si) < 0) { + pr_perror("SEIZE %d: can't parse stopped siginfo", pid); + return -1; + } + + return si.si_signo; +} + /* * This routine seizes task putting it into a special * state where we can manipulate the task via ptrace @@ -198,7 +227,7 @@ int compel_wait_task(int pid, int ppid, int (*get_status)(int pid, struct seize_ void *data) { siginfo_t si; - int status, nr_sigstop; + int status, nr_stopsig; int ret = 0, ret2, wait_errno = 0; /* @@ -291,17 +320,32 @@ try_again: goto err; } - nr_sigstop = 0; - if (ss->sigpnd & (1 << (SIGSTOP - 1))) - nr_sigstop++; - if (ss->shdpnd & (1 << (SIGSTOP - 1))) - nr_sigstop++; - if (si.si_signo == SIGSTOP) - nr_sigstop++; + nr_stopsig = 0; + if (SIG_IN_MASK(SIGSTOP, ss->sigpnd)) + nr_stopsig++; + if (SIG_IN_MASK(SIGSTOP, ss->shdpnd)) + nr_stopsig++; - if (nr_sigstop) { - if (skip_sigstop(pid, nr_sigstop)) - goto err_stop; + if (SIG_IN_MASK(SIGTSTP, ss->sigpnd) && !SIG_IN_MASK(SIGTSTP, ss->sigblk)) + nr_stopsig++; + if (SIG_IN_MASK(SIGTSTP, ss->shdpnd) && !SIG_IN_MASK(SIGTSTP, ss->sigblk)) + nr_stopsig++; + + if (sig_stop(si.si_signo)) + nr_stopsig++; + + if (nr_stopsig) { + if (skip_sigstop(pid, nr_stopsig)) { + /* + * Make sure that the task is stopped by a supported stop signal and + * send it again to restore task state before criu intervention. + */ + if (sig_stop(si.si_signo)) + kill(pid, si.si_signo); + else + kill(pid, SIGSTOP); + goto err; + } return COMPEL_TASK_STOPPED; } @@ -313,8 +357,6 @@ try_again: goto err; } -err_stop: - kill(pid, SIGSTOP); err: if (ptrace(PTRACE_DETACH, pid, NULL, NULL)) pr_perror("Unable to detach from %d", pid); @@ -322,6 +364,11 @@ err: } int compel_resume_task(pid_t pid, int orig_st, int st) +{ + return compel_resume_task_sig(pid, orig_st, st, SIGSTOP); +} + +int compel_resume_task_sig(pid_t pid, int orig_st, int st, int stop_signo) { int ret = 0; @@ -345,8 +392,18 @@ int compel_resume_task(pid_t pid, int orig_st, int st) * task with STOP in queue that would get lost after * detach, so stop it again. */ - if (orig_st == COMPEL_TASK_STOPPED) - kill(pid, SIGSTOP); + if (orig_st == COMPEL_TASK_STOPPED) { + /* + * Check that stop_signo contain supported stop signal. + * If it isn't, then send SIGSTOP. It makes sense in the case + * when we get COMPEL_TASK_STOPPED from old image, + * where stop_signo was not yet supported. + */ + if (sig_stop(stop_signo)) + kill(pid, stop_signo); + else + kill(pid, SIGSTOP); + } } else { pr_err("Unknown final state %d\n", st); ret = -1; diff --git a/criu/cr-dump.c b/criu/cr-dump.c index 60e90baed..e60da88ed 100644 --- a/criu/cr-dump.c +++ b/criu/cr-dump.c @@ -781,6 +781,11 @@ static int dump_task_core_all(struct parasite_ctl *ctl, struct pstree_item *item core->thread_core->creds->lsm_profile = dmpi(item)->thread_lsms[0]->profile; core->thread_core->creds->lsm_sockcreate = dmpi(item)->thread_lsms[0]->sockcreate; + if (core->tc->task_state == TASK_STOPPED) { + core->tc->has_stop_signo = true; + core->tc->stop_signo = item->pid->stop_signo; + } + ret = parasite_dump_thread_leader_seized(ctl, pid, core); if (ret) goto err; diff --git a/criu/cr-restore.c b/criu/cr-restore.c index 398faf048..279246c19 100644 --- a/criu/cr-restore.c +++ b/criu/cr-restore.c @@ -1350,6 +1350,9 @@ static inline int fork_with_pid(struct pstree_item *item) item->pid->state = ca.core->tc->task_state; rsti(item)->cg_set = ca.core->tc->cg_set; + if (ca.core->tc->has_stop_signo) + item->pid->stop_signo = ca.core->tc->stop_signo; + if (item->pid->state != TASK_DEAD && !task_alive(item)) { pr_err("Unknown task state %d\n", item->pid->state); return -1; @@ -2104,8 +2107,14 @@ static void finalize_restore(void) xfree(ctl); - if ((item->pid->state == TASK_STOPPED) || (opts.final_state == TASK_STOPPED)) + if (opts.final_state == TASK_STOPPED) kill(item->pid->real, SIGSTOP); + else if (item->pid->state == TASK_STOPPED) { + if (item->pid->stop_signo > 0) + kill(item->pid->real, item->pid->stop_signo); + else + kill(item->pid->real, SIGSTOP); + } } } diff --git a/criu/include/pid.h b/criu/include/pid.h index 49cb2d322..b2b7a361a 100644 --- a/criu/include/pid.h +++ b/criu/include/pid.h @@ -31,6 +31,10 @@ struct pid { pid_t real; int state; /* TASK_XXX constants */ + /* If an item is in stopped state it has a signal number + * that caused task to stop. + */ + int stop_signo; /* * The @virt pid is one which used in the image itself and keeps diff --git a/criu/proc_parse.c b/criu/proc_parse.c index 6b41a81db..946b0fc40 100644 --- a/criu/proc_parse.c +++ b/criu/proc_parse.c @@ -1027,12 +1027,13 @@ int parse_pid_status(pid_t pid, struct seize_task_status *ss, void *data) cr->s.sigpnd = 0; cr->s.shdpnd = 0; + cr->s.sigblk = 0; cr->s.seccomp_mode = SECCOMP_MODE_DISABLED; if (bfdopenr(&f)) return -1; - while (done < 13) { + while (done < 14) { str = breadline(&f); if (str == NULL) break; @@ -1143,13 +1144,23 @@ int parse_pid_status(pid_t pid, struct seize_task_status *ss, void *data) goto err_parse; cr->s.sigpnd |= sigpnd; + done++; + continue; + } + if (!strncmp(str, "SigBlk:", 7)) { + unsigned long long sigblk = 0; + + if (sscanf(str + 7, "%llx", &sigblk) != 1) + goto err_parse; + cr->s.sigblk |= sigblk; + done++; continue; } } /* seccomp and nspids are optional */ - expected_done = (parsed_seccomp ? 11 : 10); + expected_done = (parsed_seccomp ? 12 : 11); if (kdat.has_nspid) expected_done++; if (done == expected_done) diff --git a/criu/pstree.c b/criu/pstree.c index f4d77b3a4..72c4a3502 100644 --- a/criu/pstree.c +++ b/criu/pstree.c @@ -222,6 +222,7 @@ struct pstree_item *__alloc_pstree_item(bool rst) item->pid->ns[0].virt = -1; item->pid->real = -1; item->pid->state = TASK_UNDEF; + item->pid->stop_signo = -1; item->born_sid = -1; item->pid->item = item; futex_init(&item->task_st); diff --git a/criu/seize.c b/criu/seize.c index 58564ca74..1333d6db9 100644 --- a/criu/seize.c +++ b/criu/seize.c @@ -615,6 +615,9 @@ static int collect_children(struct pstree_item *item) else processes_to_wait--; + if (ret == TASK_STOPPED) + c->pid->stop_signo = compel_parse_stop_signo(pid); + c->pid->real = pid; c->parent = item; c->pid->state = ret; @@ -646,7 +649,7 @@ static void unseize_task_and_threads(const struct pstree_item *item, int st) * the item->state is the state task was in when we seized one. */ - compel_resume_task(item->pid->real, item->pid->state, st); + compel_resume_task_sig(item->pid->real, item->pid->state, st, item->pid->stop_signo); if (st == TASK_DEAD) return; @@ -950,6 +953,9 @@ int collect_pstree(void) else processes_to_wait--; + if (ret == TASK_STOPPED) + root_item->pid->stop_signo = compel_parse_stop_signo(pid); + pr_info("Seized task %d, state %d\n", pid, ret); root_item->pid->state = ret; diff --git a/images/core.proto b/images/core.proto index 35079f366..345bdca53 100644 --- a/images/core.proto +++ b/images/core.proto @@ -60,6 +60,8 @@ message task_core_entry { // Reserved for container relative start time //optional uint64 start_time = 19; optional uint64 blk_sigset_extended = 20[(criu).hex = true]; + + optional uint32 stop_signo = 21; } message task_kobj_ids_entry {