I addressed low overhead access to the memory space of a traced process in a previous post. That technique was based on creating anonymous pipes between the tracer and the tracees. While that methods works without any problem, it cannot be used to access the memory space of child processes created by the tracees. The anonymous pipes can connect a parent process to its children and since the children of the tracees are not created by the tracer, the tracer cannot establish pipes to them.
Using named pipes (FIFOs) instead of anonymous pipes solves the problem. FIFOs can be established between processes, regardless of whether they are related or not. The mechanism used to read from or write to the FIFOs is the same as the one described for pipes. The only difference is that we postpone creation and connection to the FIFOs until they are needed.
The downside of using FIFOs is the higher security risk, since any process can connect to them and try to read their contents. When we create the FIFOs, their permissions are set so that only the user who has executed the monitor can read or write to them. Therefore, the risk is limited to the case of a malicious program that is executed in the context of the same user or a super user. Both cases are possible only when the system is already compromised.
We measured the performance of FIFOs versus ptrace and observed that for buffer sizes of 160 bytes or smaller, using ptrace is more efficient than using FIFOs, but the time needed to transfer buffers using ptrace increases linearly with the buffer sizes. Transferring a 4KB buffer using ptrace takes 16 times as much as it takes using FIFOs.
Subscribe to:
Post Comments (Atom)
5 comments:
If you're on a modernish Unix system, I think you could probably create a pipe between tracer and tracee, and then have tracee pass the file descriptor for its endpoint to the new child. That would get you back down to the 40-byte threshold, if I'm understanding correctly.
This mailing list post has a good an explanation as you're likely to find of the mechanics.
Thank you very much for your comment.
I guess the method explained in the link you provided, does not work for my case.
In my project, there is one main tracer that spawns two or more tracees. To make it simple, let's say it spawns just one tracee. We can create pipes between the tracer and the tracee, but if the tracee creates a new child, we also create a new tracer thread and let the new thread trace the child. Now, we need new communication channels between the new tracer and the new tracee and just passing the file descriptors of the existing pipes to them does not work. Those pipes would be used to communicate between the old tracer and tracee and we need new channels.
The method explained in that link explains how to pass open file descriptors to another process.
Right, I meant that you start with a pipe between tracer and tracee, and then when the tracee spawns tracee2, there is another pipe created. Initially that also connects tracer and tracee, but tracee then passes its end of the pipe to tracee2. tracer's new thread should be able to access the tracer end of the new pipe fine, since file descriptors are shared among threads, but if tracer2 is a separate process you can pass the fd on that side as well.
tracing the main program:
tracer [pipe] tracee
spawn tracee2:
tracer [pipe] tracee
tracee2
create a new pipe between tracer and tracee:
tracer [pipe] tracee
tracer [pipe2] tracee
tracee2
pass the tracee end of pipe2 to tracee2, and close on tracee:
tracer [pipe] tracee
tracer [pipe2] tracee2
(This is all assuming that you can't just do the setup of the new pipe2 in tracee before forking tracee2, in which case it could be inherited more simply.)
You'll need a unix socket over which to pass the pipe endpoint to traced children, but that's roughly the same as setting up the FIFO, and you only have to use it for the handshake.
There was also a time, at least on Linux, when localhost networking was faster than some FIFO constructs. You might try UDP (with all the checksums turned off) on a lark as well, though the namespace of sockets is a little smaller than that of FIFOs. :)
Thanks again for your thorough explanation.
I will definitely consider your method and try to implement it.
Regarding the localhost sockets, I think they are not appropriate for this project, because it is very important that no process from other sessions can send any information to the tracer.
This is great info to know.
Post a Comment