Skip to content

Commit d159688

Browse files
pgbovinemmmicedcoffee
authored andcommitted
more matrix demo stuf
1 parent 70ef00b commit d159688

File tree

4 files changed

+235
-33
lines changed

4 files changed

+235
-33
lines changed

v3/bottle_server.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,30 @@ def load_matrix_problem():
6060
return json.dumps(dict(code=cod, test=testCod))
6161

6262

63+
@get('/submit_matrix_problem')
64+
def submit_matrix_problem():
65+
user_code = request.query.submitted_code
66+
prob_name = request.query.problem_name
67+
assert type(prob_name) in (str, unicode)
68+
69+
# whitelist
70+
assert prob_name in ('python_comprehension-1',)
71+
72+
test_fn = 'matrix-demo/' + prob_name + '.test.py'
73+
test_cod = open(test_fn).read()
74+
75+
# concatenate!
76+
script = test_cod + '\n' + user_code + '\nimport doctest\ndoctest.testmod()'
77+
78+
import simple_sandbox
79+
80+
def json_finalizer(executor):
81+
return json.dumps(dict(code=executor.executed_script,
82+
user_stdout=executor.user_stdout.getvalue(),
83+
user_stderr=executor.user_stderr.getvalue()))
84+
85+
return simple_sandbox.exec_str(script, json_finalizer)
86+
87+
6388
if __name__ == "__main__":
6489
run(host='localhost', port=8080, reloader=True)

v3/js/matrixtutor.js

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -263,27 +263,24 @@ $(document).ready(function() {
263263
}
264264
});
265265

266-
$('#genUrlBtn').bind('click', function() {
267-
var myArgs = {code: pyInputCodeMirror.getValue(),
268-
mode: appMode,
269-
cumulative: $('#cumulativeModeSelector').val(),
270-
py: $('#pythonVersionSelector').val()};
271-
272-
if (appMode == 'display') {
273-
myArgs.curInstr = myVisualizer.curInstr;
274-
}
275-
276-
var urlStr = $.param.fragment(window.location.href, myArgs, 2 /* clobber all */);
277-
$('#urlOutput').val(urlStr);
278-
});
279-
280-
281266
$.get('load_matrix_problem',
282267
{problem_name: 'python_comprehension-1'},
283268
function(dataFromBackend) {
284269
pyInputCodeMirror.setValue(dataFromBackend.code.rtrim());
285270
pyTestInputCodeMirror.setValue(dataFromBackend.test.rtrim());
286271
},
287272
"json");
288-
});
289273

274+
275+
$('#submitGradeBtn').bind('click', function() {
276+
$.get('submit_matrix_problem',
277+
{submitted_code: pyInputCodeMirror.getValue(),
278+
problem_name: 'python_comprehension-1'},
279+
function(dataFromBackend) {
280+
$('#gradeStdout').val(dataFromBackend.user_stdout);
281+
$('#gradeStderr').val(dataFromBackend.user_stderr);
282+
},
283+
"json");
284+
285+
});
286+
});

v3/matrixtutor.html

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -98,30 +98,23 @@ <h1 style="margin-bottom: 15px; font-size: 14pt;">Problem: python_comprehension-
9898
<button id="submitGradeBtn" class="bigBtn" type="button">Submit for Grading</button>
9999
</p>
100100

101-
<pre id="gradeResultsPane">
102-
</pre>
101+
<p>
102+
Program output:<br/>
103+
<textarea id="gradeStdout" cols="100" rows="20" wrap="off" readonly></textarea>
104+
</p>
105+
106+
<p>
107+
Errors:<br/>
108+
<textarea id="gradeStderr" cols="100" rows="10" wrap="off" readonly></textarea>
109+
</p>
103110

104111
</div>
105112

106113
<div id="footer">
107114

