diff options
| author | 2022-11-22 14:13:35 -0500 | |
|---|---|---|
| committer | 2022-11-22 14:13:35 -0500 | |
| commit | a5e18d14bb0133bf5a189f7f99adc777a56c9689 (patch) | |
| tree | 6bbc1e21ab61a1c62f34b59c17b2c015f7b298d2 | |
| parent | Move parse_files to memfs (diff) | |
Add safe_path to handle path checks
| -rw-r--r-- | snekbox/api/resources/eval.py | 8 | ||||
| -rw-r--r-- | snekbox/nsjail.py | 2 | ||||
| -rw-r--r-- | snekbox/process.py | 4 | ||||
| -rw-r--r-- | snekbox/snekio.py | 36 | 
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]: | 
