Skip to content

Commit 783b602

Browse files
committed
Self buffer sandbox output so it won't lock up if OS buffer fills
1 parent c06ded9 commit 783b602

File tree

2 files changed

+37
-26
lines changed

2 files changed

+37
-26
lines changed

worker/jailguard.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ def __init__(self, args):
2323
self.running = True
2424

2525
self.out_queue = Queue()
26+
self.child_queue = Queue()
2627
self.child = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
2728
self.checked_pids.add(self.child.pid)
2829
self.child_pids.add(self.child.pid)
2930
self.child_streams = 2
3031
Thread(target=self.reader, args=("STDOUT", self.child.stdout)).start()
3132
Thread(target=self.reader, args=("STDERR", self.child.stderr)).start()
3233
Thread(target=self.writer).start()
34+
Thread(target=self.child_writer).start()
3335
cmd_thread = Thread(target=self.cmd_loop, args=(sys.stdin,))
3436
cmd_thread.daemon = True
3537
cmd_thread.start()
@@ -54,6 +56,16 @@ def reader(self, name, pipe):
5456
self.child_streams -= 1
5557
queue.put(None)
5658

59+
def child_writer(self):
60+
queue = self.child_queue
61+
stdin = self.child.stdin
62+
while True:
63+
ln = queue.get()
64+
if ln is None:
65+
break
66+
stdin.write(ln)
67+
stdin.flush()
68+
5769
def cmd_loop(self, pipe):
5870
while True:
5971
cmd = pipe.readline()
@@ -84,8 +96,7 @@ def cmd_loop(self, pipe):
8496
raise
8597
self.out_queue.put(("SIGNALED", time.time(), "CONT"))
8698
elif cmd.startswith("SEND"):
87-
self.child.stdin.write(cmd[5:])
88-
self.child.stdin.flush()
99+
self.child_queue.put(cmd[5:])
89100
else:
90101
self.kill()
91102
raise ValueError("Unrecognized input found '%s'" % (cmd,))
@@ -116,6 +127,7 @@ def run(self):
116127
time.sleep(_UPDATE_INTERVAL)
117128
self.running = False
118129
self.out_queue.put(None)
130+
self.child_queue.put(None)
119131

120132
if __name__ == "__main__":
121133
g = Guard(sys.argv[1:])

worker/sandbox.py

+23-24
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,7 @@ def resume(self):
227227
def write(self, data):
228228
"""Write str to stdin of the process being run"""
229229
for line in data.splitlines():
230-
if not self.write_line(line):
231-
return False
232-
return True
230+
self.write_line(line)
233231

234232
def write_line(self, line):
235233
"""Write line to stdin of the process being run
@@ -244,8 +242,6 @@ def write_line(self, line):
244242
self.command_process.stdin.flush()
245243
except (OSError, IOError):
246244
self.kill()
247-
return False
248-
return True
249245

250246
def read_line(self, timeout=0):
251247
"""Read line from child process
@@ -316,6 +312,7 @@ def is_alive(self):
316312
sub_result = self.command_process.poll()
317313
if sub_result is None:
318314
return True
315+
self.child_queue.put(None)
319316
self._is_alive = False
320317
return False
321318

@@ -324,6 +321,7 @@ def start(self, shell_command):
324321
if self.is_alive:
325322
raise SandboxError("Tried to run command with one in progress.")
326323
working_directory = self.working_directory
324+
self.child_queue = Queue()
327325
shell_command = shlex.split(shell_command.replace('\\','/'))
328326
try:
329327
self.command_process = subprocess.Popen(shell_command,
@@ -342,11 +340,12 @@ def start(self, shell_command):
342340
args=(self.command_process.stderr, self.stderr_queue))
343341
stderr_monitor.daemon = True
344342
stderr_monitor.start()
343+
Thread(target=self._child_writer).start()
345344

346345
def kill(self):
347346
"""Stops the sandbox.
348347
349-
Stops down the sandbox, cleaning up any spawned processes, threads, and
348+
Shuts down the sandbox, cleaning up any spawned processes, threads, and
350349
other resources. The shell command running inside the sandbox may be
351350
suddenly terminated.
352351
@@ -357,6 +356,7 @@ def kill(self):
357356
except OSError:
358357
pass
359358
self.command_process.wait()
359+
self.child_queue.put(None)
360360

361361
def retrieve(self):
362362
"""Copy the working directory back out of the sandbox."""
@@ -399,17 +399,25 @@ def resume(self):
399399
except (ValueError, AttributeError, OSError):
400400
pass
401401

402+
def _child_writer(self):
403+
queue = self.child_queue
404+
stdin = self.command_process.stdin
405+
while True:
406+
ln = queue.get()
407+
if ln is None:
408+
break
409+
try:
410+
stdin.write(ln)
411+
stdin.flush()
412+
except (OSError, IOError):
413+
self.kill()
414+
break
415+
402416
def write(self, str):
403417
"""Write str to stdin of the process being run"""
404418
if not self.is_alive:
405419
return False
406-
try:
407-
self.command_process.stdin.write(str)
408-
self.command_process.stdin.flush()
409-
except (OSError, IOError):
410-
self.kill()
411-
return False
412-
return True
420+
self.child_queue.put(str)
413421

414422
def write_line(self, line):
415423
"""Write line to stdin of the process being run
@@ -419,13 +427,7 @@ def write_line(self, line):
419427
"""
420428
if not self.is_alive:
421429
return False
422-
try:
423-
self.command_process.stdin.write(line + "\n")
424-
self.command_process.stdin.flush()
425-
except (OSError, IOError):
426-
self.kill()
427-
return False
428-
return True
430+
self.child_queue.put(line + "\n")
429431

430432
def read_line(self, timeout=0):
431433
"""Read line from child process
@@ -495,10 +497,7 @@ def main():
495497
print()
496498
sandbox.start(" ".join(args))
497499
for line in options.send_lines:
498-
if not sandbox.write_line(line):
499-
print("Could not send line '%s'" % (line,), file=sys.stderr)
500-
sandbox.kill()
501-
sys.exit(1)
500+
sandbox.write_line(line)
502501
print("sent: " + line)
503502
time.sleep(options.send_delay)
504503
while True:

0 commit comments

Comments
 (0)