def get_fs(self) -> List[FilesystemAccessRule]: home = self.runtime_dict.get('%s_home' % self.get_executor_name().lower()) fs = super().get_fs() + [ExactFile(self._code)] if home is not None: fs += [RecursiveDir(home)] return fs
class Executor(NullStdoutMixin, CompiledExecutor): ext = 'hs' command = 'ghc' compiler_read_fs = [ RecursiveDir('/proc/self/task'), RecursiveDir('/var/lib/ghc'), ] test_program = """\ main = do a <- getContents putStr a """ def get_compile_args(self): return [self.get_command(), '-O2', '-o', self.problem, self._code]
class Executor(StripCarriageReturnsMixin, CompiledExecutor): ext = 'zig' command = 'zig' compiler_time_limit = 30 compiler_read_fs = [ RecursiveDir('~/.cache'), ] compiler_write_fs = compiler_read_fs compiler_required_dirs = ['~/.cache'] test_program = """ const std = @import("std"); pub fn main() !void { const io = std.io; const stdin = std.io.getStdIn().inStream(); const stdout = std.io.getStdOut().outStream(); var line_buf: [50]u8 = undefined; while (try stdin.readUntilDelimiterOrEof(&line_buf, '\n')) |line| { if (line.len == 0) break; try stdout.print("{}", .{line}); } }""" def get_compile_args(self): return [ self.get_command(), 'build-exe', self._code, '--release-safe', '--name', self.problem ] @classmethod def get_version_flags(cls, command): return ['version']
class Executor(ScriptExecutor): ext = 'pl' command = 'perl' fs = [RecursiveDir('/etc/perl')] test_program = 'print<>' syscalls = ['umtx_op'] def get_cmdline(self, **kwargs): return ['perl', '-Mre=eval', self._code]
class Executor(CompiledExecutor): ext = 'rkt' name = 'RKT' fs = [RecursiveDir('/etc/racket'), ExactFile('/etc/passwd')] compiler_read_fs = [ RecursiveDir('/etc/racket'), RecursiveDir('~/.local/share/racket'), ] command = 'racket' syscalls = ['epoll_create', 'epoll_create1', 'epoll_wait', 'epoll_pwait'] # Racket SIGABRTs under low-memory conditions before actually crossing the memory limit, # so give it a bit of headroom to be properly marked as MLE. data_grace = 4096 address_grace = 131072 test_program = """\ #lang racket (displayln (read-line)) """ def get_compile_args(self): return [self.runtime_dict['raco'], 'make', self._code] def get_cmdline(self, **kwargs): return [self.get_command(), self._code] def get_executable(self): return self.get_command() @classmethod def initialize(cls): if 'raco' not in cls.runtime_dict: return False return super().initialize() @classmethod def get_versionable_commands(cls): return [('racket', cls.get_command())] @classmethod def get_find_first_mapping(cls): return {'racket': ['racket'], 'raco': ['raco']}
def get_fs(self) -> List[FilesystemAccessRule]: fs = super().get_fs() if self.use_qemu: assert self._executable is not None fs += [ ExactFile('/proc/sys/vm/mmap_min_addr'), RecursiveDir('/etc/qemu-binfmt'), ExactFile(self._executable), ] return fs
def test_recursive_dir(self): self.fs = FilesystemPolicy([RecursiveDir('/usr')]) self.checkTrue('/usr') self.checkTrue('/usr/lib') self.checkTrue('/usr/lib/a/b/c/d/e') self.checkFalse('/') self.checkFalse('/etc') self.checkFalse('/us') self.checkFalse('/usr2')
def get_fs(self): fs = super().get_fs() home = self.runtime_dict.get('%s_home' % self.get_executor_name().lower()) if home is not None: fs.append(RecursiveDir(home)) components = home.split('/') components.pop() while components and components[-1]: fs.append(ExactDir('/'.join(components))) components.pop() return fs
class Executor(CompiledExecutor): ext = 'swift' command = 'swiftc' compiler_read_fs = [ RecursiveDir('~/.cache'), ] compiler_write_fs = compiler_read_fs compiler_required_dirs = ['~/.cache'] test_program = 'print(readLine()!)' def get_compile_args(self): return [self.get_command(), self._code]
def get_fs(self) -> List[FilesystemAccessRule]: fs = (super().get_fs() + [ExactFile(self._agent_file)] + [ ExactDir(str(parent)) for parent in PurePath(self._agent_file).parents ]) vm = self.get_vm() assert vm is not None vm_parent = Path(os.path.realpath(vm)).parent.parent vm_config = Path( glob.glob(f'{vm_parent}/**/jvm.cfg', recursive=True)[0]) if vm_config.is_symlink(): fs += [RecursiveDir(os.path.dirname(os.path.realpath(vm_config)))] return fs
class Executor(CompiledExecutor): ext = 'rs' command = 'cargo' test_program = HELLO_WORLD_PROGRAM compiler_time_limit = 20 compiler_read_fs = [ RecursiveDir('/home'), ExactFile('/etc/resolv.conf'), ] compiler_write_fs = [ RecursiveDir('~/.cargo'), ] def create_files(self, problem_id, source_code, *args, **kwargs): os.mkdir(self._file('src')) with open(self._file('src', 'main.rs'), 'wb') as f: f.write(source_code) with open(self._file('Cargo.toml'), 'wb') as f: f.write(CARGO_TOML) with open(self._file('Cargo.lock'), 'wb') as f: f.write(CARGO_LOCK) @classmethod def get_versionable_commands(cls): return [('rustc', os.path.join(os.path.dirname(cls.get_command()), 'rustc'))] def get_compile_args(self): args = [self.get_command(), 'build', '--release'] if bool_env('DMOJ_CARGO_OFFLINE'): args += ['--offline'] return args def get_compiled_file(self): return self._file('target', 'release', 'user_submission')
class Executor(CompiledExecutor): ext = 'ml' name = 'OCAML' command = 'ocamlfind' compiler_read_fs = [ RecursiveDir('~/.opam'), ] test_program = """ open! Base open! Core open! Stdio let () = (In_channel.iter_lines Stdio.stdin ~f:print_endline) """ def get_compile_args(self): # fmt: off return [ self.runtime_dict['ocamlfind'], 'opt', '-package', 'str', '-package', 'base', '-package', 'core', '-package', 'stdio', '-package', 'zarith', '-thread', '-linkpkg', self._code, '-o', self.problem, ] # fmt: on @classmethod def get_version_flags(cls, command): return [('opt', '-version')] @classmethod def get_versionable_commands(cls): return [('ocaml', cls.get_command())]
class Executor(CompiledExecutor): ext = 'scm' name = 'SCM' command = 'chicken-csc' command_paths = ['chicken-csc', 'csc'] compiler_read_fs = [ RecursiveDir('/var/lib/chicken'), ] test_program = '(import chicken.io) (map print (read-lines))' def get_compile_args(self): return [self.get_command(), self._code] @classmethod def get_versionable_commands(cls): return (('csc', cls.get_command()), ) @classmethod def get_version_flags(cls, command): return ['-version']
class Executor(CompiledExecutor): ext = 'dart' name = 'DART' nproc = -1 # Dart uses a really, really large number of threads command = 'dart' compiler_read_fs = [ # Dart shells out... ExactFile('/bin/sh'), RecursiveDir('/proc/self/fd'), ] test_program = """ void main() { print("echo: Hello, World!"); } """ address_grace = 128 * 1024 syscalls = [ 'epoll_create', 'epoll_create1', 'epoll_ctl', 'epoll_wait', 'epoll_pwait', 'timerfd_settime', 'memfd_create', 'ftruncate', ] def get_compile_args(self): return [ self.get_command(), '--snapshot=%s' % self.get_compiled_file(), self._code ] def get_cmdline(self, **kwargs): return [self.get_command(), self.get_compiled_file()] def get_executable(self): return self.get_command()
class Executor(CompiledExecutor): ext = 'zig' name = 'ZIG' command = 'zig' compiler_read_fs = [ RecursiveDir('~/.cache'), ] compiler_write_fs = compiler_read_fs test_program = """ const std = @import("std"); pub fn main() !void { const io = std.io; const stdin = std.io.getStdIn().inStream(); const stdout = std.io.getStdOut().outStream(); var line_buf: [50]u8 = undefined; while (try stdin.readUntilDelimiterOrEof(&line_buf, '\n')) |line| { if (line.len == 0) break; try stdout.print("{}", .{line}); } }""" def create_files(self, problem_id, source_code, *args, **kwargs): # This cleanup needs to happen because Zig refuses to compile carriage returns. # See <https://github.com/ziglang/zig/issues/544>. source_code = source_code.replace(b'\r\n', b'\r').replace(b'\r', b'\n') super().create_files(problem_id, source_code, *args, **kwargs) def get_compile_args(self): return [ self.get_command(), 'build-exe', self._code, '--release-safe', '--name', self.problem ] @classmethod def get_version_flags(cls, command): return ['version']
class Executor(JavaExecutor): ext = 'scala' compiler = 'scalac' compiler_time_limit = 20 compiler_read_fs = [ ExactFile('/bin/uname'), ExactFile('/bin/readlink'), ExactFile('/bin/grep'), ExactFile('/bin/stty'), ExactFile('/bin/bash'), RecursiveDir('/etc/alternatives'), ] vm = 'scala_vm' test_program = """\ object self_test extends App { println("echo: Hello, World!") } """ def create_files(self, problem_id, source_code, *args, **kwargs): super().create_files(problem_id, source_code, *args, **kwargs) self._class_name = problem_id def get_cmdline(self, **kwargs): res = super().get_cmdline(**kwargs) # Simply run bash -x $(which scala) and copy all arguments after -Xmx and -Xms # and add it as a list in the configuration. res[-2:-1] = self.runtime_dict['scala_args'] return res def get_compile_args(self): return [self.get_compiler(), self._code] @classmethod def get_versionable_commands(cls): return [('scalac', cls.get_compiler()), ('java', cls.get_vm())] @classmethod def autoconfig(cls): result = {} for key, files in {'scalac': ['scalac'], 'scala': ['scala']}.items(): file = cls.find_command_from_list(files) if file is None: return result, False, 'Failed to find "%s"' % key result[key] = file scala = result.pop('scala') with open(os.devnull, 'w') as devnull: process = subprocess.Popen( ['bash', '-x', scala, '-usebootcp', '-version'], stdout=devnull, stderr=subprocess.PIPE) output = utf8text(process.communicate()[1]) log = [ i for i in output.split('\n') if 'scala.tools.nsc.MainGenericRunner' in i ] if not log: return result, False, 'Failed to parse: %s' % scala cmdline = log[-1].lstrip('+ ').split() result['scala_vm'] = cls.unravel_java( cls.find_command_from_list([cmdline[0]])) result['scala_args'] = [ i for i in cmdline[1:-1] if not i.startswith(('-Xmx', '-Xms')) ] data = cls.autoconfig_run_test(result) if data[1]: data = data[:2] + ('Using %s' % scala, ) + data[3:] return data
from dmoj.cptbox import IsolateTracer, TracedPopen, syscalls from dmoj.cptbox.filesystem_policies import ExactDir, ExactFile, FilesystemAccessRule, RecursiveDir from dmoj.cptbox.handlers import ALLOW from dmoj.error import InternalError from dmoj.judgeenv import env, skip_self_test from dmoj.result import Result from dmoj.utils import setbufsize_path from dmoj.utils.ansi import print_ansi from dmoj.utils.error import print_protection_fault from dmoj.utils.unicode import utf8bytes, utf8text version_cache: Dict[str, List[Tuple[str, Tuple[int, ...]]]] = {} if os.path.isdir('/usr/home'): USR_DIR = [ RecursiveDir(f'/usr/{d}') for d in os.listdir('/usr') if d != 'home' and os.path.isdir(f'/usr/{d}') ] else: USR_DIR = [RecursiveDir('/usr')] BASE_FILESYSTEM: List[FilesystemAccessRule] = [ ExactFile('/dev/null'), ExactFile('/dev/tty'), ExactFile('/dev/zero'), ExactFile('/dev/urandom'), ExactFile('/dev/random'), *USR_DIR, RecursiveDir('/lib'), RecursiveDir('/lib32'), RecursiveDir('/lib64'),
def get_fs(self) -> List[FilesystemAccessRule]: assert self._dir is not None return BASE_FILESYSTEM + self.fs + self._load_extra_fs() + [ RecursiveDir(self._dir) ]
def test_rule_type_check(self): self.assertRaises(AssertionError, FilesystemPolicy, [ExactFile('/usr/lib')]) self.assertRaises(AssertionError, FilesystemPolicy, [ExactDir('/etc/passwd')]) self.assertRaises(AssertionError, FilesystemPolicy, [RecursiveDir('/etc/passwd')])
def test_build_checks(self): self.assertRaises(AssertionError, FilesystemPolicy, [ExactFile('not/an/absolute/path')]) self.assertRaises(AssertionError, FilesystemPolicy, [ExactDir('/nota/./normalized/path')]) self.assertRaises(AssertionError, FilesystemPolicy, [RecursiveDir('')])
def compile_with_auxiliary_files( filenames: Sequence[str], flags: List[str] = [], lang: Optional[str] = None, compiler_time_limit: Optional[int] = None, unbuffered: bool = False, ) -> 'BaseExecutor': from dmoj.executors import executors from dmoj.executors.compiled_executor import CompiledExecutor sources = {} for filename in filenames: with open(filename, 'rb') as f: sources[os.path.basename(filename)] = f.read() def find_runtime(languages): for grader in languages: if grader in executors: return grader return None use_cpp = any( map(lambda name: os.path.splitext(name)[1] in ['.cpp', '.cc'], filenames)) use_c = any( map(lambda name: os.path.splitext(name)[1] in ['.c'], filenames)) if lang is None: best_choices = ('CPP20', 'CPP17', 'CPP14', 'CPP11', 'CPP03') if use_cpp else ('C11', 'C') lang = find_runtime(best_choices) executor = executors.get(lang) if not executor: raise IOError('could not find an appropriate C++ executor') executor = executor.Executor kwargs = {'fs': executor.fs + [RecursiveDir(tempfile.gettempdir())]} if issubclass(executor, CompiledExecutor): kwargs['compiler_time_limit'] = compiler_time_limit if hasattr(executor, 'flags'): kwargs['flags'] = flags + list(executor.flags) # Optimize the common case. if use_cpp or use_c: # Some auxiliary files (like those using testlib.h) take an extremely long time to compile, so we cache them. executor = executor('_aux_file', None, aux_sources=sources, cached=True, unbuffered=unbuffered, **kwargs) else: if len(sources) > 1: raise InternalError( 'non-C/C++ auxilary programs cannot be multi-file') executor = executor('_aux_file', list(sources.values())[0], cached=True, unbuffered=unbuffered, **kwargs) return executor
def __init__(self, *, tmpdir, read_fs, write_fs): read_fs += BASE_FILESYSTEM + [ RecursiveDir(tmpdir), ExactFile('/bin/strip'), RecursiveDir('/usr/x86_64-linux-gnu'), ] write_fs += BASE_WRITE_FILESYSTEM + [RecursiveDir(tmpdir)] super().__init__(read_fs=read_fs, write_fs=write_fs) self.update({ # Process spawning system calls sys_fork: ALLOW, sys_vfork: ALLOW, sys_execve: ALLOW, sys_getcpu: ALLOW, sys_getpgid: ALLOW, # Directory system calls sys_mkdir: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=0), sys_mkdirat: self.handle_file_access_at(FilesystemSyscallKind.WRITE, dir_reg=0, file_reg=1), sys_rmdir: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=0), # Linking system calls sys_link: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=1), sys_linkat: self.handle_file_access_at(FilesystemSyscallKind.WRITE, dir_reg=2, file_reg=3), sys_unlink: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=0), sys_unlinkat: self.handle_file_access_at(FilesystemSyscallKind.WRITE, dir_reg=0, file_reg=1), sys_symlink: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=1), # Miscellaneous other filesystem system calls sys_chdir: self.handle_file_access(FilesystemSyscallKind.READ, file_reg=0), sys_chmod: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=0), sys_utimensat: self.do_utimensat, sys_umask: ALLOW, sys_flock: ALLOW, sys_fsync: ALLOW, sys_fadvise64: ALLOW, sys_fchmodat: self.handle_file_access_at(FilesystemSyscallKind.WRITE, dir_reg=0, file_reg=1), sys_fchmod: self.handle_fchmod, sys_fallocate: ALLOW, sys_ftruncate: ALLOW, sys_rename: self.handle_rename, sys_renameat: self.handle_renameat, # I/O system calls sys_readv: ALLOW, sys_pwrite64: ALLOW, sys_sendfile: ALLOW, # Event loop system calls sys_epoll_create: ALLOW, sys_epoll_create1: ALLOW, sys_epoll_ctl: ALLOW, sys_epoll_wait: ALLOW, sys_epoll_pwait: ALLOW, sys_timerfd_settime: ALLOW, sys_eventfd2: ALLOW, sys_waitid: ALLOW, sys_wait4: ALLOW, # Network system calls, we don't sandbox these sys_socket: ALLOW, sys_socketpair: ALLOW, sys_connect: ALLOW, sys_setsockopt: ALLOW, sys_getsockname: ALLOW, sys_sendmmsg: ALLOW, sys_recvfrom: ALLOW, sys_sendto: ALLOW, # Miscellaneous other system calls sys_msync: ALLOW, sys_clock_nanosleep: ALLOW, sys_memfd_create: ALLOW, sys_rt_sigsuspend: ALLOW, }) # FreeBSD-specific syscalls if 'freebsd' in sys.platform: self.update({ sys_rfork: ALLOW, sys_procctl: ALLOW, sys_cap_rights_limit: ALLOW, sys_posix_fadvise: ALLOW, sys_posix_fallocate: ALLOW, sys_setrlimit: ALLOW, sys_cap_ioctls_limit: ALLOW, sys_cap_fcntls_limit: ALLOW, sys_cap_enter: ALLOW, sys_utimes: self.handle_file_access(FilesystemSyscallKind.WRITE, file_reg=0), })
class MonoExecutor(CompiledExecutor): name = 'MONO' nproc = -1 address_grace = 262144 # Give Mono access to 64mb more data segment memory. This is a hack, for # dealing with the fact that Mono behaves extremely poorly when handling # out-of-memory situations -- in many cases, it dumps an assertion to # standard error, then *exits with a 0 exit code*. Unfortunately, it will # only infrequently throw a System.OutOfMemoryException. # # The hope here is that if a problem allows X mb of memory and we cap at # X+64 mb, if an OOM situation occurs, it will occur at a point where # VmHWM >= X. Then, even if Mono exits poorly, the submission will still # get flagged as MLE. data_grace = 65536 cptbox_popen_class = MonoTracedPopen fs = [RecursiveDir('/etc/mono')] compiler_read_fs = fs # Mono sometimes forks during its crashdump procedure, but continues even if # the call to fork fails. syscalls = [ 'wait4', 'rt_sigsuspend', 'msync', 'fadvise64', 'clock_nanosleep', ('fork', ACCESS_EAGAIN), ] def get_env(self) -> Dict[str, str]: env = super().get_env() # Disable Mono's usage of /dev/shm, so we don't have to deal with # its extremely messy access patterns to it. env['MONO_DISABLE_SHARED_AREA'] = '1' # Disable Mono's generation of core dump files (e.g. on MLE). env['MONO_CRASH_NOFILE'] = '1' return env def get_compiled_file(self) -> str: return self._file('%s.exe' % self.problem) def get_cmdline(self, **kwargs) -> List[str]: assert self._executable is not None return ['mono', self._executable] def get_executable(self) -> str: return self.runtime_dict['mono'] @classmethod def get_find_first_mapping(cls) -> Optional[Dict[str, List[str]]]: res = super().get_find_first_mapping() if res: res['mono'] = ['mono'] return res def populate_result(self, stderr: bytes, result: Result, process: TracedPopen) -> None: super().populate_result(stderr, result, process) if process.is_ir and b'Garbage collector could not allocate' in stderr: result.result_flag |= Result.MLE def parse_feedback_from_stderr(self, stderr: bytes, process: TracedPopen) -> str: match = deque(reexception.finditer(utf8text(stderr, 'replace')), maxlen=1) if not match: return '' exception = match[0].group(1) return exception @classmethod def initialize(cls) -> bool: if 'mono' not in cls.runtime_dict or not os.path.isfile( cls.runtime_dict['mono']): return False return super().initialize()