Difference between revisions of "Compel"

From CRIU
Jump to navigation Jump to search
 
(33 intermediate revisions by 3 users not shown)
Line 1: Line 1:
'''Compel''' is a utility to execute arbitrary code in a context of a foreign process. Compel is part of CRIU, and its sources are available from [https://github.com/xemul/criu/tree/criu-dev/compel the criu-dev branch of CRIU repo, subdirectory compel].
+
'''Compel''' is a utility to execute arbitrary code in a context of a foreign process. Compel is part of CRIU, and its sources are available from [https://github.com/checkpoint-restore/criu/tree/criu-dev/compel the criu-dev branch of CRIU repo, subdirectory compel].
  
 
The code to be executed is called '''parasite code'''. Once compiled with compel flags and packed, it can be executed in other task's context. Note the code is run in environment without glibc, thus it can not call the usual stdio/stdlib/etc. functions.
 
The code to be executed is called '''parasite code'''. Once compiled with compel flags and packed, it can be executed in other task's context. Note the code is run in environment without glibc, thus it can not call the usual stdio/stdlib/etc. functions.
Line 7: Line 7:
 
== Writing parasite code ==
 
== Writing parasite code ==
  
Execution of the parasite code starts with the function
+
Execution of parasite code always starts with a function in compel std plugin that should be linked with parasite binary (see below). From the parasite code these symbols should be available for libcompel to work
  
<source lang="C">
+
; <code>parasite_trap_cmd(int cmd, void *arg);</code>
int main(void *arg_p, unsigned int arg_s);
+
: This routine gets called by <code>compel_run_in_thread()</code>
</source>
 
  
that should be present in your code. The <code>arg_p</code> and <code>arg_s</code> is the binary argument that will get delivered to parasite code by complel <code>_start()</code> call (see below). Sometimes this binary argument can be treated as CLI arguments argc/argv.
+
; <code>parasite_daemon_cmd(int cmd, void *arg);</code>
 +
: This routine gets called by <code>compel_rpc_call()</code> and <code>compel_rpc_call_sync()</code>. The <code>arg</code> points to the memory with arguments, see the [[#Arguments passing]] section below.
 +
 
 +
; <code>parasite_cleanup(void);</code>
 +
: This gets called on parasite unload by <code>compel_cure()</code>
  
 
== Compiling and packing ==
 
== Compiling and packing ==
Line 23: Line 26:
 
</pre>
 
</pre>
  
If the code consists of more than one source file, they all need to be compiled,
+
Then link the parasite binary. Include all the .o files needed.
then linked together with the proper linker flags for compel:
 
  
 
<pre>
 
<pre>
$ ld foo1.o foo2.o -o foo.o $(compel ldflags)
+
$ ld $(compel ldflags) foo1.o foo2.o $(compel plugins) -o parasite.po
 
</pre>
 
</pre>
  
Finally, pack the binary. Packing links the object file with [[compel plugins]].
+
The .po blob can now be loaded as parasite.
  
<pre>
+
== Loading blob ==
$ compel pack foo.o -o foo.compel.o [-l ''plugin'' [...]]
+
 
</pre>
+
=== Using CLI ===
  
The <code>-l</code> option specifies plugins to be added to the packed binary. The <code>std</code> plugin is added by default, i.e. it does not require any flags.
+
This functionality is in plans and not implemented yet.
  
Now, the <code>foo.compel.o</code> is ready for remote execution (note that <code>foo.o</code> was not).
+
=== Using libcompel.a library ===
  
== Running ==
+
Currently there is only one way to load the blob into victim task using libcompel.a, called ''c-header'' <ref>This is done for historical reasons. It was the most handy way to load [[parasite code]] by CRIU. In plans we have loading the .po ELF file itself</ref>. First you should make a header out of your .po file with the <code>hgen</code> action of compel tool:
  
Using CLI, the compiled and packed parasite code can be executed like this:
+
compel hgen -f parasite.po -o parasite.h
  
<pre>
+
Options meaning is the following:
$ compel run -f foo.compel.o -p ''pid'' [args ...]
+
* <code>-f</code> tells which binary to turn into header
</pre>
+
* <code>-o</code> tells where to write the resulting header
  
Alternatively, you can link it with the <code>libcompel.so</code> and use the following calls:
+
Once <code>parasite.h</code> file is ready, it should be included into the infecting program source code to be compiled with it.
  
<source lang="C">
+
== Running parasite code ==
libcompel_exec();
 
libcompel_exec_start();
 
libcompel_exec_end();
 
</source>
 
  
These library calls require binary argument that is copied into the parasite context and is available from it via arg_p/arg_s pair. When run from CLI, the arguments are packed in the usual way (argc/argv).
+
So, in order to infect a task with parasite one must do the following.
  
== Communicating ==
+
* Stop the task. This is done by calling <code>compel_stop_task(int pid)</code>. Its return value should be saved in case it's positive (it contains the task state).
 +
* Prepare the infection handler. This is done by calling <code>compel_prepare(int pid)</code>. The return value is an opaque pointer to <code>struct parasite_ctl</code>.
 +
* Run the remote code:
 +
** Execute a system call with <code>compel_syscall(ctl, int syscall_nr, long *ret, int arg ...)</code> (all 6 parameters)
 +
** Infect the victim with the parasite blob by calling <code>compel_infect(ctl, nr_thread, size_of_args_area)</code>
 +
* Cure the victim by calling <code>compel_cure(ctl)</code>. Note that <code>ctl</code> pointer is freed by the call so it should not be used thereafter.
 +
* Resume the task by calling <code>compel_resume_task(pid, orig_state, state)</code> with the saved state value from the first step.
  
There are several ways to pass parameters to the parasite code.
+
=== Infecting ===
  
If you run the parasite binary from CLI, the command line arguments after <code>--</code> are passed into the parasite's <code>main()</code> function.
+
Infecting the victim with a parasite blob needs some special treatment.
  
<pre>
+
First, the <code>ctl</code> should be configured with the blob information. For that,
$ compel run -f foo.compel.o -p 123 -- arg1 arg2 arg3
+
you should call <code>''PREFIX''_setup_c_header()</code> function
</pre>
+
with <code>ctl</code> as an argument. Here <code>''PREFIX''</code>
 +
is the same as the argument given to <code>-p</code> option
 +
to <code>compel hgen</code>, and if not given, it is derived
 +
from the input file name, dropping the path and the suffix
 +
(in the above example it's <code>parasite</code>).
  
In <code>main()</code>, the standard <code>argc</code> and <code>argv</code> can be obtained using the following code:
+
Second, when infecting a parasite one should specify the amount of threads it will mess with (1, if only the thread leader will be accessed) and the maximum size of the memory area shared between infecting task and parasite used for arguments/result passing.
  
<source lang="C">
+
=== Arguments passing ===
argc = std_argc(arg_p);
 
argv = std_argv(arg_p, argc);
 
</source>
 
  
These calls are available in <code>argv</code> [[compel plugins|plugin]]. Now, argc and argv can be handled as usual.
+
To pass arguments between the infecting code and parasite, one may call <code>compel_parasite_args(ctl, type)</code> and get the pointer where it can put data. Subsequent calls to <code>compel_rpc_call[_sync]()</code> will result in this data visible in <code>void *arg</code> address of the <code>parasite_daemon_cmd()</code>.
  
 
== See also ==
 
== See also ==
 
* [[Compel/Usage_scenarios]]
 
* [[Compel/Usage_scenarios]]
 
* [[Compel plugins]]
 
* [[Compel plugins]]
 +
* Examples of working code available [https://github.com/checkpoint-restore/criu/tree/criu-dev/compel/test on github] <ref>Clone the repo, build the project by running <code>make</code>, then go to <code>compel/test/$test_name</code> directory and run <code>make</code> there. Running <code>spy</code> bynary runs the example. Then -- RTFS or talk to us on [https://lists.openvz.org/mailman/listinfo/criu criu@openvz.org] :)</ref>
 +
* Info about CRIU [[code blobs]] in which the same technology is used
 +
  
 +
-----
 +
<references/>
 
[[Category:Compel]]
 
[[Category:Compel]]
 +
[[Category:Editor help needed]]

Latest revision as of 14:43, 29 August 2018

Compel is a utility to execute arbitrary code in a context of a foreign process. Compel is part of CRIU, and its sources are available from the criu-dev branch of CRIU repo, subdirectory compel.

The code to be executed is called parasite code. Once compiled with compel flags and packed, it can be executed in other task's context. Note the code is run in environment without glibc, thus it can not call the usual stdio/stdlib/etc. functions.

A set of compel plugins are provided for your convenience. Plugins get linked to the parasite binary during the pack stage.

Writing parasite code[edit]

Execution of parasite code always starts with a function in compel std plugin that should be linked with parasite binary (see below). From the parasite code these symbols should be available for libcompel to work

parasite_trap_cmd(int cmd, void *arg);
This routine gets called by compel_run_in_thread()
parasite_daemon_cmd(int cmd, void *arg);
This routine gets called by compel_rpc_call() and compel_rpc_call_sync(). The arg points to the memory with arguments, see the #Arguments passing section below.
parasite_cleanup(void);
This gets called on parasite unload by compel_cure()

Compiling and packing[edit]

Compile the source of your parasite code with compel flags:

$ gcc -c foo1.c -o foo1.o $(compel cflags)

Then link the parasite binary. Include all the .o files needed.

$ ld $(compel ldflags) foo1.o foo2.o $(compel plugins) -o parasite.po 

The .po blob can now be loaded as parasite.

Loading blob[edit]

Using CLI[edit]

This functionality is in plans and not implemented yet.

Using libcompel.a library[edit]

Currently there is only one way to load the blob into victim task using libcompel.a, called c-header [1]. First you should make a header out of your .po file with the hgen action of compel tool:

compel hgen -f parasite.po -o parasite.h

Options meaning is the following:

  • -f tells which binary to turn into header
  • -o tells where to write the resulting header

Once parasite.h file is ready, it should be included into the infecting program source code to be compiled with it.

Running parasite code[edit]

So, in order to infect a task with parasite one must do the following.

  • Stop the task. This is done by calling compel_stop_task(int pid). Its return value should be saved in case it's positive (it contains the task state).
  • Prepare the infection handler. This is done by calling compel_prepare(int pid). The return value is an opaque pointer to struct parasite_ctl.
  • Run the remote code:
    • Execute a system call with compel_syscall(ctl, int syscall_nr, long *ret, int arg ...) (all 6 parameters)
    • Infect the victim with the parasite blob by calling compel_infect(ctl, nr_thread, size_of_args_area)
  • Cure the victim by calling compel_cure(ctl). Note that ctl pointer is freed by the call so it should not be used thereafter.
  • Resume the task by calling compel_resume_task(pid, orig_state, state) with the saved state value from the first step.

Infecting[edit]

Infecting the victim with a parasite blob needs some special treatment.

First, the ctl should be configured with the blob information. For that, you should call PREFIX_setup_c_header() function with ctl as an argument. Here PREFIX is the same as the argument given to -p option to compel hgen, and if not given, it is derived from the input file name, dropping the path and the suffix (in the above example it's parasite).

Second, when infecting a parasite one should specify the amount of threads it will mess with (1, if only the thread leader will be accessed) and the maximum size of the memory area shared between infecting task and parasite used for arguments/result passing.

Arguments passing[edit]

To pass arguments between the infecting code and parasite, one may call compel_parasite_args(ctl, type) and get the pointer where it can put data. Subsequent calls to compel_rpc_call[_sync]() will result in this data visible in void *arg address of the parasite_daemon_cmd().

See also[edit]



  1. This is done for historical reasons. It was the most handy way to load parasite code by CRIU. In plans we have loading the .po ELF file itself
  2. Clone the repo, build the project by running make, then go to compel/test/$test_name directory and run make there. Running spy bynary runs the example. Then -- RTFS or talk to us on criu@openvz.org :)