As was said in overall design of
systrace, communication of kernel and userland portion is done via
systrace device /dev/systrace.
Userland systrace process gets questions from kernel portion via
poll()/read() and sends replies back via ioctl().
Life cycle of userland portion looks like this:
main()
intercept_run() /* runs traced binary */
[ or run binary via daemon() and attach to it via intercept_attach(); ]
while (intercept_read(trfd) != -1) {
intercept_read()
-> intercept.read()
-> fbsd_read()
read(fd, &msg, sizeof(msg)); /* read msg from /dev/systrace */
switch(msg.msg_type){
case SYSTR_MSG_ASK:
-> intercept_syscall()
-> intercept_sccb_find(emulation, name);
[ intercept_translate() ] (optionaly)
...
if (!ic_abort)
action = (*sc->cb)(fd, ...);
else
action = ICPOLICY_NEVER;
} else if (intercept_gencb != NULL)
action = (*intercept_gencb)(fd, ...);
/* Resume execution of the process */
intercept.answer(fd, ...);
case SYSTR_MSG_RES:
...
case SYSTR_MSG_CHILD:
...
}
}
systrace_dumppolicy(); /* eventualy save generated policy to file */
|
fig.1: /bin/systrace lifecycle (pseudocode), arrows hint function call
/bin/systrace execs traced binary via intercept_run(), optionaly
it will run it in background via daemon() and then it will attach to it
via intercept_attach(). If you are using xsystrace, it will start it here.
The whole concept of intercept_*() functions is that they are basically
wrapper for functions specialized for given system type.
For each system type, there is specific set of functions,
so e.g. for FreeBSD intercept_read() will call
freebsd-syscalls.c:fbsd_read().
intercept.h:intercept_system structure
contains pointers to functions from given system. In this case,
pointers to the functions from freebsd-syscalls.c.
Then systrace enters intercept_read() cycle, in which it reads messages
from kernel and responds to them via intercept.answer(). This will of
course call fbsd_answer(), which creates and fills
struct systrace_answer structure and passes it to
kernel via ioctl(fd, STRIOCANSWER, &ans).
syscall will be processed or not according to this answer.
kernel portion then updates str_process structure and wakes up the
process.
More details on intercept.read():
intercept.read()
-> fbsd_read()
SYSTR_MSG_ASK /* kernel asks for policy for this given syscall */
solves via intercept_syscall()
SYSTR_MSG_RES /* kernel reports syscall result to userland */
intercept_syscall_result()
/* speciall execve handling */
SYSTR_MSG_EMUL
fbsd_set_emulation()
fbsd_switch_emulation()
intercept_syscall_result()
SYSTR_MSG_UGID /* kernel reports real uid/gid */
intercept_ugid()
fbsd_answer()
SYSTR_MSG_CHILD /* kernel informs about child exec/exit */
intercept_child_info()
SYSTR_MSG_POLICYFREE /* kernel signales to free the policy in userland */
intercept_policy_free()
|
fig.2: how userland portion reacts on various messages from kernel
The reading cycle stops, when intercept_read()
returns -1. If police was generated, it will save it to file in
~/.systrace/.
|