aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar ionite34 <[email protected]>2022-11-22 14:13:35 -0500
committerGravatar ionite34 <[email protected]>2022-11-22 14:13:35 -0500
commita5e18d14bb0133bf5a189f7f99adc777a56c9689 (patch)
tree6bbc1e21ab61a1c62f34b59c17b2c015f7b298d2
parentMove parse_files to memfs (diff)
Add safe_path to handle path checks
-rw-r--r--snekbox/api/resources/eval.py8
-rw-r--r--snekbox/nsjail.py2
-rw-r--r--snekbox/process.py4
-rw-r--r--snekbox/snekio.py36
4 files changed, 32 insertions, 18 deletions
diff --git a/snekbox/api/resources/eval.py b/snekbox/api/resources/eval.py
index d6549ab..14fa295 100644
--- a/snekbox/api/resources/eval.py
+++ b/snekbox/api/resources/eval.py
@@ -9,7 +9,7 @@ from snekbox.nsjail import NsJail
__all__ = ("EvalResource",)
-from snekbox.snekio import FileAttachment, ParsingError
+from snekbox.snekio import FileAttachment, IllegalPathError
log = logging.getLogger(__name__)
@@ -106,8 +106,10 @@ class EvalResource:
py_args=req.media["args"],
files=[FileAttachment.from_dict(file) for file in req.media.get("files", [])],
)
- except ParsingError as e:
- raise falcon.HTTPBadRequest(description=f"Invalid file in request: {e}")
+ except IllegalPathError as e:
+ raise falcon.HTTPBadRequest(
+ title="Request file path failed validation", description=str(e)
+ )
except Exception:
log.exception("An exception occurred while trying to process the request")
raise falcon.HTTPInternalServerError
diff --git a/snekbox/nsjail.py b/snekbox/nsjail.py
index ee15f71..30198e5 100644
--- a/snekbox/nsjail.py
+++ b/snekbox/nsjail.py
@@ -260,4 +260,4 @@ class NsJail:
log.info(f"nsjail return code: {returncode}")
- return EvalResult(args, returncode, output, attachments=attachments)
+ return EvalResult(args, returncode, output, files=attachments)
diff --git a/snekbox/process.py b/snekbox/process.py
index 6740909..552b91a 100644
--- a/snekbox/process.py
+++ b/snekbox/process.py
@@ -25,8 +25,8 @@ class EvalResult(CompletedProcess[_T]):
returncode: int | None,
stdout: _T | None = None,
stderr: _T | None = None,
- attachments: list[FileAttachment] | None = None,
+ files: list[FileAttachment] | None = None,
) -> None:
"""Create an evaluation result."""
super().__init__(args, returncode, stdout, stderr)
- self.files: list[FileAttachment] = attachments or []
+ self.files: list[FileAttachment] = files or []
diff --git a/snekbox/snekio.py b/snekbox/snekio.py
index 9f2c3ca..bc71501 100644
--- a/snekbox/snekio.py
+++ b/snekbox/snekio.py
@@ -9,6 +9,27 @@ from typing import Generic, TypeVar
T = TypeVar("T", str, bytes)
+def safe_path(path: str) -> str:
+ """
+ Returns the `path` str if there are no security issues.
+
+ Raises:
+ IllegalPathError: Raised on any path rule violation.
+ """
+ # Disallow absolute paths
+ if Path(path).is_absolute():
+ raise IllegalPathError(f"File path '{path}' must be relative")
+
+ # Disallow traversal beyond root
+ try:
+ test_root = Path("/home")
+ Path(test_root).joinpath(path).resolve().relative_to(test_root.resolve())
+ except ValueError:
+ raise IllegalPathError(f"File path '{path}' may not traverse beyond root")
+
+ return path
+
+
class AttachmentError(ValueError):
"""Raised when an attachment is invalid."""
@@ -17,7 +38,7 @@ class ParsingError(AttachmentError):
"""Raised when an incoming file cannot be parsed."""
-class IllegalPathError(AttachmentError):
+class IllegalPathError(ParsingError):
"""Raised when an attachment has an illegal path."""
@@ -31,18 +52,9 @@ class FileAttachment(Generic[T]):
@classmethod
def from_dict(cls, data: dict[str, str]) -> FileAttachment[bytes]:
"""Convert a dict to an attachment."""
- name = data["name"]
- path = Path(name)
- parts = path.parts
-
- if path.is_absolute() or set(parts[0]) & {"\\", "/"}:
- raise IllegalPathError(f"File path '{name}' must be relative")
-
- if any(set(part) == {"."} for part in parts):
- raise IllegalPathError(f"File path '{name}' may not use traversal ('..')")
-
+ path = safe_path(data["path"])
content = b64decode(data.get("content", ""))
- return cls(name, content)
+ return cls(path, content)
@classmethod
def from_path(cls, file: Path) -> FileAttachment[bytes]: