cms
cms copied to clipboard
Sandbox does not restrict file access and execution
The sandbox does not restrict writing to local files and allows access to a large part of the directory tree, in particular everything in /usr/
. Here are some examples how this can be exploited:
-
In the Batch task type (but not restricted to Batch), linking the solution file to the output file can trick the system.
#include <unistd.h> int main() { return symlink("output.txt", "res.txt"); }
When
res.txt
gets copied to the sandbox directory, it overwrites theoutput.txt
file. This can be fixed in Sandbox.py, see this patch for a possible fix. I have not checked if this fix breaks other task types, but those who are, are vulnerable to this attack anyway. Whitelisting files as proposed in #309 would also work.This is also a privilege escalation, because the path linked to does not need to exist. It is perfectly fine to link to
/tmp/i_was_here
(or any other path) and the EvaluationService process will happily write to that file. -
Execute installed binaries and load installed libraries. For example, running a Python interpreter from C, even if Python is not an allowed language:
#include <unistd.h> static const char *code = "print(eval('+'.join(raw_input().split())))"; int main() { return execl("/usr/bin/python2", "/usr/bin/python2", "-S", "-c", code, NULL); }
This is possible because
execl
does not start a new process, it just replaces the old one.On a similar note, one could submit a source file that contains a Base64-encoding of a binary file that makes use of "unallowed features" such as being compiling with
-O3
or-pthread
, using a bigint library-lgmp
or similar. This data can be written to a file and executed like above. -
Access to all header files in
/usr/include
. This is more of a gray zone. Is it allowed to#include <boost/graph/dijkstra_shortest_paths.hpp>
? While those files should not be available in the first place, it would be nice to be able to whitelist only the standard library headers and their dependencies for compiling.
Tentative solution to the first part (and #309): https://github.com/stefano-maggiolo/cms/commits/master (last 3 commits). I'd like to add your patch as well. My addition still lacks the whitelisting on compilation (haven't checked, but I'm pretty sure you can write to a file with C's preprocessor).
Let me know what you think, thanks!
I submitted the whitelisting of writable files. I'm not sure what could be a proper solution for the other two problems.
Is there any reason to allow executing and reading of writable files (0777)? If it would be changed to writing only (0722), it will solve part 2 of the second problem (creating and executing a file). I tried this with a simple C++ program and it worked.
As for the first part of the second problem (executing files from /usr), here are some possible solutions:
- for C++ and Pascal we can just remove /usr from sandbox or check the source code or compiled binary for suspicious system calls;
- for Java (at least with Oracle JDK), everything can be controlled with grader;
- I'm not sure about Python, but is there anything useful for Python users in /usr that is not already in Python?
Thanks for the fix! A few comments:
The access should be either write-only (as artikz said) or read-and-execute-only. Assuming you consider compilation unsafe, you need to be careful with symlinks when overwriting permissions (since the symlink may point outside the sandbox). I'm not aware of a language where you can create files during compilation, though.
Change open(path, "w")
to open(path, "w").close()
so that you don't depend on a refcounting Python interpreter.
Some languages (C, C++, Pascal) can be statically linked, so there's no need for /usr
or any other system files to exist during runtime. Together with the more restrictive permissions, this solves problem 2 at least for those languages.
Other languages (Python, Java) may be restricted by white- and blacklisting files by hand. The execl-Trick works with Python too, so one might want prevent executing /usr/bin/perl
. Having no restriction by default should be fine, as long as you can manage such a whitelist in a config file.
- I will change permission from 0777 to 0722 - don't know why I didn't think of it...
- I will add
close()
as well (not that I don't trust Python to close the file, but it is more readable). - I skimmed through the CPP docs and it seems I for once overestimated its power, it shouldn't be possible to mess with external files during compilation through it. This is good because preliminary tests in closing down compilations were unsuccessful (too many temp files).
- I will investigate on a good way to make the import of /usr dependent on the language to solve 2 partially. Sadly I don't think that whitelisting files would be an easy fix as isolate doesn't allow such a fine level of control.
Not sure whether it will help, but seems that remounting /usr with noexec flag and making hard link to /usr/bin/python2.7 inside evaluation sandbox does what we want. Not checked with java though.
As for the first problem: I do not think it is a good idea to re-use the sandbox used in executing the solution for running the checker. Generally, it is not safe to recycle sandboxes without cleaning them up first. There might be symlinks, hardlinks, crazy permissions, dragons etc. in it.
Second, if you do not like Python to be executable inside the sandbox, just do not put it there. This is much easier than inventing complex access control schemes. It might be nice to let CMS configure the parameters of the sandbox (like what should be mounted where) in more pleasant way, though.
For analysis for these and other attacks, please see my paper from last year's IOI conference: https://mj.ucw.cz/papers/secgrad.pdf