aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--snekbox/api/resources/eval.py28
-rw-r--r--snekbox/nsjail.py18
-rw-r--r--tests/test_integration.py32
4 files changed, 47 insertions, 35 deletions
diff --git a/README.md b/README.md
index 6e9b0e8..52cc7f1 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,9 @@ The above command will make the API accessible on the host via `http://localhost
### Python multi-version support
-By default, the binary that runs within nsjail is the binary specified by `DEFAULT_BINARY_PATH` at the top of [`nsjail.py`]. This can be overridden by specifying `binary_path` in the request body of calls to `POST /eval` or by setting the `binary_path` kwarg if calling `NSJail.python3()` directly.
+By default, the executable that runs within nsjail is defined by `DEFAULT_EXECUTABLE_PATH` at the top of [`nsjail.py`]. This can be overridden by specifying `executable_path` in the request body of calls to `POST /eval` or by setting the `executable_path` kwarg if calling `NSJail.python3()` directly.
-Any binary that exists within the container is a valid value for `binary_path`. The main use case of this feature is currently to specify the version of Python to use.
+Any executable that exists within the container is a valid value for `executable_path`. The main use case of this feature is currently to specify the version of Python to use.
Python versions currently available can be found in the [`Dockerfile`] by looking for build stages that match `builder-py-*`. These binaries are then copied into the `base` build stage further down.
diff --git a/snekbox/api/resources/eval.py b/snekbox/api/resources/eval.py
index 3172f60..b53899a 100644
--- a/snekbox/api/resources/eval.py
+++ b/snekbox/api/resources/eval.py
@@ -6,7 +6,7 @@ from pathlib import Path
import falcon
from falcon.media.validators.jsonschema import validate
-from snekbox.nsjail import DEFAULT_BINARY_PATH, NsJail
+from snekbox.nsjail import DEFAULT_EXECUTABLE_PATH, NsJail
from snekbox.snekio import FileAttachment, ParsingError
__all__ = ("EvalResource",)
@@ -44,7 +44,7 @@ class EvalResource:
"required": ["path"],
},
},
- "binary_path": {"type": "string"},
+ "executable_path": {"type": "string"},
},
"anyOf": [
{"required": ["input"]},
@@ -125,24 +125,24 @@ class EvalResource:
body.setdefault("args", ["-c"])
body["args"].append(body["input"])
- binary_path = body.get("binary_path")
- if not binary_path:
- binary_path = DEFAULT_BINARY_PATH
+ executable_path = body.get("executable_path")
+ if not executable_path:
+ executable_path = DEFAULT_EXECUTABLE_PATH
else:
- binary_path = Path(binary_path)
- if not binary_path.exists():
- raise falcon.HTTPBadRequest(title="binary_path does not exist")
- if not binary_path.is_file():
- raise falcon.HTTPBadRequest(title="binary_path is not a file")
- if not binary_path.stat().st_mode & 0o100 == 0o100:
- raise falcon.HTTPBadRequest(title="binary_path is not executable")
- binary_path = binary_path.resolve().as_posix()
+ executable_path = Path(executable_path)
+ if not executable_path.exists():
+ raise falcon.HTTPBadRequest(title="executable_path does not exist")
+ if not executable_path.is_file():
+ raise falcon.HTTPBadRequest(title="executable_path is not a file")
+ if not executable_path.stat().st_mode & 0o100 == 0o100:
+ raise falcon.HTTPBadRequest(title="executable_path is not executable")
+ executable_path = executable_path.resolve().as_posix()
try:
result = self.nsjail.python3(
py_args=body["args"],
files=[FileAttachment.from_dict(file) for file in body.get("files", [])],
- binary_path=binary_path,
+ executable_path=executable_path,
)
except ParsingError as e:
raise falcon.HTTPBadRequest(title="Request file is invalid", description=str(e))
diff --git a/snekbox/nsjail.py b/snekbox/nsjail.py
index fe95f80..adbf69e 100644
--- a/snekbox/nsjail.py
+++ b/snekbox/nsjail.py
@@ -26,7 +26,7 @@ log = logging.getLogger(__name__)
LOG_PATTERN = re.compile(
r"\[(?P<level>(I)|[DWEF])\]\[.+?\](?(2)|(?P<func>\[\d+\] .+?:\d+ )) ?(?P<msg>.+)"
)
-DEFAULT_BINARY_PATH = "/snekbin/python/default/bin/python"
+DEFAULT_EXECUTABLE_PATH = "/snekbin/python/default/bin/python"
class NsJail:
@@ -174,7 +174,7 @@ class NsJail:
nsjail_args: Iterable[str],
log_path: str,
fs_home: str,
- binary_path: str,
+ executable_path: str,
) -> Sequence[str]:
if self.cgroup_version == 2:
nsjail_args = ("--use_cgroupv2", *nsjail_args)
@@ -203,7 +203,7 @@ class NsJail:
log_path,
*nsjail_args,
"--",
- binary_path,
+ executable_path,
*iter_lstrip(py_args),
]
@@ -262,7 +262,7 @@ class NsJail:
py_args: Iterable[str],
files: Iterable[FileAttachment] = (),
nsjail_args: Iterable[str] = (),
- binary_path: Path = DEFAULT_BINARY_PATH,
+ executable_path: Path = DEFAULT_EXECUTABLE_PATH,
) -> EvalResult:
"""
Execute Python 3 code in an isolated environment and return the completed process.
@@ -271,14 +271,20 @@ class NsJail:
py_args: Arguments to pass to Python.
files: FileAttachments to write to the sandbox prior to running Python.
nsjail_args: Overrides for the NsJail configuration.
- binary_path: The path to the binary to execute under.
+ executable_path: The path to the executable to run within nsjail.
"""
with NamedTemporaryFile() as nsj_log, MemFS(
instance_size=self.memfs_instance_size,
home=self.memfs_home,
output=self.memfs_output,
) as fs:
- args = self._build_args(py_args, nsjail_args, nsj_log.name, str(fs.home), binary_path)
+ args = self._build_args(
+ py_args,
+ nsjail_args,
+ nsj_log.name,
+ str(fs.home),
+ executable_path,
+ )
try:
files_written = self._write_files(fs.home, files)
diff --git a/tests/test_integration.py b/tests/test_integration.py
index e173dd3..0d8f700 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -52,8 +52,8 @@ class IntegrationTests(unittest.TestCase):
self.assertTrue(all(status == 200 for status in statuses))
self.assertTrue(all(json.loads(response)["returncode"] == 0 for response in responses))
- def test_multi_binary_support(self):
- """Test eval requests with different binary paths set."""
+ def test_alternate_executable_support(self):
+ """Test eval requests with different executable paths set."""
with run_gunicorn():
get_python_version_body = {
"input": "import sys; print('.'.join(map(str, sys.version_info[:2])))"
@@ -62,17 +62,19 @@ class IntegrationTests(unittest.TestCase):
(
get_python_version_body,
"3.12\n",
- "test default binary is used when binary_path not specified",
+ "test default executable is used when executable_path not specified",
),
(
- get_python_version_body | {"binary_path": "/snekbin/python/3.12/bin/python"},
+ get_python_version_body
+ | {"executable_path": "/snekbin/python/3.12/bin/python"},
"3.12\n",
- "test default binary is used when explicitly set",
+ "test default executable is used when explicitly set",
),
(
- get_python_version_body | {"binary_path": "/snekbin/python/3.13/bin/python"},
+ get_python_version_body
+ | {"executable_path": "/snekbin/python/3.13/bin/python"},
"3.13\n",
- "test alternative binary is used when set",
+ "test alternative executable is used when set",
),
]
for body, expected, msg in cases:
@@ -81,21 +83,25 @@ class IntegrationTests(unittest.TestCase):
self.assertEqual(status, 200)
self.assertEqual(json.loads(response)["stdout"], expected)
- def invalid_binary_paths(self):
- """Test that passing invalid binary paths result in no code execution."""
+ def invalid_executable_paths(self):
+ """Test that passing invalid executable paths result in no code execution."""
with run_gunicorn():
cases = [
- ("/abc/def", "test non-existent files are not run", "binary_path does not exist"),
- ("/snekbin", "test directories are not run", "binary_path is not a file"),
+ (
+ "/abc/def",
+ "test non-existent files are not run",
+ "executable_path does not exist",
+ ),
+ ("/snekbin", "test directories are not run", "executable_path is not a file"),
(
"/etc/hostname",
"test non-executable files are not run",
- "binary_path is not executable",
+ "executable_path is not executable",
),
]
for path, msg, expected in cases:
with self.subTest(msg=msg, path=path, expected=expected):
- body = {"args": ["-c", "echo", "hi"], "binary_path": path}
+ body = {"args": ["-c", "echo", "hi"], "executable_path": path}
response, status = snekbox_request(body)
self.assertEqual(status, 400)
self.assertEqual(json.loads(response)["stdout"], expected)