2011-09-23 12:00:45 +04:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <sys/user.h>
|
|
|
|
#include <sys/wait.h>
|
2012-02-01 16:23:50 +03:00
|
|
|
#include <sys/socket.h>
|
2011-09-23 12:00:45 +04:00
|
|
|
|
ctrools: Rewrite task/threads stopping engine is back
This commit brings the former "Rewrite task/threads stopping engine"
commit back. Handling it separately is too complex so better try
to handle it in-place.
Note some tests might fault, it's expected.
---
Stopping tasks with STOP and proceeding with SEIZE is actually excessive --
the SEIZE if enough. Moreover, just killing a task with STOP is also racy,
since task should be given some time to come to sleep before its proc
can be parsed.
Rewrite all this code to SEIZE task and all its threads from the very beginning.
With this we can distinguish stopped task state and migrate it properly (not
supported now, need to implement).
This thing however has one BIG problem -- after we SEIZE-d a task we should
seize
it's threads, but we should do it in a loop -- reading /proc/pid/task and
seizing
them again and again, until the contents of this dir stops changing (not done
now).
Besides, after we seized a task and all its threads we cannot scan it's children
list once -- task can get reparented to init and any task's child can call clone
with CLONE_PARENT flag thus repopulating the children list of the already seized
task (not done also)
This patch is ugly, yes, but splitting it doesn't help to review it much, sorry
:(
Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
Signed-off-by: Cyrill Gorcunov <gorcunov@openvz.org>
2012-02-01 19:45:31 +04:00
|
|
|
#include "crtools.h"
|
2011-09-23 12:00:45 +04:00
|
|
|
#include "compiler.h"
|
|
|
|
#include "syscall.h"
|
|
|
|
#include "types.h"
|
2011-12-19 21:57:59 +04:00
|
|
|
#include "ptrace.h"
|
2011-09-23 12:00:45 +04:00
|
|
|
#include "util.h"
|
2012-02-01 16:23:50 +03:00
|
|
|
#include "util-net.h"
|
|
|
|
#include "log.h"
|
2012-02-29 16:06:48 +03:00
|
|
|
#include "sockets.h"
|
2012-02-13 23:02:23 +04:00
|
|
|
#include "processor-flags.h"
|
2011-09-23 12:00:45 +04:00
|
|
|
#include "parasite-syscall.h"
|
|
|
|
#include "parasite-blob.h"
|
|
|
|
#include "parasite.h"
|
|
|
|
|
|
|
|
#ifdef CONFIG_X86_64
|
|
|
|
static const char code_syscall[] = {0x0f, 0x05, 0xcc, 0xcc,
|
|
|
|
0xcc, 0xcc, 0xcc, 0xcc};
|
|
|
|
|
|
|
|
#define code_syscall_size (round_up(sizeof(code_syscall), sizeof(long)))
|
|
|
|
#define parasite_size (round_up(sizeof(parasite_blob), sizeof(long)))
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
static int can_run_syscall(unsigned long ip, unsigned long start, unsigned long end)
|
|
|
|
{
|
|
|
|
return ip >= start && ip < (end - code_syscall_size);
|
|
|
|
}
|
|
|
|
|
2011-09-23 12:00:45 +04:00
|
|
|
static int syscall_fits_vma_area(struct vma_area *vma_area)
|
|
|
|
{
|
|
|
|
return can_run_syscall((unsigned long)vma_area->vma.start,
|
|
|
|
(unsigned long)vma_area->vma.start,
|
|
|
|
(unsigned long)vma_area->vma.end);
|
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
static struct vma_area *get_vma_by_ip(struct list_head *vma_area_list, unsigned long ip)
|
2011-09-23 12:00:45 +04:00
|
|
|
{
|
2012-02-15 18:00:50 +04:00
|
|
|
struct vma_area *vma_area;
|
|
|
|
|
|
|
|
list_for_each_entry(vma_area, vma_area_list, list) {
|
|
|
|
if (!in_vma_area(vma_area, ip))
|
|
|
|
continue;
|
|
|
|
if (!(vma_area->vma.prot & PROT_EXEC))
|
|
|
|
continue;
|
|
|
|
if (syscall_fits_vma_area(vma_area))
|
|
|
|
return vma_area;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
2011-09-23 12:00:45 +04:00
|
|
|
}
|
|
|
|
|
2012-02-13 23:02:23 +04:00
|
|
|
/* Note it's destructive on @regs */
|
|
|
|
static void parasite_setup_regs(unsigned long new_ip, user_regs_struct_t *regs)
|
|
|
|
{
|
|
|
|
regs->ip = new_ip;
|
|
|
|
|
|
|
|
/* Avoid end of syscall processing */
|
|
|
|
regs->orig_ax = -1;
|
|
|
|
|
|
|
|
/* Make sure flags are in known state */
|
|
|
|
regs->flags &= ~(X86_EFLAGS_TF | X86_EFLAGS_DF | X86_EFLAGS_IF);
|
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
/* we run at @regs->ip */
|
2012-02-21 09:25:16 +03:00
|
|
|
static int __parasite_execute(struct parasite_ctl *ctl, pid_t pid, user_regs_struct_t *regs)
|
2011-09-23 12:00:45 +04:00
|
|
|
{
|
|
|
|
siginfo_t siginfo;
|
|
|
|
int status;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
again:
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_SETREGS, pid, NULL, regs)) {
|
|
|
|
pr_err("Can't set registers (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Most ideas are taken from Tejun Heo's parasite thread
|
|
|
|
* https://code.google.com/p/ptrace-parasite/
|
|
|
|
*/
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_CONT, pid, NULL, NULL)) {
|
|
|
|
pr_err("Can't continue (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wait4(pid, &status, __WALL, NULL) != pid) {
|
|
|
|
pr_err("Waited pid mismatch (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!WIFSTOPPED(status)) {
|
|
|
|
pr_err("Task is still running (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo)) {
|
|
|
|
pr_err("Can't get siginfo (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_GETREGS, pid, NULL, regs)) {
|
|
|
|
pr_err("Can't obtain registers (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
|
|
|
if (WSTOPSIG(status) != SIGTRAP || siginfo.si_code != SI_KERNEL) {
|
|
|
|
retry_signal:
|
2012-02-15 18:00:50 +04:00
|
|
|
pr_debug("** delivering signal %d si_code=%d\n",
|
|
|
|
siginfo.si_signo, siginfo.si_code);
|
|
|
|
|
2012-03-01 19:01:05 +04:00
|
|
|
if (ctl->signals_blocked) {
|
|
|
|
pr_err("Unexpected %d task interruption, aborting\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2012-02-13 23:06:18 +04:00
|
|
|
/* FIXME: jerr(siginfo.si_code > 0, err_restore); */
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
/*
|
|
|
|
* This requires some explanation. If a signal from original
|
|
|
|
* program delivered while we're trying to execute our
|
|
|
|
* injected blob -- we need to setup original registers back
|
|
|
|
* so the kernel would make sigframe for us and update the
|
|
|
|
* former registers.
|
|
|
|
*
|
|
|
|
* Then we should swap registers back to our modified copy
|
|
|
|
* and retry.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (ptrace(PTRACE_SETREGS, pid, NULL, &ctl->regs_orig)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("Can't set registers (pid: %d)\n", pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ptrace(PTRACE_INTERRUPT, pid, NULL, NULL)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("Can't interrupt (pid: %d)\n", pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ptrace(PTRACE_CONT, pid, NULL, (void *)(unsigned long)siginfo.si_signo)) {
|
|
|
|
pr_err("Can't continue (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wait4(pid, &status, __WALL, NULL) != pid) {
|
|
|
|
pr_err("Waited pid mismatch (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!WIFSTOPPED(status)) {
|
|
|
|
pr_err("Task is still running (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo)) {
|
|
|
|
pr_err("Can't get siginfo (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
|
|
|
if (siginfo.si_code >> 8 != PTRACE_EVENT_STOP)
|
|
|
|
goto retry_signal;
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
/*
|
|
|
|
* Signal is delivered, so we should update
|
|
|
|
* original registers.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
user_regs_struct_t r;
|
|
|
|
if (ptrace(PTRACE_GETREGS, pid, NULL, &r)) {
|
|
|
|
pr_err("Can't obtain registers (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
ctl->regs_orig = r;
|
|
|
|
}
|
|
|
|
|
2011-09-23 12:00:45 +04:00
|
|
|
goto again;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-01-30 21:18:37 +04:00
|
|
|
* Our code is done.
|
2011-09-23 12:00:45 +04:00
|
|
|
*/
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_INTERRUPT, pid, NULL, NULL)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("Can't interrupt (pid: %d)\n", pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_CONT, pid, NULL, NULL)) {
|
|
|
|
pr_err("Can't continue (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (wait4(pid, &status, __WALL, NULL) != pid) {
|
|
|
|
pr_err("Waited pid mismatch (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (!WIFSTOPPED(status)) {
|
|
|
|
pr_err("Task is still running (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &siginfo)) {
|
|
|
|
pr_err("Can't get siginfo (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (siginfo.si_code >> 8 != PTRACE_EVENT_STOP) {
|
|
|
|
pr_err("si_code doesn't match (pid: %d si_code: %d)\n",
|
|
|
|
pid, siginfo.si_code);
|
|
|
|
goto err;
|
2012-02-08 14:11:54 +04:00
|
|
|
}
|
2012-02-15 18:00:50 +04:00
|
|
|
|
|
|
|
ret = 0;
|
2011-09-23 12:00:45 +04:00
|
|
|
err:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-02-21 09:25:16 +03:00
|
|
|
static int parasite_execute_by_pid(unsigned long cmd, struct parasite_ctl *ctl,
|
|
|
|
pid_t pid,
|
2012-02-15 18:00:50 +04:00
|
|
|
parasite_status_t *args, int args_size)
|
2012-02-12 00:32:32 +04:00
|
|
|
{
|
|
|
|
int ret;
|
2012-02-21 09:25:16 +03:00
|
|
|
user_regs_struct_t regs_orig, regs;
|
|
|
|
|
|
|
|
if (ctl->pid == pid)
|
|
|
|
regs = ctl->regs_orig;
|
|
|
|
else {
|
|
|
|
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s_orig)) {
|
|
|
|
pr_err("Can't obtain registers (pid: %d)\n", pid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
regs = regs_orig;
|
|
|
|
}
|
2012-02-12 00:32:32 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
memcpy(ctl->addr_cmd, &cmd, sizeof(cmd));
|
2012-03-19 12:02:00 +04:00
|
|
|
if (args)
|
|
|
|
memcpy(ctl->addr_args, args, args_size);
|
2012-02-12 00:32:32 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
parasite_setup_regs(ctl->parasite_ip, ®s);
|
2012-02-12 00:32:32 +04:00
|
|
|
|
2012-02-21 09:25:16 +03:00
|
|
|
ret = __parasite_execute(ctl, pid, ®s);
|
2012-02-12 00:32:32 +04:00
|
|
|
|
2012-03-19 12:02:00 +04:00
|
|
|
if (args)
|
|
|
|
memcpy(args, ctl->addr_args, args_size);
|
|
|
|
|
|
|
|
BUG_ON(ret && !args);
|
2012-02-12 00:32:32 +04:00
|
|
|
|
2012-02-16 21:54:49 +04:00
|
|
|
if (ret)
|
|
|
|
pr_err("Parasite exited with %d ret (%li at %li)\n",
|
2012-03-19 12:02:00 +04:00
|
|
|
ret, args->ret, args->line);
|
2012-02-16 21:54:49 +04:00
|
|
|
|
2012-02-21 09:25:16 +03:00
|
|
|
if (ctl->pid != pid)
|
|
|
|
if (ptrace(PTRACE_SETREGS, pid, NULL, ®s_orig)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("Can't restore registers (pid: %d)\n", ctl->pid);
|
2012-02-21 09:25:16 +03:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-02-12 00:32:32 +04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-02-21 09:25:16 +03:00
|
|
|
static int parasite_execute(unsigned long cmd, struct parasite_ctl *ctl,
|
|
|
|
parasite_status_t *args, int args_size)
|
|
|
|
{
|
|
|
|
return parasite_execute_by_pid(cmd, ctl, ctl->pid, args, args_size);
|
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
static void *mmap_seized(struct parasite_ctl *ctl,
|
|
|
|
void *addr, size_t length, int prot,
|
|
|
|
int flags, int fd, off_t offset)
|
2011-11-29 13:33:59 +03:00
|
|
|
{
|
2012-02-15 18:00:50 +04:00
|
|
|
user_regs_struct_t regs = ctl->regs_orig;
|
|
|
|
void *map = NULL;
|
|
|
|
int ret;
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
regs.ax = (unsigned long)__NR_mmap; /* mmap */
|
|
|
|
regs.di = (unsigned long)addr; /* @addr */
|
|
|
|
regs.si = (unsigned long)length; /* @length */
|
|
|
|
regs.dx = (unsigned long)prot; /* @prot */
|
|
|
|
regs.r10= (unsigned long)flags; /* @flags */
|
|
|
|
regs.r8 = (unsigned long)fd; /* @fd */
|
|
|
|
regs.r9 = (unsigned long)offset; /* @offset */
|
2012-02-12 14:51:38 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
parasite_setup_regs(ctl->syscall_ip, ®s);
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-21 09:25:16 +03:00
|
|
|
ret = __parasite_execute(ctl, ctl->pid, ®s);
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if ((long)regs.ax > 0)
|
|
|
|
map = (void *)regs.ax;
|
|
|
|
err:
|
|
|
|
return map;
|
|
|
|
}
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
static int munmap_seized(struct parasite_ctl *ctl, void *addr, size_t length)
|
|
|
|
{
|
|
|
|
user_regs_struct_t regs = ctl->regs_orig;
|
|
|
|
int ret;
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
regs.ax = (unsigned long)__NR_munmap; /* mmap */
|
|
|
|
regs.di = (unsigned long)addr; /* @addr */
|
|
|
|
regs.si = (unsigned long)length; /* @length */
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
parasite_setup_regs(ctl->syscall_ip, ®s);
|
2011-11-29 13:33:59 +03:00
|
|
|
|
2012-02-21 09:25:16 +03:00
|
|
|
ret = __parasite_execute(ctl, ctl->pid, ®s);
|
2012-02-15 18:00:50 +04:00
|
|
|
if (!ret)
|
|
|
|
ret = (int)regs.ax;
|
2011-11-29 13:33:59 +03:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-03-21 11:47:00 +04:00
|
|
|
static int gen_parasite_saddr(struct sockaddr_un *saddr, int key)
|
2012-02-01 16:23:50 +03:00
|
|
|
{
|
|
|
|
int sun_len;
|
|
|
|
|
|
|
|
saddr->sun_family = AF_UNIX;
|
|
|
|
snprintf(saddr->sun_path, UNIX_PATH_MAX,
|
2012-03-21 11:47:00 +04:00
|
|
|
"X/crtools-pr-%d", key);
|
2012-02-01 16:23:50 +03:00
|
|
|
|
|
|
|
sun_len = SUN_LEN(saddr);
|
|
|
|
*saddr->sun_path = '\0';
|
|
|
|
|
|
|
|
return sun_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parasite_send_fd(struct parasite_ctl *ctl, int fd)
|
|
|
|
{
|
|
|
|
struct sockaddr_un saddr;
|
|
|
|
int sun_len, ret = -1;
|
|
|
|
int sock;
|
|
|
|
|
2012-02-21 22:59:33 +04:00
|
|
|
sun_len = gen_parasite_saddr(&saddr, ctl->pid);
|
2012-02-01 16:23:50 +03:00
|
|
|
|
|
|
|
sock = socket(PF_UNIX, SOCK_DGRAM, 0);
|
|
|
|
if (sock < 0) {
|
|
|
|
pr_perror("Can't create socket");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (send_fd(sock, &saddr, sun_len, fd) < 0) {
|
|
|
|
pr_perror("Can't send file descriptor");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
|
|
close(sock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-03-26 17:35:00 +04:00
|
|
|
static int parasite_prep_file(int fd, struct parasite_ctl *ctl)
|
2012-01-24 16:40:55 +04:00
|
|
|
{
|
2012-02-01 13:00:51 +03:00
|
|
|
int ret;
|
2012-01-24 16:40:55 +04:00
|
|
|
|
2012-03-26 17:35:00 +04:00
|
|
|
if (fchmod(fd, CR_FD_PERM_DUMP)) {
|
|
|
|
pr_perror("Can't change permissions on file");
|
2012-01-24 16:40:55 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-03-26 17:35:00 +04:00
|
|
|
ret = parasite_send_fd(ctl, fd);
|
2012-02-01 13:00:51 +03:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2012-01-24 16:40:55 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
static int parasite_file_cmd(char *what, int cmd, int type,
|
|
|
|
struct parasite_ctl *ctl,
|
|
|
|
struct cr_fdset *cr_fdset)
|
2011-11-29 15:12:25 +03:00
|
|
|
{
|
2012-02-01 13:00:51 +03:00
|
|
|
parasite_status_t args = { };
|
2012-03-26 17:35:00 +04:00
|
|
|
int ret = -1, fd;
|
2011-11-29 15:12:25 +03:00
|
|
|
|
|
|
|
pr_info("\n");
|
2012-02-15 18:00:50 +04:00
|
|
|
pr_info("Dumping %s (pid: %d)\n", what, ctl->pid);
|
2011-11-29 15:12:25 +03:00
|
|
|
pr_info("----------------------------------------\n");
|
|
|
|
|
2012-03-26 17:43:29 +04:00
|
|
|
fd = fdset_fd(cr_fdset, type);
|
2012-03-26 17:35:00 +04:00
|
|
|
ret = parasite_prep_file(fd, ctl);
|
2012-01-24 16:40:55 +04:00
|
|
|
if (ret < 0)
|
2011-11-29 15:12:25 +03:00
|
|
|
goto out;
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
ret = parasite_execute(cmd, ctl, (parasite_status_t *)&args, sizeof(args));
|
2011-11-29 15:12:25 +03:00
|
|
|
|
2012-03-26 17:35:00 +04:00
|
|
|
fchmod(fd, CR_FD_PERM);
|
2011-11-29 15:12:25 +03:00
|
|
|
out:
|
|
|
|
pr_info("----------------------------------------\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-02-01 16:23:50 +03:00
|
|
|
static int parasite_init(struct parasite_ctl *ctl, pid_t pid)
|
|
|
|
{
|
|
|
|
struct parasite_init_args args = { };
|
|
|
|
|
2012-02-21 22:59:33 +04:00
|
|
|
args.sun_len = gen_parasite_saddr(&args.saddr, pid);
|
2012-02-01 16:23:50 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
return parasite_execute(PARASITE_CMD_INIT, ctl,
|
|
|
|
(parasite_status_t *)&args, sizeof(args));
|
2012-02-01 16:23:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int parasite_set_logfd(struct parasite_ctl *ctl, pid_t pid)
|
|
|
|
{
|
|
|
|
parasite_status_t args = { };
|
|
|
|
int ret;
|
|
|
|
|
2012-03-01 18:52:42 +04:00
|
|
|
ret = parasite_send_fd(ctl, log_get_fd());
|
2012-02-01 16:23:50 +03:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
ret = parasite_execute(PARASITE_CMD_SET_LOGFD, ctl, &args, sizeof(args));
|
2012-02-01 16:23:50 +03:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-02-21 09:25:17 +03:00
|
|
|
int parasite_dump_tid_addr_seized(struct parasite_ctl *ctl, pid_t pid, unsigned int **tid_addr)
|
|
|
|
{
|
|
|
|
struct parasite_dump_tid_addr args = { };
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = parasite_execute_by_pid(PARASITE_CMD_DUMP_TID_ADDR, ctl, pid,
|
|
|
|
(parasite_status_t *)&args, sizeof(args));
|
|
|
|
|
|
|
|
*tid_addr = args.tid_addr;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-01-24 16:45:19 +04:00
|
|
|
int parasite_dump_sigacts_seized(struct parasite_ctl *ctl, struct cr_fdset *cr_fdset)
|
|
|
|
{
|
2012-02-15 18:00:50 +04:00
|
|
|
return parasite_file_cmd("sigactions", PARASITE_CMD_DUMP_SIGACTS,
|
|
|
|
CR_FD_SIGACT, ctl, cr_fdset);
|
2012-01-24 16:45:19 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int parasite_dump_itimers_seized(struct parasite_ctl *ctl, struct cr_fdset *cr_fdset)
|
|
|
|
{
|
2012-02-15 18:00:50 +04:00
|
|
|
return parasite_file_cmd("timers", PARASITE_CMD_DUMP_ITIMERS,
|
|
|
|
CR_FD_ITIMERS, ctl, cr_fdset);
|
2012-01-24 16:45:19 +04:00
|
|
|
}
|
|
|
|
|
2012-01-27 21:35:59 +04:00
|
|
|
int parasite_dump_misc_seized(struct parasite_ctl *ctl, struct parasite_dump_misc *misc)
|
|
|
|
{
|
|
|
|
return parasite_execute(PARASITE_CMD_DUMP_MISC, ctl,
|
2012-02-15 18:00:50 +04:00
|
|
|
(parasite_status_t *)misc,
|
|
|
|
sizeof(struct parasite_dump_misc));
|
2012-01-27 21:35:59 +04:00
|
|
|
}
|
|
|
|
|
2012-02-29 16:06:48 +03:00
|
|
|
int parasite_dump_socket_info(struct parasite_ctl *ctl, struct cr_fdset *fdset,
|
|
|
|
struct sk_queue *queue)
|
|
|
|
{
|
|
|
|
int ret, i;
|
|
|
|
struct cr_fdset *fds;
|
|
|
|
unsigned arg_size;
|
|
|
|
struct parasite_dump_sk_queues *arg;
|
|
|
|
struct sk_queue_entry *sk_entry;
|
|
|
|
|
|
|
|
if (queue->entries == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pr_info("Dumping socket queues\n");
|
|
|
|
|
|
|
|
arg_size = sizeof(struct parasite_dump_sk_queues) +
|
|
|
|
queue->entries * sizeof(struct sk_queue_item);
|
|
|
|
|
|
|
|
/* FIXME arg size is only enough for ~1k of sockets */
|
|
|
|
if (arg_size > PARASITE_ARG_SIZE) {
|
|
|
|
pr_err("Too many sockets to drain queue from\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = -1;
|
|
|
|
arg = xzalloc(arg_size);
|
|
|
|
if (arg == NULL)
|
|
|
|
goto err_alloc;
|
|
|
|
|
|
|
|
sk_entry = queue->list;
|
|
|
|
for (i = 0; i < queue->entries; i++, arg->nr_items++) {
|
|
|
|
struct sk_queue_entry *tmp = sk_entry;
|
|
|
|
|
|
|
|
memcpy(&arg->items[i], &sk_entry->item, sizeof(struct sk_queue_item));
|
|
|
|
sk_entry = tmp->next;
|
|
|
|
xfree(tmp);
|
|
|
|
}
|
|
|
|
|
2012-03-26 17:47:38 +04:00
|
|
|
ret = parasite_prep_file(fdset_fd(glob_fdset, CR_FD_SK_QUEUES), ctl);
|
2012-02-29 16:06:48 +03:00
|
|
|
if (ret < 0)
|
|
|
|
goto err_prepf;
|
|
|
|
|
|
|
|
ret = parasite_execute(PARASITE_CMD_DUMP_SK_QUEUES, ctl,
|
|
|
|
(parasite_status_t *)arg, arg_size);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_exec;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_exec:
|
|
|
|
err_prepf:
|
|
|
|
xfree(arg);
|
|
|
|
err_alloc:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-10-13 16:36:50 +04:00
|
|
|
/*
|
|
|
|
* This routine drives parasite code (been previously injected into a victim
|
|
|
|
* process) and tells it to dump pages into the file.
|
|
|
|
*/
|
2011-09-23 12:00:45 +04:00
|
|
|
int parasite_dump_pages_seized(struct parasite_ctl *ctl, struct list_head *vma_area_list,
|
2012-01-11 13:30:38 +04:00
|
|
|
struct cr_fdset *cr_fdset)
|
2011-09-23 12:00:45 +04:00
|
|
|
{
|
2012-01-24 16:39:56 +04:00
|
|
|
struct parasite_dump_pages_args parasite_dumppages = { };
|
2012-02-12 00:26:54 +04:00
|
|
|
parasite_status_t *st = ¶site_dumppages.status;
|
2011-09-23 12:00:45 +04:00
|
|
|
unsigned long nrpages_dumped = 0;
|
|
|
|
struct vma_area *vma_area;
|
2012-02-15 18:00:50 +04:00
|
|
|
int ret = -1;
|
2011-09-23 12:00:45 +04:00
|
|
|
|
|
|
|
pr_info("\n");
|
2012-01-11 13:30:38 +04:00
|
|
|
pr_info("Dumping pages (type: %d pid: %d)\n", CR_FD_PAGES, ctl->pid);
|
2011-09-23 12:00:45 +04:00
|
|
|
pr_info("----------------------------------------\n");
|
|
|
|
|
2012-03-26 17:43:29 +04:00
|
|
|
ret = parasite_prep_file(fdset_fd(cr_fdset, CR_FD_PAGES), ctl);
|
2012-01-24 16:40:55 +04:00
|
|
|
if (ret < 0)
|
2011-11-23 13:08:28 +04:00
|
|
|
goto out;
|
2011-10-03 11:52:13 +04:00
|
|
|
|
2012-02-12 00:26:54 +04:00
|
|
|
ret = parasite_execute(PARASITE_CMD_DUMPPAGES_INIT, ctl, st, sizeof(*st));
|
|
|
|
if (ret < 0) {
|
2012-03-19 12:02:00 +04:00
|
|
|
pr_err("Dumping pages failed with %li at %li\n",
|
2012-02-12 00:26:54 +04:00
|
|
|
parasite_dumppages.status.ret,
|
|
|
|
parasite_dumppages.status.line);
|
|
|
|
goto out;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
|
|
|
list_for_each_entry(vma_area, vma_area_list, list) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The special areas are not dumped.
|
|
|
|
*/
|
|
|
|
if (!(vma_area->vma.status & VMA_AREA_REGULAR))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* No dumps for file-shared mappings */
|
|
|
|
if (vma_area->vma.status & VMA_FILE_SHARED)
|
|
|
|
continue;
|
|
|
|
|
2012-02-14 20:19:49 +03:00
|
|
|
/* No dumps for SYSV IPC mappings */
|
|
|
|
if (vma_area->vma.status & VMA_AREA_SYSVIPC)
|
|
|
|
continue;
|
|
|
|
|
2012-03-21 10:12:00 +04:00
|
|
|
if (vma_area_is(vma_area, VMA_ANON_SHARED))
|
|
|
|
continue;
|
|
|
|
|
2011-09-23 12:00:45 +04:00
|
|
|
pr_info_vma(vma_area);
|
|
|
|
parasite_dumppages.vma_entry = vma_area->vma;
|
|
|
|
|
2012-03-21 10:12:00 +04:00
|
|
|
if (!vma_area_is(vma_area, VMA_ANON_PRIVATE) &&
|
|
|
|
!vma_area_is(vma_area, VMA_FILE_PRIVATE)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_warn("Unexpected VMA area found\n");
|
2012-02-12 00:26:54 +04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-11-29 13:33:59 +03:00
|
|
|
ret = parasite_execute(PARASITE_CMD_DUMPPAGES, ctl,
|
2012-02-15 18:00:50 +04:00
|
|
|
(parasite_status_t *) ¶site_dumppages,
|
|
|
|
sizeof(parasite_dumppages));
|
2011-11-29 13:33:59 +03:00
|
|
|
if (ret) {
|
2012-03-19 12:02:00 +04:00
|
|
|
pr_err("Dumping pages failed with %li at %li\n",
|
2012-02-01 13:00:51 +03:00
|
|
|
parasite_dumppages.status.ret,
|
|
|
|
parasite_dumppages.status.line);
|
2011-11-22 20:07:20 +04:00
|
|
|
|
2012-02-12 00:26:54 +04:00
|
|
|
goto out;
|
2011-11-22 20:07:20 +04:00
|
|
|
}
|
|
|
|
|
2011-09-23 12:00:45 +04:00
|
|
|
pr_info(" (dumped: %16li pages)\n", parasite_dumppages.nrpages_dumped);
|
|
|
|
nrpages_dumped += parasite_dumppages.nrpages_dumped;
|
|
|
|
}
|
|
|
|
|
2012-03-19 12:02:00 +04:00
|
|
|
parasite_execute(PARASITE_CMD_DUMPPAGES_FINI, ctl, NULL, 0);
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-03-26 17:43:29 +04:00
|
|
|
if (write_img(fdset_fd(cr_fdset, CR_FD_PAGES), &zero_page_entry))
|
2012-02-12 00:26:54 +04:00
|
|
|
goto out;
|
2011-09-23 12:00:45 +04:00
|
|
|
|
|
|
|
pr_info("\n");
|
|
|
|
pr_info("Summary: %16li pages dumped\n", nrpages_dumped);
|
2012-02-08 14:11:54 +04:00
|
|
|
ret = 0;
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2011-11-23 13:08:28 +04:00
|
|
|
out:
|
2012-03-26 17:43:29 +04:00
|
|
|
fchmod(fdset_fd(cr_fdset, CR_FD_PAGES), CR_FD_PERM);
|
2011-09-23 12:00:45 +04:00
|
|
|
pr_info("----------------------------------------\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
int parasite_cure_seized(struct parasite_ctl *ctl)
|
2011-09-23 12:00:45 +04:00
|
|
|
{
|
2012-02-01 16:23:50 +03:00
|
|
|
parasite_status_t args = { };
|
2012-02-15 18:00:50 +04:00
|
|
|
int ret = 0;
|
2012-02-01 16:23:50 +03:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ctl->parasite_ip) {
|
2012-03-01 19:01:05 +04:00
|
|
|
ctl->signals_blocked = 0;
|
2012-03-19 12:02:00 +04:00
|
|
|
parasite_execute(PARASITE_CMD_FINI, ctl, NULL, 0);
|
2012-02-01 16:23:50 +03:00
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ctl->remote_map) {
|
|
|
|
if (munmap_seized(ctl, (void *)ctl->remote_map, ctl->map_length)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("munmap_seized failed (pid: %d)\n", ctl->pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
ret = -1;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ctl->local_map) {
|
|
|
|
if (munmap(ctl->local_map, parasite_size)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("munmap failed (pid: %d)\n", ctl->pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace_poke_area(ctl->pid, (void *)ctl->code_orig,
|
|
|
|
(void *)ctl->syscall_ip, sizeof(ctl->code_orig))) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("Can't restore syscall blob (pid: %d)\n", ctl->pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
ret = -1;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_SETREGS, ctl->pid, NULL, &ctl->regs_orig)) {
|
2012-03-01 18:52:42 +04:00
|
|
|
pr_err("Can't restore registers (pid: %d)\n", ctl->pid);
|
2012-02-08 14:11:54 +04:00
|
|
|
ret = -1;
|
2011-09-23 12:00:45 +04:00
|
|
|
}
|
|
|
|
|
2012-01-11 13:32:43 +04:00
|
|
|
free(ctl);
|
2011-09-23 12:00:45 +04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-02-17 01:39:36 +04:00
|
|
|
struct parasite_ctl *parasite_infect_seized(pid_t pid, struct list_head *vma_area_list)
|
2011-09-23 12:00:45 +04:00
|
|
|
{
|
|
|
|
struct parasite_ctl *ctl = NULL;
|
|
|
|
struct vma_area *vma_area;
|
2012-02-12 14:50:34 +04:00
|
|
|
int ret, fd;
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
/*
|
|
|
|
* Control block early setup.
|
|
|
|
*/
|
2012-01-13 17:20:57 +04:00
|
|
|
ctl = xzalloc(sizeof(*ctl));
|
2011-09-23 12:00:45 +04:00
|
|
|
if (!ctl) {
|
2011-09-30 14:37:12 +04:00
|
|
|
pr_err("Parasite control block allocation failed (pid: %d)\n", pid);
|
2011-09-23 12:00:45 +04:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
if (ptrace(PTRACE_GETREGS, pid, NULL, &ctl->regs_orig)) {
|
|
|
|
pr_err("Can't obtain registers (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
vma_area = get_vma_by_ip(vma_area_list, ctl->regs_orig.ip);
|
2011-09-23 12:00:45 +04:00
|
|
|
if (!vma_area) {
|
2011-09-30 14:37:12 +04:00
|
|
|
pr_err("No suitable VMA found to run parasite "
|
2012-02-15 18:00:50 +04:00
|
|
|
"bootstrap code (pid: %d)\n", pid);
|
2012-02-13 23:17:51 +04:00
|
|
|
goto err;
|
2011-09-23 12:00:45 +04:00
|
|
|
}
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
ctl->pid = pid;
|
|
|
|
ctl->syscall_ip = vma_area->vma.start;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Inject syscall instruction and remember original code,
|
|
|
|
* we will need it to restore original program content.
|
|
|
|
*/
|
|
|
|
BUILD_BUG_ON(sizeof(code_syscall) != sizeof(ctl->code_orig));
|
|
|
|
BUILD_BUG_ON(!is_log2(sizeof(code_syscall)));
|
|
|
|
|
|
|
|
memcpy(ctl->code_orig, code_syscall, sizeof(ctl->code_orig));
|
|
|
|
if (ptrace_swap_area(ctl->pid, (void *)ctl->syscall_ip,
|
|
|
|
(void *)ctl->code_orig, sizeof(ctl->code_orig))) {
|
|
|
|
pr_err("Can't inject syscall blob (pid: %d)\n", pid);
|
|
|
|
goto err;
|
|
|
|
}
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
/*
|
|
|
|
* Inject a parasite engine. Ie allocate memory inside alien
|
|
|
|
* space and copy engine code there. Then re-map the engine
|
|
|
|
* locally, so we will get an easy way to access engine memory
|
|
|
|
* without using ptrace at all.
|
|
|
|
*/
|
|
|
|
ctl->remote_map = mmap_seized(ctl, NULL, (size_t)parasite_size,
|
2012-02-13 23:17:51 +04:00
|
|
|
PROT_READ | PROT_WRITE | PROT_EXEC,
|
|
|
|
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
|
|
|
|
if (!ctl->remote_map) {
|
2011-09-30 14:37:12 +04:00
|
|
|
pr_err("Can't allocate memory for parasite blob (pid: %d)\n", pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err_restore;
|
2011-09-23 12:00:45 +04:00
|
|
|
}
|
|
|
|
|
2012-02-12 14:50:34 +04:00
|
|
|
ctl->map_length = round_up(parasite_size, PAGE_SIZE);
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-17 01:39:36 +04:00
|
|
|
fd = open_proc_rw(pid, "map_files/%p-%p",
|
2012-02-15 18:00:50 +04:00
|
|
|
ctl->remote_map, ctl->remote_map + ctl->map_length);
|
2012-02-17 01:39:36 +04:00
|
|
|
if (fd < 0)
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err_restore;
|
2011-09-23 12:00:45 +04:00
|
|
|
|
2012-02-12 14:50:34 +04:00
|
|
|
ctl->local_map = mmap(NULL, parasite_size, PROT_READ | PROT_WRITE,
|
2012-02-13 23:17:51 +04:00
|
|
|
MAP_SHARED | MAP_FILE, fd, 0);
|
2012-02-12 14:50:34 +04:00
|
|
|
close(fd);
|
|
|
|
|
|
|
|
if (ctl->local_map == MAP_FAILED) {
|
2012-02-15 18:00:50 +04:00
|
|
|
ctl->local_map = NULL;
|
2012-02-12 14:50:34 +04:00
|
|
|
pr_perror("Can't map remote parasite map");
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err_restore;
|
2012-02-12 14:50:34 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
pr_info("Putting parasite blob into %p->%p\n", ctl->local_map, ctl->remote_map);
|
2012-02-13 21:49:18 +04:00
|
|
|
memcpy(ctl->local_map, parasite_blob, sizeof(parasite_blob));
|
2012-02-12 14:50:34 +04:00
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
/* Setup the rest of a control block */
|
2012-02-13 23:17:51 +04:00
|
|
|
ctl->parasite_ip = PARASITE_HEAD_ADDR((unsigned long)ctl->remote_map);
|
|
|
|
ctl->addr_cmd = (void *)PARASITE_CMD_ADDR((unsigned long)ctl->local_map);
|
|
|
|
ctl->addr_args = (void *)PARASITE_ARGS_ADDR((unsigned long)ctl->local_map);
|
2012-02-12 14:51:38 +04:00
|
|
|
|
2012-02-01 16:23:50 +03:00
|
|
|
ret = parasite_init(ctl, pid);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("%d: Can't create a transport socket\n", pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err_restore;
|
2012-02-01 16:23:50 +03:00
|
|
|
}
|
|
|
|
|
2012-03-01 19:01:05 +04:00
|
|
|
ctl->signals_blocked = 1;
|
|
|
|
|
2012-02-01 16:23:50 +03:00
|
|
|
ret = parasite_set_logfd(ctl, pid);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("%d: Can't set a logging descriptor\n", pid);
|
2012-02-15 18:00:50 +04:00
|
|
|
goto err_restore;
|
2012-02-01 16:23:50 +03:00
|
|
|
}
|
|
|
|
|
2011-09-23 12:00:45 +04:00
|
|
|
return ctl;
|
|
|
|
|
2012-02-15 18:00:50 +04:00
|
|
|
err_restore:
|
|
|
|
parasite_cure_seized(ctl);
|
2012-02-13 23:17:51 +04:00
|
|
|
|
2011-09-23 12:00:45 +04:00
|
|
|
err:
|
2012-02-13 23:17:51 +04:00
|
|
|
xfree(ctl);
|
2011-09-23 12:00:45 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else /* CONFIG_X86_64 */
|
|
|
|
# error x86-32 is not yet implemented
|
|
|
|
#endif /* CONFIG_X86_64 */
|