Skip to content

Commit

Permalink
refactor(run_submission): use a custom wrapper
Browse files Browse the repository at this point in the history
This wrapper will only take effect if DEBUG
is False and the sandboxing submodule isn't
cloned.
  • Loading branch information
JasonGrace2282 committed Oct 12, 2024
1 parent 212b674 commit 8fa21a4
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 106 deletions.
5 changes: 0 additions & 5 deletions docs/source/contributing/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ Submissions
In order to actually create a submission, there are some more steps. First,
you'll need to install `redis <https://redis.io/download>`_.

You'll also need to run some scripts to emulate the sandboxing process that goes on in production.
Run the following script::

pipenv run python3 scripts/create_wrappers.py

After that, you'll want to start up the development server and create a course,
and an assignment in the course. After saving the assignment, you can hit "Upload grader"
to add a grader - the simplest example of a grader is located in ``scripts/sample_grader.py``.
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ extend-ignore-names = [
"INP001",
]

"**/management/*" = [
"INP001",
]

[tool.ruff.format]
docstring-code-format = true
line-ending = "lf"
Expand Down
66 changes: 0 additions & 66 deletions scripts/create_wrappers.py

This file was deleted.

55 changes: 55 additions & 0 deletions scripts/grader_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""A sample wrapper script for running python submissions.
The text in this file is read in :func:`run_submission` and
executed if ``settings.USE_SANDBOXING`` is not ``True``.
"""

from __future__ import annotations

import argparse
import subprocess
import sys
from pathlib import Path


def parse_args() -> list[str]:
parser = argparse.ArgumentParser()
parser.add_argument("--write", action="append")
parser.add_argument("--read", action="append")
# since we're not being sandboxed, we don't need to do anything
# with the grader arguments
_grader_args, submission_args = parser.parse_known_args()

if submission_args and submission_args[0] == "--":
return submission_args[1:]
return submission_args


def find_python() -> str:
venv = Path("{venv_path}")
if venv.name == "None":
return "{python}"
if (python := venv / "bin" / "python").exists():
return str(python)
return str(venv / "bin" / "python3")


def main() -> int:
args = parse_args()
submission_path = Path("{submission_path}")

if submission_path.suffix != ".py":
raise NotImplementedError("Only python submissions are supported in DEBUG.")

python = find_python()
output = subprocess.run(
[python, "--", str(submission_path), *args],
stdout=sys.stdout,
stderr=sys.stderr,
check=False,
)
return output.returncode


if __name__ == "__main__":
sys.exit(main())
72 changes: 37 additions & 35 deletions tin/apps/submissions/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import os
import re
import select
import shutil
import signal
import subprocess
import sys
import time
import traceback
from decimal import Decimal
from pathlib import Path

import psutil
from asgiref.sync import async_to_sync
Expand Down Expand Up @@ -42,12 +42,12 @@ def run_submission(submission_id):
)
submission_path = submission.file_path

submission_wrapper_path = submission.wrapper_file_path
submission_wrapper_path = Path(submission.wrapper_file_path)

args = get_assignment_sandbox_args(
["mkdir", "-p", "--", os.path.dirname(submission_wrapper_path)],
["mkdir", "-p", "--", str(submission_wrapper_path.parent)],
network_access=False,
whitelist=[os.path.dirname(os.path.dirname(submission_wrapper_path))],
whitelist=[str(submission_wrapper_path.parent.parent)],
)

try:
Expand All @@ -70,35 +70,38 @@ def run_submission(submission_id):
else: # pragma: no cover
python_exe = "/usr/bin/python3.10"

if not settings.DEBUG or shutil.which("bwrap") is not None:
folder_name = "sandboxed"
else:
folder_name = "testing"

with open(
os.path.join(
settings.BASE_DIR,
"sandboxing",
"wrappers",
folder_name,
f"{submission.assignment.language}.txt",
if settings.USE_SANDBOXING:
wrapper_text = (
Path(settings.BASE_DIR)
.joinpath(
"sandboxing",
"wrappers",
"sandboxed",
f"{submission.assignment.language}.txt",
)
.read_text("utf-8")
)
) as wrapper_file:
wrapper_text = wrapper_file.read().format(
has_network_access=bool(submission.assignment.has_network_access),
venv_path=(
submission.assignment.venv.path
if submission.assignment.venv_fully_created
else None
),
submission_path=submission_path,
python=python_exe,

elif submission.assignment.language == "P":
wrapper_text = settings.DEBUG_GRADER_WRAPPER_SCRIPT.read_text("utf-8")
else:
raise NotImplementedError(
f"Unsupported language {submission.assignment.language} in DEBUG"
)

with open(submission_wrapper_path, "w", encoding="utf-8") as f_obj:
f_obj.write(wrapper_text)
wrapper_text = wrapper_text.format(
has_network_access=bool(submission.assignment.has_network_access),
venv_path=(
submission.assignment.venv.path
if submission.assignment.venv_fully_created
else None
),
submission_path=submission_path,
python=python_exe,
)

os.chmod(submission_wrapper_path, 0o700)
submission_wrapper_path.write_text(wrapper_text, "utf-8")
submission_wrapper_path.chmod(0o700)
except OSError:
submission.grader_output = (
"An internal error occurred. Please try again.\n"
Expand Down Expand Up @@ -126,15 +129,15 @@ def run_submission(submission_id):
python_exe,
"-u",
grader_path,
submission_wrapper_path,
str(submission_wrapper_path),
submission_path,
submission.student.username,
grader_log_path,
]

if not settings.DEBUG or shutil.which("firejail") is not None:
if settings.USE_SANDBOXING:
whitelist = [os.path.dirname(grader_path)]
read_only = [grader_path, submission_path, os.path.dirname(submission_wrapper_path)]
read_only = [grader_path, submission_path, str(submission_wrapper_path.parent)]
if submission.assignment.venv_fully_created:
whitelist.append(submission.assignment.venv.path)
read_only.append(submission.assignment.venv.path)
Expand All @@ -147,7 +150,7 @@ def run_submission(submission_id):
read_only=read_only,
)

env = dict(os.environ)
env = os.environ.copy()
if submission.assignment.venv_fully_created:
env.update(submission.assignment.venv.get_activation_env())

Expand Down Expand Up @@ -275,5 +278,4 @@ def run_submission(submission_id):
submission.channel_group_name, {"type": "submission.updated"}
)

if os.path.exists(submission_wrapper_path):
os.remove(submission_wrapper_path)
submission_wrapper_path.unlink(missing_ok=True)
10 changes: 10 additions & 0 deletions tin/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from __future__ import annotations

import os
from pathlib import Path

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand All @@ -28,6 +29,11 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

USE_SANDBOXING = (
not DEBUG or Path(BASE_DIR).joinpath("sandboxing", "wrappers", "sandboxed", "P.txt").exists()
)


ALLOWED_HOSTS = [
"127.0.0.1",
"localhost",
Expand Down Expand Up @@ -305,6 +311,10 @@

VENV_FILE_SIZE_LIMIT = 1 * 1000 * 1000 * 1000 # 1 GB

# The wrapper script to use when running submissions outside of production
# We still need this so that it can handle cli arguments to the wrapper script
DEBUG_GRADER_WRAPPER_SCRIPT = Path(BASE_DIR).parent / "scripts" / "grader_wrapper.py"

# Spaces and special characters may not be handled correctly
# Not importing correctly - specified directly in apps/submissions/tasks.py
# as of 8/3/2022, 2022ldelwich
Expand Down

0 comments on commit 8fa21a4

Please sign in to comment.