-
Notifications
You must be signed in to change notification settings - Fork 5
[RFC] refactor how tool is written #45
Comments
To clarify, by pattern you mean instruction patterns that can be easily patched right?
Because they were not patched, instead they were caught by SECCOMP which traps on a ptrace tracer?
You're worried about these registers being clovered here. Since classically we only save/restore the more common CPU registers.
So we're worried about Rust standard library doing system calls as part of the work.
We would basically have to roll out our own data structures and call system calls ourselves. Granted this would be no different had we done it in C right? Assuming we don't need anything too fancy, we could insert our own mini-libc or functionality that we need. Write it once and use it everywhere? While technically unsafe, we could wrap our functions and data structures in safe interfaces.
I prefer the approach of avoiding rust stdlib all together and hand managing data structures and memory. |
Of course if we have ptrace stops or can use breakpoint instruction it would be even easier. For regular function calls, rather than save caller saved registers (
Right, with C we actually have more direct control on how the tool is linked, for rust it is harder. For instance, with C we can built libc.a from musl-libc, then link our tool with libc.a (static), then use I think use |
Are you referring here to the "shared global memory" option (rather than the message-passing/RPC approach to globalState)? We have a complicated decision tree of possible futures we're considering, so good to clarify which branch we're on ;-). |
Why is this additional "whitelisting" approach specific to |
The prerequisite is to make sure the tool shared library is a standalone library doesn't link against any other libraries, so that everything is self contained. If the guarantee satisfies, then we know it has all its It would not work if the tool linked with external library, such as glibc, because when the tool calls |
systrace allows using a tool shared library (tool) with
--tool
switch.A tool basically implements
captured_syscall
C API, so after systracesuccessfully patched a syscall site, it can generate trampoline and can jump
to
captured_syscall
, so that we can intercerpt the original syscalls.The tool is loaded by systrace using
LD_PRELOAD
, hence it is not usableafter
LD_PRELOAD
is finished. There're already about 20+ syscalls calledby
ld-linux.so
and they're not catchable. For now this is a hard limitation,however, we can still catch them by
SECCOMP
. once the tool is(LD_PRE)loaded, systrace tries to patch any syscall with predefined rules
(in
src/bpf.c
). please note we only apply patching when thesyscall
andfollowing instructions match our predefined pattern, hence, if there's no
pattern match, patching would not occur. This makes write interception code
cumbersome, because not all syscalls are catchable into
captured_syscall
function call in tracee's memory space. The plan is when such case happens,
we could use ptrace SECCOMP stop to inject
captured_syscall
, forcing traceeto do this very function call. It is relatively easy to inject real syscalls,
and we've done that in the past many times. however
captured_syscall
is aregular C function (written in rust), and it could use mmx/sse registers, hence
it would be more difficult to inject it in the tracer, nonetheless, it should
be possible with proper
xsave/xrestore
instructions.In the future, we might install a second seccomp rule in tool's init function,
so that we can patch the syscall either in tracee's memory space, or intercept
the syscall in
SIGSYS
signal handler, but this also have risks such as thedecoding of
ucontext
from the signal handler seems complicated, and redictingcontrol flow in the same task seems more difficult than ptrace.
The tool library is running in tracee's memory space, however, because we
intercept raw syscall, we must be very careful to avoid dead locks. i.e.: doing
allocations could be dangrous, drop (inserted by rust) could be dangerous
as well, because it may call
pthread_xxx
, which then may callfutex
syscall.Even there's no dead lock, doing the extra syscalls can cause performance
degration. Thus the tool must be written in a very strong constrait. We also
have a choice to use
std
orno_std
. usingno_std
allows the tool not tohave dependencies on any external library (including libc), because of that, we
can rewrite the seccomp filters, allowing all syscalls inside tool memory
range (by checking procfs). however,
no_std
variant is a lot more difficultto write, less documented, and have less libraries and features.
After serveral discussion, our
captured_syscall
could be look like:ProcessState
holds resources sharing among threads, such as unix filedescriptor, signal handlers, etc. while
ThreadState
holds resources localto any threads. The hard part is our trampoline, like a reguar syscall,
doesn't know anything, except the syscall no and six arguments. We could
allocate
ProcessState
during ptrace exec event; and allocateThreadState
both in exec event and fork/vfork/clone event. however, because the
heap belongs to the tracee only, it could be quite difficult to prepare
those data structures in the tracer, even with help of
Serialize/Deserialize
.It could be possible to abuse inject function calls once again, or we could
rewrite all tracees' global allocator, forcing them use the same heap
preallocated by the tracer. This isn't any easier by any means, i.e.: the
tracer will need to expose some APIs to claim/reclaim memory to the tracees;
so that tracees could use the exposed API to implements their own Global
Allocator; It also seems very unsafe, because any tracee have access to the
global heap, shared among the tracer and all tracees.
The text was updated successfully, but these errors were encountered: