Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
JasonGrace2282 committed Nov 10, 2024

Verified

This commit was signed with the committer’s verified signature.
Tschakki Mona Bärenfänger
1 parent bd017e0 commit dbb0226
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
@@ -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``.
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -178,6 +178,10 @@ extend-ignore-names = [
"INP001",
]

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

[tool.ruff.format]
docstring-code-format = true
line-ending = "lf"
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
@@ -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
@@ -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:
@@ -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"
@@ -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)
@@ -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())

@@ -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
@@ -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__)))
@@ -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",
@@ -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

0 comments on commit dbb0226

Please sign in to comment.