Skip to content

Commit

Permalink
Limit input and output sizes
Browse files Browse the repository at this point in the history
Use also the highest pickle protocol instead of the lowest protocol
  • Loading branch information
vstinner committed Mar 22, 2012
1 parent 29d199a commit b8cb3a1
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 34 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Expand Up @@ -11,6 +11,7 @@ Version 1.6
* stdin, stdout and stderr are redirected to /dev/null (or :NUL on Windows)
* Drop the "interpreter" configuration feature: move the code to interpreter.py
* Replace sys.path and other related variables
* Limit input and output sizes

Version 1.5 (2012-03-20)
------------------------
Expand Down
1 change: 1 addition & 0 deletions README
Expand Up @@ -29,6 +29,7 @@ limits:
* number of child process = 0 (disable fork or thread at the OS level)
* pysandbox is able to catch crashes like segmentation fault (SIGSEGV)
* stdin, stdout and stderr are redirected to /dev/null (or :NUL on Windows)
* input and output data are limited to 64 KB

Protection of the namespace:

Expand Down
14 changes: 14 additions & 0 deletions sandbox/config.py
Expand Up @@ -82,9 +82,15 @@ def __init__(self, *features, **kw):
if self._use_subprocess:
self._timeout = DEFAULT_TIMEOUT
self._max_memory = 50 * 1024 * 1024
# size in bytes of all input objects serialized by pickle
self._max_input_size = 64 * 1024
# size in bytes of the result serialized by pickle
self._max_output_size = 64 * 1024
else:
self._timeout = None
self._max_memory = None
self._max_input_size = None
self._max_output_size = None

# open() whitelist: see safe_open()
self._open_whitelist = set()
Expand Down Expand Up @@ -208,6 +214,14 @@ def _set_timeout(self, timeout):
def max_memory(self):
return self._max_memory

@property
def max_input_size(self):
return self._max_input_size

@property
def max_output_size(self):
return self._max_output_size

@property
def import_whitelist(self):
return dict((name, (tuple(value[0]), tuple(value[1])))
Expand Down
6 changes: 4 additions & 2 deletions sandbox/subprocess_child.py
Expand Up @@ -9,6 +9,8 @@
except ImportError:
resource = None

PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL

def set_process_limits(config):
if resource is not None:
# deny fork and thread
Expand Down Expand Up @@ -65,7 +67,7 @@ def execute_child():
output_data['locals'] = locals
except base_exception, err:
output_data = {'error': err}
pickle.dump(output_data, output)
pickle.dump(output_data, output, PICKLE_PROTOCOL)
output.flush()
output.close()

Expand All @@ -78,7 +80,7 @@ def call_child(wpipe, sandbox, func, args, kw):
except BaseException, err:
data = {'error': err}
output = os.fdopen(wpipe, 'wb')
pickle.dump(data, output)
pickle.dump(data, output, PICKLE_PROTOCOL)
output.flush()
output.close()
if config.has_feature("stdout"):
Expand Down
76 changes: 44 additions & 32 deletions sandbox/subprocess_parent.py
Expand Up @@ -67,41 +67,53 @@ def execute_subprocess(sandbox, code, globals, locals):
finally:
os.umask(old_umask)

with input_file:
with output_file:
input_data = {
'code': code,
'config': sandbox.config,
'locals': locals,
'globals': globals,
}
args = (
sys.executable,
# FIXME: use '-S'
'-E',
'-m', 'sandbox.subprocess_child',
input_file.name, output_file.name)
pickle.dump(input_data, input_file)
# FIXME: check data size?
input_file.flush()
try:
input_data = {
'code': code,
'config': sandbox.config,
'locals': locals,
'globals': globals,
}
args = (
sys.executable,
# FIXME: use '-S'
'-E',
'-m', 'sandbox.subprocess_child',
input_file.name, output_file.name)
pickle.dump(input_data, input_file)
input_file.flush()
if sandbox.config.max_input_size:
size = input_file.tell()
if size > sandbox.config.max_input_size:
raise SandboxError("Input data are too big: %s bytes (max=%s)"
% (size, sandbox.config.max_input_size))

# create the subprocess
process = subprocess.Popen(args, close_fds=True, shell=False)
# create the subprocess
process = subprocess.Popen(args, close_fds=True, shell=False)

# wait data
exitcode = process.wait()
if exitcode:
if os.name != "nt" and exitcode < 0:
signum = -exitcode
if signum == SIGALRM:
raise Timeout()
text = "subprocess killed by signal %s" % signum
else:
text = "subprocess failed with exit code %s" % exitcode
raise SandboxError(text)
# wait data
exitcode = process.wait()
if exitcode:
if os.name != "nt" and exitcode < 0:
signum = -exitcode
if signum == SIGALRM:
raise Timeout()
text = "subprocess killed by signal %s" % signum
else:
text = "subprocess failed with exit code %s" % exitcode
raise SandboxError(text)

output_data = pickle.load(output_file)
# FIXME: check data size?
if sandbox.config.max_output_size:
output_file.seek(0, 2)
size = output_file.tell()
output_file.seek(0)
if size > sandbox.config.max_output_size:
raise SandboxError("Output data are too big: %s bytes (max=%s)"
% (size, sandbox.config.max_output_size))
output_data = pickle.load(output_file)
finally:
input_file.close()
output_file.close()

if 'error' in output_data:
raise output_data['error']
Expand Down

0 comments on commit b8cb3a1

Please sign in to comment.