Compel
Compel temporary GIT repo is at http://github.com/xemul/compel. Now compel sits in the main criu repo at https://github.com/xemul/criu/tree/criu-dev/compel.
Compel
An utility to execute code in foreign process address space. The code should be compiled with compel flags and packed, then it can be executed in other task's context. The parasite code executes in an environment w/o glibc, thus you cannot call the usual stdio/stdlib/etc. functions. Compel provides a set of plugins for your convenience. Plugins get linked to parasite binary on the pack stage (see below).
How we want it to look like
Execution of the parasite code starts with the function
int main(void *arg_p, unsigned int arg_s);
that should be present in your code. The arg_p and arg_s is the binary argument that will get delivered to parasite code by complel _start() call (see below). Sometimes this binary argument can be treated as CLI arguments argc/argv.
Compile the sources and pack the binary
Take a program on C and compile it with compel flags
$ gcc -c foo.c -o foo.o $(compel cflags) -I
To combine the foo.o out of many sources, they should all be linked with compel flags as well
$ ld foo_1.o foo_2.o -o foo.o $(compel ldflags)
The compel-headers is devel/include/ after make install-devel.
Pack the binary. Packing would link the object file with compel plugins (see below)
$ compel pack foo.o -o foo.compel.o -L [-l]
The compel-libs is devel/lib/compel/ after make install-devel.
The foo.compel.o is ready for remote execution (foo.o was not).
Execute the code remotely
Using CLI like this
$ compel run -f foo.compel.o -p $pid
Or, you can link with libcompel.so and use
libcompel_exec() libcompel_exec_start()/libcompel_exec_end()
calls described in include/compel/compel.h header. The test/ directory contains several examples of how to launch parasites.
The library calls require binary argument that will get copied into parasite context and passed to it via arg_p/arg_s pair. When run from CLI the arguments are packed in argc/argv manner.
How to communicate to parasite code
There are several ways for doing this.
If you run the parasite binary from CLI, the tail command line arguments are passed into the parasite main() function.
$ compel run -f foo.compel.o -p 123 -- arg1 arg2 arg3
In the main() common argc and argv are accessed using the
argc = std_argc(arg_p); argv = std_argv(arg_p, argc);
calls. Then use argc and argv as you would use them in normal C program run from shell.
If you run the parasite using library _start/_end calls, you can pass file descriptors to parasite using fds plugin or setup shmem between these two using shmem plugin. See test/async_fds/ and test/async_shmem/ for code examples.
Plugins
- std
- This plugins gets packed with your binary by default and provides standard Linux system calls, strings functions and printf-like priting helper.
- fds
- This one allows you to send and receive fds in parasite to/from the master process.
- shmem
- This one sets up shared memory between parasite and master.
Usage ideas
One thing parasite code can do is call clone() and create thread having access to main process VM, FDT, FS, etc. The new thread can then
- Check socket FDs to get stuck/closed by polling them
- Apply "logrotate" on the fly
- Garbage collector
- Catch SIGSEGV, do smth with mappings and act upon "illegal" memory access
- Remote swap for task
- WSS detection
Another is to do some activity on the victim and then just unload. With this we can
- Death detection. Open pipe/socket and pass the other end outside. Once the victim dies the pipe/socket will wake up.
- Binary updates. E.g. live patching or libs relink
- Tunneling -- replace opened socket with unix one, and send the former one to the caller
- Inject socket spy
- Pack/Unpack
- Crypt/Decrypt
- Traffic analyzer
- Traffic fanout (multiplex)
- The same for files on disks -- proxy via pipe(s)
- Filter/split logs
- Do "nohup" on the fly
- Debug stuff by MSG_PEEK-ing sockets messages of tee+splice sockets
- Re-connect sleeping sockets to other addresses (not 100% safe)
- "Soft" restart of a service -- call execve() from it's context
- Force entering into CT (except pid namespace, probably)
- Re-open all files (and cwd, root) to facilitate moving on new / (e.g. for disk replacement)
- Remove leaks from e.g. malloc/free heap
- Force reparent (pid change!)
- Re-open all files -- force daemonize