0

I'm working on a project where I will be running potentially malicious code. It's basic organization is that there is a master and a slave process. The slave process runs the potentially malicious code, and has seccomp enabled.

import prctl

prctl.set_seccomp(True)

This is how seccomp is turned on. I can communicate fine FROM the slave TO the master, but not the other way around. When I don't turn on seccomp, I can use:

import sys

lines = sys.stdin.read()

Or something along those lines. I found this quite odd, I should have access to read and write given the default parameters of seccomp, especially for stdin/out. I have even tried opening stdin before I turn on seccomp. For example.

stdinFile = sys.stdin
prctl.set_seccomp(True)
lines = stdinFile.read()

But still to no avail. I have also tried readlines() which doesn't work. A friend suggested that I try Unix Domain Sockets, opening it before seccomp goes on, and then just using the write() call. This didn't work either. If anyone has any suggestions on how to combat this problem, please post them! I have seen some code in C for something like

seccomp_add_rule(stuff)

But I have been unsuccessful at using this in Python with the cffi module.

KosherBacon
  • 172
  • 1
  • 11
  • 1
    What does "to no avail" mean? Does it raise an exception? Return EOF (an empty string)? Hang forever? Abort your program with a signal? …? – abarnert Aug 11 '14 at 05:25
  • 1
    Also, if you're just trying to do one big `sys.stdin.read()`, why not just do that before calling `set_seccomp(True)`? – abarnert Aug 11 '14 at 05:26
  • 1
    As a side note, none of the stuff you're trying makes any sense. `stdinFile = sys.stdin` doesn't "open stdin", it just makes `stdinFile` another reference to the same stdin that's already open. Using `readlines` instead of `read` isn't going to help anything. (Also, it's more than a little misleading to `read()` the whole thing into one giant string, but call that string `lines`.) – abarnert Aug 11 '14 at 05:27
  • Hey, thanks for the quick replies. @abarnert for your first question, the process is killed with I think a SIGKILL. I'm sorry about some of the confusion with my phrasing. I can't call read() before seccomp because the potentially malicious code needs results from a database request (supposed to), which I run in the master, then I want to pipe results to the slave process. For your third piece, it was something that I had seen online, but was just a shot in the dark. As well as in general the slave code which is really just an exec(code) piece wants data from the master (which I am okay with). – KosherBacon Aug 11 '14 at 05:56
  • 1
    Why do you even need to fire off the slave before reading the database request? Is there additional work the slave needs to do before that? – abarnert Aug 11 '14 at 05:59
  • @abarnert there isn't. That might solve part of the problem, silly me. But the slave still might make a request and expect a response. This would be during the exec(code) piece which must have seccomp on. So a user submits code, I pass it to a slave process, it runs with seccomp on, but the user may decide mid-way through their code that he/she wants to submit a request and know whether or not it failed. – KosherBacon Aug 11 '14 at 06:35
  • 1
    I realize this is a workaround rather than an actual solution (which is part of why I posted it as a comment rather than an answer). But you might be able to expand the solution by firing off new slave processes (that each read `stdin` before `set_setcomp`); I don't know if that works for your use case or not, but it might. – abarnert Aug 11 '14 at 17:37
  • @abarnert I think that would solve my initial issue. Any ideas on how I could use something like that to pipe stdin half way through computing user code (seccomp on)? I'm really quite baffled because seccomp is supposted to allow "read(), write(), _exit(), and sigreturn()" but I can't use the read() command at all. – KosherBacon Aug 11 '14 at 18:38
  • 1
    Yeah, I don't understand it either. If you can read off a _different_ pipe, just not stdin for some reason, you could obviously use that for a workaround. Or if you can read a regular file that you opened before starting `seccomp`. Or if you can access shared memory. I have no idea if any of those would work, but it can't hurt to try, right? Hopefully someone will have a real solution, but until they do, better to have a fallback workaround than not… – abarnert Aug 11 '14 at 22:17
  • 1
    @abarnert thank you so much for all of the help that you've given. There is one thing that I may have found. I was digging through some documentation for seccomp in C and at a good example. It seems that placing stdin in non-blocking mode, and reading from it once does something with fstat() (I have no idea what it does) but apparently it could open up the pipe for use after seccomp is turned on. I'll see if I can implement this and whether or not it will work. – KosherBacon Aug 12 '14 at 04:03
  • 1
    That reminds me of something: If you're using Python 2.x, `sys.stdin.read` may be doing more than just a `read` at the system level, either in the Python layer or the stdio layer; maybe getting the fd with `sys.stdin.fileno()` then using `os.read` on that will work? (If you're on 3.x, you could try getting `sys.stdin.buffer.raw` and calling `read` on that instead of on `stdin` itself, but it's less likely to be an issue.) – abarnert Aug 12 '14 at 05:05
  • @abarnert I've still had no luck. What's really been getting me is that if I run something like `cat file.txt | ./script.py`, my script can read from that `stdin` and print those messages. But it is unable to when I open it up in a sub-process. `self.p = Popen([sys.executable, "script.py"], stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)` is how I am opening it. – KosherBacon Aug 13 '14 at 03:52
  • I'm pretty sure `cat` uses normal buffered stdio, just like Python 2.7 `file` objects, so that rules out a lot of possibilities with the parent side doing something wrong in writing. So the best guess is that the difference is between what `subprocess` does and what the shell does. You could test that by changing your Python script to just print stuff, then shell-pipe it to the other script; if that still works, it's probably `subprocess` piping… – abarnert Aug 13 '14 at 04:07
  • That reminds me: All versions of `subprocess` before Python 3.2 have some bugs that can lead to blocking in some weird circumstances. I don't know if the same thing could lead to your child violating the `seccomp` rules, but… can you try installing [`subprocess32`](https://pypi.python.org/pypi/subprocess32/) off PyPI and using that instead to see if it makes a difference? – abarnert Aug 13 '14 at 04:08
  • Just a heads up I'm using Python 3.4.1. – KosherBacon Aug 13 '14 at 04:42
  • Oh, that rules out a lot of possible problems. Your `subprocess` is fine, and your file objects aren't using `stdio` at all; any buffering is in pure Python. Which leaves us with even fewer possibilities. I think we've eliminated the impossible, but there's nothing improbably left, which means… that I'm not as smart as Sherlock Holmes, I guess… – abarnert Aug 13 '14 at 05:54
  • @abarnert I was wondering if it was possible to instead of writing to the stdin using the sub-process module, but rather to `/proc/pid/fd/0` (where `pid` is the sub-process's pid) or whatever the file location. I also need to find the file descriptor number for sys.stdin. If I could write to that file from the master, maybe it could work? – KosherBacon Aug 13 '14 at 14:51
  • That might work. In theory it ought to be just a symlink to the same pipe, but who knows whether `seccomp` affects that, at least without doing a lot more reading? Also, if you're launching the process, I think you can just make sure that its `stdin` is fd 0 (meaning "don't do anything weird to break what `subprocess` does) instead of trying to figure it out. If not, you mentioned that you can write in the other direction, so the child could always start off with `sys.stdout.write('{}\n'.format(sys.stdin.fileno()))` I suppose. – abarnert Aug 13 '14 at 19:39
  • @abarnert I've still had a lot of difficulty getting this to work. I have confirmed that my fd is 0, and have been writing to the correct location. However, I've been receiving illegal seek problems. I'm thinking about tryign PyPy's sandbox (something I tried earlier for a different issue, but haven't returned to). Maybe I can get this to properly do stdin and stdin out... – KosherBacon Aug 14 '14 at 23:31

1 Answers1

0

sys.stdin is not a file handle, you need to open it and get a file handle before calling set_seccomp. You could use os.fdopen for this. The file descriptor for stdin / stdout is available as sys.stdin.fileno().

totaam
  • 1,306
  • 14
  • 25