Running pupil code¶
Trace and Bugs run the program you give them. Fade does not run anything: it blanks lines out of the solution as text.
A run happens in a separate Python process, started by the app with these limits. They are there to stop an accident (a loop that never ends, a print flood) from freezing your Mac. They are not a security boundary: the file is one you chose, written by you or copied from a pupil, and you would run it in Thonny anyway.
What a run gets¶
- A time limit. Trace allows 10 seconds, Bugs 4 seconds per variant. When it is up the process is killed. Trace writes nothing and says The program did not finish within 10s (infinite loop?); in Bugs that version's key and card say hangs (infinite loop). A CPU limit of the same size backs it up.
- An output cap of 1 MB (1024 KB) each for what the program prints and
for its error messages. Anything past the cap is read and thrown away, so
the app's memory stays flat however much the program prints. Capped
output ends with the line
[output truncated at 1024 KB]: in Trace, the key's program output ends with it; in Bugs, that version's What happens adds output capped at 1024 KB. Trace also caps what it collects inside the run at 1 MB, so awhile True: print(i)traced for 10 seconds stays small. - A fresh, temporary working folder that is deleted afterwards, a
minimal environment, and
python -I(no site packages, noPYTHON*variables from your shell). Inputs you type in the Inputs box are fed toinput(), one line each. When they run out, Trace stops and writes nothing: the result strip says which input was missing (asked for a 2nd input, but was given only 1 line), the result pane says how many lines to type, and the Inputs box is outlined (Trace). In Bugs,input()raisesEOFErrorand that version crashes. - A refusal to run files that import networking or process modules:
socket,ssl,http,urllib,requests,subprocess,multiprocessingand friends. The check reads the source before running it and also recognises__import__("socket")andimportlib.import_module("socket")when the module name is written out as a string.
What a run does not stop¶
os.system("…"),os.popenor anything else inosthat reaches the shell. The import check is a list of module names, not a sandbox.- Writing, moving or deleting files anywhere your account can. The working folder is temporary; the rest of the disk is not hidden.
- An import whose name is built at run time, such as
__import__("soc" + "ket"). The check is best effort by design. - Memory. A program that builds a huge list can still use a lot of it until the time limit kills the process.
If a file came from somewhere you do not trust, read it before you run it, exactly as you would before running it on the board.
Why not a real sandbox¶
Containers, seccomp or network namespaces would cost a great deal, still would not be a boundary on macOS, and would protect against the wrong thing: these are your own files. So Staffroom caps the output and writes the limits down instead.