108115
This version of <a href="http://pythontutor.com/">Online Python Tutor</a> supports
109116
<a href="http://www.python.org/doc/3.3.0/">Python 3.3</a> with limited module
110-
imports and no file I/O.
111-
The following modules may be imported:
112-
bisect,
113-
collections,
114-
datetime,
115-
functools,
116-
heapq,
117-
itertools,
118-
json,
119-
math,
120-
operator,
121-
random,
122-
re,
123-
string
124-
</p>
117+
imports and no file I/O.</p>
125118

126119
<p>Have a question? Maybe the <a
127120
href="https://github.com/pgbovine/OnlinePythonTutor/blob/master/v3/docs/user-FAQ.md">FAQ</a>

v3/simple_sandbox.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# This is a SUPER simple sandbox, with code copied from pg_logger.py
2+
# It's not meant to be actually secure, so ALWAYS practice defense in
3+
# depth and use it alongside more heavyweight OS-level sandboxing using,
4+
# say, Linux containers, and in conjunction with other sandboxes like:
5+
# https://github.com/cemc/safeexec
6+
#
7+
# tested so far on Linux 2.6.32 (x86-64) and Mac OS X 10.8
8+
#
9+
# Philip Guo (philip@pgbovine.net)
10+
11+
import bdb
12+
import sys
13+
import traceback
14+
import types
15+
16+
# TODO: use the 'six' package to smooth out Py2 and Py3 differences
17+
is_python3 = (sys.version_info[0] == 3)
18+
19+
if is_python3:
20+
import io as cStringIO
21+
else:
22+
import cStringIO
23+
24+
25+
#DEBUG = False
26+
DEBUG = True
27+
28+
29+
# simple sandboxing scheme:
30+
#
31+
# - use resource.setrlimit to deprive this process of ANY file descriptors
32+
# (which will cause file read/write and subprocess shell launches to fail)
33+
# - restrict user builtins and module imports
34+
# (beware that this is NOT foolproof at all ... there are known flaws!)
35+
#
36+
# ALWAYS use defense-in-depth and don't just rely on these simple mechanisms
37+
import resource
38+
39+
40+
# ugh, I can't figure out why in Python 2, __builtins__ seems to
41+
# be a dict, but in Python 3, __builtins__ seems to be a module,
42+
# so just handle both cases ... UGLY!
43+
if type(__builtins__) is dict:
44+
BUILTIN_IMPORT = __builtins__['__import__']
45+
else:
46+
assert type(__builtins__) is types.ModuleType
47+
BUILTIN_IMPORT = __builtins__.__import__
48+
49+
50+
# whitelist of module imports
51+
ALLOWED_MODULE_IMPORTS = ('doctest',)
52+
53+
# PREEMPTIVELY import all of these modules, so that when the user's
54+
# script imports them, it won't try to do a file read (since they've
55+
# already been imported and cached in memory). Remember that when
56+
# the user's code runs, resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0))
57+
# will already be in effect, so no more files can be opened.
58+
#
59+
# NB: All modules in CUSTOM_MODULE_IMPORTS will be imported, warts and
60+
# all, so they better work on Python 2 and 3!
61+
for m in ALLOWED_MODULE_IMPORTS:
62+
__import__(m)
63+
64+
65+
# there's no point in banning builtins since malicious users can
66+
# circumvent those anyways
67+
68+
69+
class SandboxExecutor(bdb.Bdb):
70+
def __init__(self, finalizer_func):
71+
bdb.Bdb.__init__(self)
72+
self.ORIGINAL_STDOUT = sys.stdout
73+
self.ORIGINAL_STDERR = sys.stderr
74+
self.executed_script = None # Python script to be executed!
75+
76+
# a function that takes the output trace as a parameter and
77+
# processes it
78+
self.finalizer_func = finalizer_func
79+
80+
81+
def _runscript(self, script_str):
82+
self.executed_script = script_str
83+
84+
self.user_stdout = cStringIO.StringIO()
85+
self.user_stderr = cStringIO.StringIO()
86+
87+
sys.stdout = self.user_stdout
88+
sys.stderr = self.user_stderr
89+
90+
try:
91+
# enforce resource limits RIGHT BEFORE running script_str
92+
93+
# set ~200MB virtual memory limit AND a 5-second CPU time
94+
# limit (tuned for Webfaction shared hosting) to protect against
95+
# memory bombs such as:
96+
# x = 2
97+
# while True: x = x*x
98+
resource.setrlimit(resource.RLIMIT_AS, (200000000, 200000000))
99+
resource.setrlimit(resource.RLIMIT_CPU, (5, 5))
100+
101+
# protect against unauthorized filesystem accesses ...
102+
resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0)) # no opened files allowed
103+
104+
# VERY WEIRD. If you activate this resource limitation, it
105+
# ends up generating an EMPTY trace for the following program:
106+
# "x = 0\nfor i in range(10):\n x += 1\n print x\n x += 1\n"
107+
# (at least on my Webfaction hosting with Python 2.7)
108+
#resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0)) # (redundancy for paranoia)
109+
110+
# The posix module is a built-in and has a ton of OS access
111+
# facilities ... if you delete those functions from
112+
# sys.modules['posix'], it seems like they're gone EVEN IF
113+
# someone else imports posix in a roundabout way. Of course,
114+
# I don't know how foolproof this scheme is, though.
115+
# (It's not sufficient to just "del sys.modules['posix']";
116+
# it can just be reimported without accessing an external
117+
# file and tripping RLIMIT_NOFILE, since the posix module
118+
# is baked into the python executable, ergh. Actually DON'T
119+
# "del sys.modules['posix']", since re-importing it will
120+
# refresh all of the attributes. ergh^2)
121+
for a in dir(sys.modules['posix']):
122+
delattr(sys.modules['posix'], a)
123+
# do the same with os
124+
for a in dir(sys.modules['os']):
125+
if not a in ('path', 'stat', 'environ'):
126+
delattr(sys.modules['os'], a)
127+
# ppl can dig up trashed objects with gc.get_objects()
128+
import gc
129+
for a in dir(sys.modules['gc']):
130+
delattr(sys.modules['gc'], a)
131+
del sys.modules['gc']
132+
133+
# sys.modules contains an in-memory cache of already-loaded
134+
# modules, so if you delete modules from here, they will
135+
# need to be re-loaded from the filesystem.
136+
#
137+
# Thus, as an extra precaution, remove these modules so that
138+
# they can't be re-imported without opening a new file,
139+
# which is disallowed by resource.RLIMIT_NOFILE
140+
#
141+
# Of course, this isn't a foolproof solution by any means,
142+
# and it might lead to UNEXPECTED FAILURES later in execution.
143+
del sys.modules['os']
144+
del sys.modules['os.path']
145+
del sys.modules['sys']
146+
147+
# ... here we go!
148+
self.run(script_str)
149+
# sys.exit ...
150+
except SystemExit:
151+
raise bdb.BdbQuit
152+
except:
153+
if DEBUG:
154+
traceback.print_exc()
155+
raise bdb.BdbQuit # need to forceably STOP execution
156+
157+
158+
def finalize(self):
159+
sys.stdout = self.ORIGINAL_STDOUT
160+
sys.stderr = self.ORIGINAL_STDERR
161+
return self.finalizer_func(self)
162+
163+
164+
def print_finalizer(executor):
165+
#print 'DONE:'
166+
#print executor.executed_script
167+
print 'stdout:'
168+
print executor.user_stdout.getvalue()
169+
print 'stderr:'
170+
print executor.user_stderr.getvalue()
171+
172+
173+
# the MAIN meaty function!!!
174+
def exec_str(script_str, finalizer):
175+
logger = SandboxExecutor(finalizer)
176+
177+
try:
178+
logger._runscript(script_str)
179+
except bdb.BdbQuit:
180+
pass
181+
finally:
182+
return logger.finalize()
183+
184+
185+
if __name__ == "__main__":
186+
script = open(sys.argv[1]).read()
187+
exec_str(script, print_finalizer)

0 commit comments

Comments
 (0)