kmaork / hypno

Inject python code into a running python process
MIT License
85 stars 12 forks source link

Support injecting into containers in linux #18

Open kmaork opened 1 year ago

kmaork commented 1 year ago

Can check if the target process has a different /proc/<pid>/task/<pid>/ns/mnt. If it does, create the shared library in /proc/pid/root instead in the current filesystem. Python's tempfile module doesn't support searching for a tempdir in a different filesystem, and it caches it for the whole process once it's found. So maybe we can create a subprocess, nsenter the target filesystem, and then continue normally. Alternatively, just copy the logic in tempfile or assume that /tmp is writable.

cakemanny commented 1 month ago

Hi, I think this sounds really useful.

I think there may also need to be a change to the pyinjector.inject(...) API. Currently it checks that the shared library exists before injecting.

A couple of ideas came to mind already but neither are too satisfying:

pyinjector could relativize the path when it sees that it starts with /proc/<pid>/root

pyinjector.inject(123, '/proc/123/root/path/to/lib.so')

# ->

injector.inject(b'/path/to/lib.so')

Or it could double check /proc/<pid>/root when the path is not initially found

pyinjector.inject(123, '/path/to/lib.so')

# ->

if ... and not os.path.isfile('/proc/123/root/path/to/lib.so'):
   raise ...
kmaork commented 1 month ago

Hmmm how about just always using /proc/pid/root both for the shared library tmpdir in hypno and for the existence check in pyinjector?

cakemanny commented 1 month ago

Ahh, you mean NamedTemporaryFile(..., dir=f'/proc/{pid}/root') instead of NamedTemporaryFile(..., dir=f'/proc/{pid}/root/tmp') ? I think it's quite common to mount / as readonly. I think it's less common to not have /tmp mounted read/write. But maybe I've misunderstood. ...

I was thinking over this some more this morning and I think I've perhaps come up with a clearer API for pyinjector

def inject(pid: int, library_path: AnyStr, uninject: bool = False,
           process_root: AnyStr = "") -> int:
           ...

which would be called, in those examples, like so

    injector.inject(b'/path/to/lib.so', process_root=b'/proc/123/root')

This has the advantage that it extends generally to any kind of chroot jail and that pyinjector doesn't have to bake in any knowledge about Linux's /proc filesystem. The caller is responsible for making it clear at what path they expect the target process to see the library.

kmaork commented 1 month ago

In hypno I just meant that it's possible to always look for a tempdir in the target process' fs using /proc/pid/root, regardless if it's the same or not as /.

In pyinjector, I like your suggestion, but I think that it'd also be useful to always check for existence of the shared library in the target process' fs (again using /proc/pid/root) by default. The additional argument you suggested could still be used to override this default behavior.