Skip to content

Commit

Permalink
change regex to filesystem policy matcher, first step of DMOJ#871
Browse files Browse the repository at this point in the history
  • Loading branch information
Riolku committed Aug 30, 2021
1 parent 69fb153 commit 0da9eb4
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 47 deletions.
7 changes: 6 additions & 1 deletion .freebsd.test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from dmoj import judgeenv
from dmoj.citest import ci_test

from dmoj.executors.filesystem_rules import FilesystemPolicy, File

EXECUTORS = [
'AWK',
'BF',
Expand Down Expand Up @@ -31,7 +33,10 @@

def main():
judgeenv.env['runtime'] = {}
judgeenv.env['extra_fs'] = {'PERL': ['/dev/dtrace/helper$'], 'RUBY2': ['/dev/dtrace/helper$']}
judgeenv.env['extra_fs'] = {
'PERL': FilesystemPolicy(File("/dev/dtrace/helper")),
'RUBY2': FilesystemPolicy(File("/dev/dtrace/helper"))
}

logging.basicConfig(level=logging.INFO)

Expand Down
12 changes: 7 additions & 5 deletions dmoj/cptbox/isolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from dmoj.cptbox.syscalls import *
from dmoj.utils.unicode import utf8text

from dmoj.executors.filesystem_rules import FilesystemPolicy

log = logging.getLogger('dmoj.security')
open_write_flags = [os.O_WRONLY, os.O_RDWR, os.O_TRUNC, os.O_CREAT, os.O_EXCL]

Expand Down Expand Up @@ -185,11 +187,9 @@ def __init__(self, read_fs, write_fs=None, writable=(1, 2)):

def _compile_fs_jail(self, fs):
if fs:
fs_re = '|'.join(fs)
return fs
else:
fs_re = '(?!)' # Disallow accessing everything by default.

return re.compile(fs_re)
return FilesystemPolicy()

def is_write_flags(self, open_flags):
for flag in open_write_flags:
Expand Down Expand Up @@ -258,7 +258,9 @@ def _file_access_check(self, rel_file, debugger, is_open, flag_reg=1, dirfd=AT_F

is_write = is_open and self.is_write_flags(getattr(debugger, 'uarg%d' % flag_reg))
fs_jail = self.write_fs_jail if is_write else self.read_fs_jail
if fs_jail.match(file) is None:
if fs_jail.check(file) is not True:
log.warning(f"Denied access to {file}.")
# log.warning(repr([ x.path for x in fs_jail.items ]))
return file, False
return file, True

Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/COFFEE.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from dmoj.executors.script_executor import ScriptExecutor

from .filesystem_rules import FilesystemPolicy, File

class Executor(ScriptExecutor):
ext = 'coffee'
Expand Down Expand Up @@ -38,7 +39,7 @@ def get_cmdline(self, **kwargs):
return [self.get_command(), self.runtime_dict['coffee'], self._code]

def get_fs(self):
return super().get_fs() + [self.runtime_dict['coffee'], self._code]
return super().get_fs() + FilesystemPolicy(File(self.runtime_dict['coffee']), File(self._code))

@classmethod
def get_versionable_commands(cls):
Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/DART.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dmoj.executors.compiled_executor import CompiledExecutor

from .filesystem_rules import FilesystemPolicy, Dir, File

# Running DART normally results in unholy memory usage
# Thankfully compiling it results in something...far more sane
Expand All @@ -16,7 +17,7 @@ class Executor(CompiledExecutor):
address_grace = 128 * 1024

syscalls = ['epoll_create', 'epoll_ctl', 'epoll_wait', 'timerfd_settime', 'memfd_create', 'ftruncate']
fs = ['.*/vm-service$']
fs = FilesystemPolicy()

def get_compile_args(self):
return [self.get_command(), '--snapshot=%s' % self.get_compiled_file(), self._code]
Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/FORTH.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dmoj.executors.script_executor import ScriptExecutor

from .filesystem_rules import FilesystemPolicy, Dir, File

class Executor(ScriptExecutor):
name = 'FORTH'
Expand All @@ -10,7 +11,7 @@ class Executor(ScriptExecutor):
HELLO
'''
fs = [r'/\.gforth-history$']
fs = FilesystemPolicy(File('/.gforth-history'))

def get_cmdline(self, **kwargs):
return [self.get_command(), self._code, '-e', 'bye']
3 changes: 2 additions & 1 deletion dmoj/executors/PERL.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from dmoj.executors.script_executor import ScriptExecutor

from .filesystem_rules import FilesystemPolicy, Dir

class Executor(ScriptExecutor):
ext = 'pl'
name = 'PERL'
command = 'perl'
fs = ['/etc/perl/.*?']
fs = FilesystemPolicy(Dir('/etc/perl/'))
test_program = 'print<>'
syscalls = ['umtx_op']

Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/PHP.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from dmoj.executors.script_executor import ScriptExecutor

from .filesystem_rules import FilesystemPolicy, Dir, File

class Executor(ScriptExecutor):
name = 'PHP'
ext = 'php'
command = 'php'
command_paths = ['php7', 'php5', 'php']

fs = [r'.*/php[\w-]*\.ini$', r'.*/conf.d/.*\.ini$']
fs = FilesystemPolicy()

test_program = '<?php while($f = fgets(STDIN)) echo $f;'

Expand Down
10 changes: 8 additions & 2 deletions dmoj/executors/RKT.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

from dmoj.executors.compiled_executor import CompiledExecutor

from .filesystem_rules import FilesystemPolicy, Dir, File

class Executor(CompiledExecutor):
ext = 'rkt'
name = 'RKT'
fs = [os.path.expanduser(r'~/\.racket/'), os.path.expanduser(r'~/\.local/share/racket/'),
'/etc/racket/.*?', '/etc/passwd$']

fs = FilesystemPolicy(
# Dir(os.path.expanduser('~/.racket/')),
# Dir(os.path.expanduser('~/.local/share/racket/')),
Dir("/etc/racket"),
File("/etc/passwd")
)

command = 'racket'

Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/RUBY2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from dmoj.executors.script_executor import ScriptExecutor

from .filesystem_rules import FilesystemPolicy, Dir, File

class Executor(ScriptExecutor):
ext = 'rb'
Expand All @@ -12,7 +13,7 @@ class Executor(ScriptExecutor):
nproc = -1
command_paths = ['ruby2.%d' % i for i in reversed(range(0, 8))] + ['ruby2%d' % i for i in reversed(range(0, 8))]
syscalls = ['thr_set_name', 'eventfd2']
fs = ['/proc/self/loginuid$']
fs = FilesystemPolicy(File('/proc/self/loginuid'))

def get_fs(self):
fs = super().get_fs()
Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/SWIFT.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from dmoj.executors.compiled_executor import CompiledExecutor

from .filesystem_rules import FilesystemPolicy, Dir, File

class Executor(CompiledExecutor):
ext = 'swift'
name = 'SWIFT'
command = 'swiftc'
fs = ['/lib']
fs = FilesystemPolicy(Dir('/lib'))
test_program = 'print(readLine()!)'

def get_compile_args(self):
Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/TUR.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from dmoj.executors.compiled_executor import CompiledExecutor
from dmoj.judgeenv import env

from .filesystem_rules import FilesystemPolicy, File

class Executor(CompiledExecutor):
ext = 't'
Expand All @@ -15,7 +16,7 @@ class Executor(CompiledExecutor):
'''

def get_fs(self):
return super().get_fs() + [self._code + 'bc']
return super().get_fs() + FilesystemPolicy(File(self._code + 'bc'))

def get_compile_args(self):
tprologc = self.runtime_dict['tprologc']
Expand Down
23 changes: 23 additions & 0 deletions dmoj/executors/filesystem_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class FilesystemPolicy:
def __init__(self, *items):
self.items = list(items)

def __add__(self, other):
return FilesystemPolicy(*(self.items + other.items))

def check(self, path):
return any(item.check(path) for item in self.items)

class Dir:
def __init__(self, path):
self.path = path

def check(self, path):
return path.startswith(self.path)

class File:
def __init__(self, path):
self.path = path

def check(self, path):
return self.path == path
6 changes: 4 additions & 2 deletions dmoj/executors/java_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from dmoj.judgeenv import skip_self_test
from dmoj.utils.unicode import utf8bytes, utf8text

from .filesystem_rules import FilesystemPolicy, File, Dir

recomment = re.compile(r'/\*.*?\*/', re.DOTALL | re.U)
restring = re.compile(r''''(?:\\.|[^'\\])'|"(?:\\.|[^"\\])*"''', re.DOTALL | re.U)
reinline_comment = re.compile(r'//.*?(?=[\r\n])', re.U)
Expand Down Expand Up @@ -81,10 +83,10 @@ def get_executable(self):
return self.get_vm()

def get_fs(self):
return super().get_fs() + [self._agent_file]
return super().get_fs() + FilesystemPolicy(File(self._agent_file))

def get_write_fs(self):
return super().get_write_fs() + [os.path.join(self._dir, 'submission_jvm_crash.log')]
return super().get_write_fs() + FilesystemPolicy(File(os.path.join(self._dir, 'submission_jvm_crash.log')))

def get_agent_flag(self):
agent_flag = '-javaagent:%s=policy:%s' % (self._agent_file, self._policy_file)
Expand Down
90 changes: 64 additions & 26 deletions dmoj/executors/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,76 @@
from dmoj.utils import setbufsize_path
from dmoj.utils.unicode import utf8bytes

BASE_FILESYSTEM = [
'/dev/(?:null|tty|zero|u?random)$',
'/usr/(?!home)',
'/lib(?:32|64)?/',
'/opt/',
'/etc$',
'/etc/(?:localtime|timezone)$',
'/usr$',
'/tmp$',
'/$',
]
BASE_WRITE_FILESYSTEM = ['/dev/stdout$', '/dev/stderr$', '/dev/null$']
from .filesystem_rules import FilesystemPolicy, Dir, File

BASE_FILESYSTEM = FilesystemPolicy(
File("/dev/null"),
File("/dev/tty"),
File("/dev/zero"),
File("/dev/urandom"),
File("/dev/random"),
*[ Dir(f"/usr/{d}") for d in os.listdir("/usr") if d != "home" ],

Dir("/lib"),
Dir("/lib32"),
Dir("/lib64"),
Dir("/opt"),

File("/etc"),
File("/etc/localtime"),
File("/etc/timezone"),

File("/usr"),
File("/tmp"),
File("/")
)

BASE_WRITE_FILESYSTEM = FilesystemPolicy(
File("/dev/stdout"),
File("/dev/stderr"),
File("/dev/null")
)

if 'freebsd' in sys.platform:
BASE_FILESYSTEM += [r'/etc/s?pwd\.db$', '/dev/hv_tsc$']
BASE_FILESYSTEM += FilesystemPolicy(
File("/etc/spwd.db"),
File("/etc/pwd.db"),
File("/dev/hv_tsc")
)
else:
BASE_FILESYSTEM += ['/sys/devices/system/cpu(?:$|/online)', '/etc/selinux/config$']
BASE_FILESYSTEM += FilesystemPolicy(
File("/sys/devices/system/cpu"),
Dir("/sys/devices/system/cpu/online"),
File("/etc/selinux/config")
)

if sys.platform.startswith('freebsd'):
BASE_FILESYSTEM += [r'/etc/libmap\.conf$', r'/var/run/ld-elf\.so\.hints$']
BASE_FILESYSTEM += FilesystemPolicy(
File("/etc/libmap.conf"),
File("/var/run/ld-elf.so.hints")
)
else:
# Linux and kFreeBSD mounts linux-style procfs.
BASE_FILESYSTEM += [
'/proc$',
'/proc/self/(?:maps|exe|auxv)$',
'/proc/self$',
'/proc/(?:meminfo|stat|cpuinfo|filesystems|xen|uptime)$',
'/proc/sys/vm/overcommit_memory$',
]
BASE_FILESYSTEM += FilesystemPolicy(
File("/proc"),
File("/proc/self/maps"),
File("/proc/self/exe"),
File("/proc/auxv"),
File("/proc/meminfo"),
File("/proc/stat"),
File("/proc/cpuinfo"),
File("/proc/filesystems"),
File("/proc/xen"),
File("/proc/uptime"),
File("/proc/sys/vm/overcommit_memory")
)

# Linux-style ld.
BASE_FILESYSTEM += [r'/etc/ld\.so\.(?:nohwcap|preload|cache)$']
BASE_FILESYSTEM += FilesystemPolicy(
File("/etc/ld.so.nohwcap"),
File("/etc/ld.so.preload"),
File("/etc/ld.so.cache")
)

UTF8_LOCALE = 'C.UTF-8'

Expand All @@ -56,8 +94,8 @@ class PlatformExecutorMixin(metaclass=abc.ABCMeta):
data_grace = 0
fsize = 0
personality = 0x0040000 # ADDR_NO_RANDOMIZE
fs: List[str] = []
write_fs: List[str] = []
fs: List[str] = FilesystemPolicy()
write_fs: List[str] = FilesystemPolicy()
syscalls: List[Union[str, Tuple[str, Any]]] = []

def _add_syscalls(self, sec):
Expand All @@ -75,7 +113,7 @@ def get_security(self, launch_kwargs=None):

def get_fs(self):
name = self.get_executor_name()
fs = BASE_FILESYSTEM + self.fs + env.get('extra_fs', {}).get(name, []) + [re.escape(self._dir)]
fs = BASE_FILESYSTEM + self.fs + env.get('extra_fs', {}).get(name, FilesystemPolicy()) + FilesystemPolicy(Dir(self._dir))
return fs

def get_write_fs(self):
Expand Down
3 changes: 2 additions & 1 deletion dmoj/executors/mono_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

reexception = re.compile(r'\bFATAL UNHANDLED EXCEPTION: (.*?):', re.U)

from .filesystem_rules import FilesystemPolicy, Dir, File

class MonoTracedPopen(TracedPopen):
def _cpu_time_exceeded(self):
Expand All @@ -32,7 +33,7 @@ class MonoExecutor(CompiledExecutor):
# get flagged as MLE.
data_grace = 65536
cptbox_popen_class = MonoTracedPopen
fs = ['/etc/mono/']
fs = FilesystemPolicy(Dir('/etc/mono/'))
# Mono sometimes forks during its crashdump procedure, but continues even if
# the call to fork fails.
syscalls = [
Expand Down
Loading

0 comments on commit 0da9eb4

Please sign in to comment.