示例#1
0
def main() -> None:
    """Evaluate Python code through NsJail."""
    args = parse_args()
    result = NsJail().python3(args.code, *args.nsjail_args)
    print(result.stdout)
class NsJailTests(unittest.TestCase):
    def setUp(self):
        super().setUp()

        self.nsjail = NsJail()
        self.nsjail.DEBUG = False
        self.logger = logging.getLogger("snekbox.nsjail")

    def test_print_returns_0(self):
        result = self.nsjail.python3("print('test')")
        self.assertEqual(result.returncode, 0)
        self.assertEqual(result.stdout, "test\n")
        self.assertEqual(result.stderr, None)

    def test_timeout_returns_137(self):
        code = dedent("""
            while True:
                pass
        """).strip()

        with self.assertLogs(self.logger) as log:
            result = self.nsjail.python3(code)

        self.assertEqual(result.returncode, 137)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)
        self.assertIn("run time >= time limit", "\n".join(log.output))

    def test_memory_returns_137(self):
        # Add a kilobyte just to be safe.
        code = dedent(f"""
            x = ' ' * {MEM_MAX + 1000}
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 137)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_subprocess_resource_unavailable(self):
        code = dedent("""
            import subprocess
            print(subprocess.check_output('kill -9 6', shell=True).decode())
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Resource temporarily unavailable", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_read_only_file_system(self):
        for path in ("/", "/etc", "/lib", "/lib64", "/snekbox", "/usr"):
            with self.subTest(path=path):
                code = dedent(f"""
                    with open('{path}/hello', 'w') as f:
                        f.write('world')
                """).strip()

                result = self.nsjail.python3(code)
                self.assertEqual(result.returncode, 1)
                self.assertIn("Read-only file system", result.stdout)
                self.assertEqual(result.stderr, None)

    def test_forkbomb_resource_unavailable(self):
        code = dedent("""
            import os
            while 1:
                os.fork()
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Resource temporarily unavailable", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_sigsegv_returns_139(self):  # In honour of Juan.
        code = dedent("""
            import ctypes
            ctypes.string_at(0)
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 139)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_null_byte_value_error(self):
        result = self.nsjail.python3("\0")
        self.assertEqual(result.returncode, None)
        self.assertEqual(result.stdout, "ValueError: embedded null byte")
        self.assertEqual(result.stderr, None)

    def test_log_parser(self):
        log_lines = (
            "[D][2019-06-22T20:07:00+0000][16] void foo::bar()():100 This is a debug message.",
            "[I][2019-06-22T20:07:48+0000] pid=20 ([STANDALONE MODE]) "
            "exited with status: 2, (PIDs left: 0)",
            "[W][2019-06-22T20:06:04+0000][14] void cmdline::logParams(nsjconf_t*)():250 "
            "Process will be UID/EUID=0 in the global user namespace, and will have user "
            "root-level access to files",
            "[W][2019-06-22T20:07:00+0000][16] void foo::bar()():100 This is a warning!",
            "[E][2019-06-22T20:07:00+0000][16] bool "
            "cmdline::setupArgv(nsjconf_t*, int, char**, int)():316 No command-line provided",
            "[F][2019-06-22T20:07:00+0000][16] int main(int, char**)():204 "
            "Couldn't parse cmdline options", "Invalid Line")

        with self.assertLogs(self.logger, logging.DEBUG) as log:
            self.nsjail._parse_log(log_lines)

        self.assertIn("DEBUG:snekbox.nsjail:This is a debug message.",
                      log.output)
        self.assertIn("ERROR:snekbox.nsjail:Couldn't parse cmdline options",
                      log.output)
        self.assertIn("ERROR:snekbox.nsjail:No command-line provided",
                      log.output)
        self.assertIn(
            "WARNING:snekbox.nsjail:Failed to parse log line 'Invalid Line'",
            log.output)
        self.assertIn("WARNING:snekbox.nsjail:This is a warning!", log.output)
        self.assertIn(
            "INFO:snekbox.nsjail:pid=20 ([STANDALONE MODE]) exited with status: 2, (PIDs left: 0)",
            log.output)

    def test_shm_and_tmp_not_mounted(self):
        for path in ("/dev/shm", "/run/shm", "/tmp"):
            with self.subTest(path=path):
                code = dedent(f"""
                    with open('{path}/test', 'wb') as file:
                        file.write(bytes([255]))
                """).strip()

                result = self.nsjail.python3(code)
                self.assertEqual(result.returncode, 1)
                self.assertIn("No such file or directory", result.stdout)
                self.assertEqual(result.stderr, None)

    def test_multiprocessing_shared_memory_disabled(self):
        code = dedent("""
            from multiprocessing.shared_memory import SharedMemory
            try:
                SharedMemory('test', create=True, size=16)
            except FileExistsError:
                pass
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Function not implemented", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_numpy_import(self):
        result = self.nsjail.python3("import numpy")
        self.assertEqual(result.returncode, 0)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_output_order(self):
        stdout_msg = "greetings from stdout!"
        stderr_msg = "hello from stderr!"
        code = dedent(f"""
            print({stdout_msg!r})
            raise ValueError({stderr_msg!r})
        """).strip()

        result = self.nsjail.python3(code)
        self.assertLess(result.stdout.find(stdout_msg),
                        result.stdout.find(stderr_msg),
                        msg="stdout does not come before stderr")
        self.assertEqual(result.stderr, None)
    def setUp(self):
        super().setUp()

        self.nsjail = NsJail()
        self.nsjail.DEBUG = False
        self.logger = logging.getLogger("snekbox.nsjail")
示例#4
0
class NsJailTests(unittest.TestCase):
    def setUp(self):
        super().setUp()

        self.nsjail = NsJail()
        self.nsjail.DEBUG = False
        self.logger = logging.getLogger("snekbox.nsjail")

    def test_print_returns_0(self):
        result = self.nsjail.python3("print('test')")
        self.assertEqual(result.returncode, 0)
        self.assertEqual(result.stdout, "test\n")
        self.assertEqual(result.stderr, None)

    def test_timeout_returns_137(self):
        code = dedent("""
            while True:
                pass
        """).strip()

        with self.assertLogs(self.logger) as log:
            result = self.nsjail.python3(code)

        self.assertEqual(result.returncode, 137)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)
        self.assertIn("run time >= time limit", "\n".join(log.output))

    def test_memory_returns_137(self):
        # Add a kilobyte just to be safe.
        code = dedent(f"""
            x = ' ' * {MEM_MAX + 1000}
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 137)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_subprocess_resource_unavailable(self):
        code = dedent("""
            import subprocess
            print(subprocess.check_output('kill -9 6', shell=True).decode())
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Resource temporarily unavailable", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_read_only_file_system(self):
        code = dedent("""
            open('hello', 'w').write('world')
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Read-only file system", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_forkbomb_resource_unavailable(self):
        code = dedent("""
            import os
            while 1:
                os.fork()
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Resource temporarily unavailable", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_sigsegv_returns_139(self):  # In honour of Juan.
        code = dedent("""
            import ctypes
            ctypes.string_at(0)
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 139)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_null_byte_value_error(self):
        result = self.nsjail.python3("\0")
        self.assertEqual(result.returncode, None)
        self.assertEqual(result.stdout, "ValueError: embedded null byte")
        self.assertEqual(result.stderr, None)

    def test_log_parser(self):
        log_lines = (
            "[D][2019-06-22T20:07:00+0000][16] void foo::bar()():100 This is a debug message.",
            "[I][2019-06-22T20:07:48+0000] pid=20 ([STANDALONE MODE]) "
            "exited with status: 2, (PIDs left: 0)",
            "[W][2019-06-22T20:06:04+0000][14] void cmdline::logParams(nsjconf_t*)():250 "
            "Process will be UID/EUID=0 in the global user namespace, and will have user "
            "root-level access to files",
            "[W][2019-06-22T20:07:00+0000][16] void foo::bar()():100 This is a warning!",
            "[E][2019-06-22T20:07:00+0000][16] bool "
            "cmdline::setupArgv(nsjconf_t*, int, char**, int)():316 No command-line provided",
            "[F][2019-06-22T20:07:00+0000][16] int main(int, char**)():204 "
            "Couldn't parse cmdline options", "Invalid Line")

        with self.assertLogs(self.logger, logging.DEBUG) as log:
            self.nsjail._parse_log(log_lines)

        self.assertIn("DEBUG:snekbox.nsjail:This is a debug message.",
                      log.output)
        self.assertIn("ERROR:snekbox.nsjail:Couldn't parse cmdline options",
                      log.output)
        self.assertIn("ERROR:snekbox.nsjail:No command-line provided",
                      log.output)
        self.assertIn(
            "WARNING:snekbox.nsjail:Failed to parse log line 'Invalid Line'",
            log.output)
        self.assertIn("WARNING:snekbox.nsjail:This is a warning!", log.output)
        self.assertIn(
            "INFO:snekbox.nsjail:pid=20 ([STANDALONE MODE]) exited with status: 2, (PIDs left: 0)",
            log.output)
示例#5
0
    def setUp(self):
        super().setUp()

        self.nsjail = NsJail()
        self.logger = logging.getLogger("snekbox.nsjail")
        self.logger.setLevel(logging.WARNING)
示例#6
0
class NsJailTests(unittest.TestCase):
    def setUp(self):
        super().setUp()

        self.nsjail = NsJail()
        self.logger = logging.getLogger("snekbox.nsjail")
        self.logger.setLevel(logging.WARNING)

    def test_print_returns_0(self):
        result = self.nsjail.python3("print('test')")
        self.assertEqual(result.returncode, 0)
        self.assertEqual(result.stdout, "test\n")
        self.assertEqual(result.stderr, None)

    def test_timeout_returns_137(self):
        code = dedent("""
            while True:
                pass
        """).strip()

        with self.assertLogs(self.logger) as log:
            result = self.nsjail.python3(code)

        self.assertEqual(result.returncode, 137)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)
        self.assertIn("run time >= time limit", "\n".join(log.output))

    def test_memory_returns_137(self):
        # Add a kilobyte just to be safe.
        code = dedent(f"""
            x = ' ' * {self.nsjail.config.cgroup_mem_max + 1000}
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 137)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_subprocess_resource_unavailable(self):
        code = dedent("""
            import subprocess
            print(subprocess.check_output('kill -9 6', shell=True).decode())
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Resource temporarily unavailable", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_read_only_file_system(self):
        for path in ("/", "/etc", "/lib", "/lib64", "/snekbox", "/usr"):
            with self.subTest(path=path):
                code = dedent(f"""
                    with open('{path}/hello', 'w') as f:
                        f.write('world')
                """).strip()

                result = self.nsjail.python3(code)
                self.assertEqual(result.returncode, 1)
                self.assertIn("Read-only file system", result.stdout)
                self.assertEqual(result.stderr, None)

    def test_forkbomb_resource_unavailable(self):
        code = dedent("""
            import os
            while 1:
                os.fork()
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Resource temporarily unavailable", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_sigsegv_returns_139(self):  # In honour of Juan.
        code = dedent("""
            import ctypes
            ctypes.string_at(0)
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 139)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_null_byte_value_error(self):
        result = self.nsjail.python3("\0")
        self.assertEqual(result.returncode, None)
        self.assertEqual(result.stdout, "ValueError: embedded null byte")
        self.assertEqual(result.stderr, None)

    def test_print_bad_unicode_encode_error(self):
        result = self.nsjail.python3("print(chr(56550))")
        self.assertEqual(result.returncode, 1)
        self.assertIn("UnicodeEncodeError", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_unicode_env_erase_escape_fails(self):
        result = self.nsjail.python3(dedent("""
            import os
            import sys
            os.unsetenv('PYTHONIOENCODING')
            os.execl(sys.executable, 'python', '-c', 'print(chr(56550))')
        """).strip())
        self.assertEqual(result.returncode, None)
        self.assertEqual(result.stdout, "UnicodeDecodeError: invalid Unicode in output pipe")
        self.assertEqual(result.stderr, None)

    @unittest.mock.patch("snekbox.nsjail.DEBUG", new=False)
    def test_log_parser(self):
        log_lines = (
            "[D][2019-06-22T20:07:00+0000][16] void foo::bar()():100 This is a debug message.",
            "[I][2019-06-22T20:07:48+0000] pid=20 ([STANDALONE MODE]) "
            "exited with status: 2, (PIDs left: 0)",
            "[W][2019-06-22T20:06:04+0000][14] void cmdline::logParams(nsjconf_t*)():250 "
            "Process will be UID/EUID=0 in the global user namespace, and will have user "
            "root-level access to files",
            "[W][2019-06-22T20:07:00+0000][16] void foo::bar()():100 This is a warning!",
            "[E][2019-06-22T20:07:00+0000][16] bool "
            "cmdline::setupArgv(nsjconf_t*, int, char**, int)():316 No command-line provided",
            "[F][2019-06-22T20:07:00+0000][16] int main(int, char**)():204 "
            "Couldn't parse cmdline options",
            "Invalid Line"
        )

        with self.assertLogs(self.logger, logging.DEBUG) as log:
            self.nsjail._parse_log(log_lines)

        self.assertIn("DEBUG:snekbox.nsjail:This is a debug message.", log.output)
        self.assertIn("ERROR:snekbox.nsjail:Couldn't parse cmdline options", log.output)
        self.assertIn("ERROR:snekbox.nsjail:No command-line provided", log.output)
        self.assertIn("WARNING:snekbox.nsjail:Failed to parse log line 'Invalid Line'", log.output)
        self.assertIn("WARNING:snekbox.nsjail:This is a warning!", log.output)
        self.assertIn(
            "INFO:snekbox.nsjail:pid=20 ([STANDALONE MODE]) exited with status: 2, (PIDs left: 0)",
            log.output
        )

    def test_shm_and_tmp_not_mounted(self):
        for path in ("/dev/shm", "/run/shm", "/tmp"):
            with self.subTest(path=path):
                code = dedent(f"""
                    with open('{path}/test', 'wb') as file:
                        file.write(bytes([255]))
                """).strip()

                result = self.nsjail.python3(code)
                self.assertEqual(result.returncode, 1)
                self.assertIn("No such file or directory", result.stdout)
                self.assertEqual(result.stderr, None)

    def test_multiprocessing_shared_memory_disabled(self):
        code = dedent("""
            from multiprocessing.shared_memory import SharedMemory
            try:
                SharedMemory('test', create=True, size=16)
            except FileExistsError:
                pass
        """).strip()

        result = self.nsjail.python3(code)
        self.assertEqual(result.returncode, 1)
        self.assertIn("Function not implemented", result.stdout)
        self.assertEqual(result.stderr, None)

    def test_numpy_import(self):
        result = self.nsjail.python3("import numpy")
        self.assertEqual(result.returncode, 0)
        self.assertEqual(result.stdout, "")
        self.assertEqual(result.stderr, None)

    def test_output_order(self):
        stdout_msg = "greetings from stdout!"
        stderr_msg = "hello from stderr!"
        code = dedent(f"""
            print({stdout_msg!r})
            raise ValueError({stderr_msg!r})
        """).strip()

        result = self.nsjail.python3(code)
        self.assertLess(
            result.stdout.find(stdout_msg),
            result.stdout.find(stderr_msg),
            msg="stdout does not come before stderr"
        )
        self.assertEqual(result.stderr, None)

    def test_stdout_flood_results_in_graceful_sigterm(self):
        stdout_flood = dedent("""
            while True:
                print('abcdefghij')
        """).strip()

        result = self.nsjail.python3(stdout_flood)
        self.assertEqual(result.returncode, 143)

    def test_large_output_is_truncated(self):
        chunk = "a" * READ_CHUNK_SIZE
        expected_chunks = OUTPUT_MAX // sys.getsizeof(chunk) + 1

        nsjail_subprocess = unittest.mock.MagicMock()

        # Go 10 chunks over to make sure we exceed the limit
        nsjail_subprocess.stdout = io.StringIO((expected_chunks + 10) * chunk)
        nsjail_subprocess.poll.return_value = None

        output = self.nsjail._consume_stdout(nsjail_subprocess)
        self.assertEqual(output, chunk * expected_chunks)
示例#7
0
文件: eval.py 项目: wookie184/snekbox
 def __init__(self):
     self.nsjail = NsJail()
示例#8
0
文件: eval.py 项目: wookie184/snekbox
class EvalResource:
    """
    Evaluation of Python code.

    Supported methods:

    - POST /eval
        Evaluate Python code and return the result
    """

    REQ_SCHEMA = {
        "type": "object",
        "properties": {
            "input": {
                "type": "string"
            }
        },
        "required": ["input"]
    }

    def __init__(self):
        self.nsjail = NsJail()

    @validate(REQ_SCHEMA)
    def on_post(self, req: falcon.Request, resp: falcon.Response) -> None:
        """
        Evaluate Python code and return stdout, stderr, and the return code.

        The return codes mostly resemble those of a Unix shell. Some noteworthy cases:

        - None
            The NsJail process failed to launch
        - 137 (SIGKILL)
            Typically because NsJail killed the Python process due to time or memory constraints
        - 255
            NsJail encountered a fatal error

        Request body:

        >>> {
        ...     "input": "print(1 + 1)"
        ... }

        Response format:

        >>> {
        ...     "stdout": "2\\n",
        ...     "returncode": 0
        ... }

        Status codes:

        - 200
            Successful evaluation; not indicative that the input code itself works
        - 400
           Input's JSON schema is invalid
        - 415
            Unsupported content type; only application/JSON is supported
        """
        code = req.media["input"]

        try:
            result = self.nsjail.python3(code)
        except Exception:
            log.exception(
                "An exception occurred while trying to process the request")
            raise falcon.HTTPInternalServerError

        resp.media = {"stdout": result.stdout, "returncode": result.returncode}