Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fchmod, rename(at) sandboxing for compilers #933

Merged
merged 4 commits into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions dmoj/cptbox/isolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,25 @@ def is_write_flags(self, open_flags: int) -> bool:

return False

@staticmethod
def read_path(syscall: str, debugger: Debugger, ptr: int):
try:
file = debugger.readstr(ptr)
except MaxLengthExceeded as e:
log.warning('Denied access via syscall %s to overly long path: %r', syscall, e.args[0])
return None, ACCESS_ENAMETOOLONG(debugger)
except UnicodeDecodeError as e:
log.warning('Denied access via syscall %s to path with invalid unicode: %r', syscall, e.object)
return None, ACCESS_ENOENT(debugger)
return file, None

def check_file_access(self, syscall, argument, is_write=None, is_open=False) -> HandlerCallback:
assert is_write is None or not is_open

def check(debugger: Debugger) -> bool:
file_ptr = getattr(debugger, 'uarg%d' % argument)
try:
file = debugger.readstr(file_ptr)
except MaxLengthExceeded as e:
log.warning('Denied access via syscall %s to overly long path: %r', syscall, e.args[0])
return ACCESS_ENAMETOOLONG(debugger)
except UnicodeDecodeError as e:
log.warning('Denied access via syscall %s to path with invalid unicode: %r', syscall, e.object)
return ACCESS_ENOENT(debugger)
file, error = self.read_path(syscall, debugger, getattr(debugger, 'uarg%d' % argument))
if error is not None:
return error

file, error = self._file_access_check(file, debugger, is_open, is_write=is_write)
if not error:
Expand All @@ -230,15 +236,9 @@ def check(debugger: Debugger) -> bool:

def check_file_access_at(self, syscall, argument=1, is_open=False, is_write=None) -> HandlerCallback:
def check(debugger: Debugger) -> bool:
file_ptr = getattr(debugger, 'uarg%d' % argument)
try:
file = debugger.readstr(file_ptr)
except MaxLengthExceeded as e:
log.warning('Denied access via syscall %s to overly long path: %r', syscall, e.args[0])
return ACCESS_ENAMETOOLONG(debugger)
except UnicodeDecodeError as e:
log.warning('Denied access via syscall %s to path with invalid unicode: %r', syscall, e.object)
return ACCESS_ENOENT(debugger)
file, error = self.read_path(syscall, debugger, getattr(debugger, 'uarg%d' % argument))
if error is not None:
return error

file, error = self._file_access_check(
file, debugger, is_open, is_write=is_write, dirfd=debugger.arg0, flag_reg=2
Expand Down
46 changes: 40 additions & 6 deletions dmoj/executors/compiled_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pylru

from dmoj.cptbox import IsolateTracer, TracedPopen
from dmoj.cptbox._cptbox import AT_FDCWD
from dmoj.cptbox._cptbox import AT_FDCWD, Debugger
from dmoj.cptbox.filesystem_policies import ExactFile, FilesystemAccessRule, RecursiveDir
from dmoj.cptbox.handlers import ACCESS_EFAULT, ACCESS_EPERM, ALLOW
from dmoj.cptbox.syscalls import *
Expand Down Expand Up @@ -118,13 +118,11 @@ def handle_execve(debugger):
sys_fsync: ALLOW,
sys_fadvise64: ALLOW,
sys_fchmodat: self.check_file_access_at('fchmodat', is_write=True),
# FIXME: this allows changing any FD that is open, not just RW ones.
sys_fchmod: ALLOW,
sys_fchmod: self.do_fchmod,
sys_fallocate: ALLOW,
sys_ftruncate: ALLOW,
# FIXME: this doesn't validate the source nor target
sys_rename: ALLOW,
sys_renameat: ALLOW,
sys_rename: self.do_rename,
sys_renameat: self.do_renameat,
# I/O system calls
sys_readv: ALLOW,
sys_pwrite64: ALLOW,
Expand Down Expand Up @@ -202,6 +200,42 @@ def on_return():

return self.check_file_access_at('utimensat')(debugger)

def do_fchmod(self, debugger: Debugger) -> bool:
path = self._getfd_pid(debugger.tid, debugger.uarg0)
return True if self.write_fs_jail.check(path) else ACCESS_EPERM(debugger)

def do_rename(self, debugger: Debugger) -> bool:
old_path, old_path_error = self.read_path('rename', debugger, debugger.uarg0)
if old_path_error is not None:
return old_path_error

new_path, new_path_error = self.read_path('rename', debugger, debugger.uarg1)
if new_path_error is not None:
return new_path_error

if not self._file_access_check(old_path, debugger, is_write=True, is_open=False):
return ACCESS_EPERM(debugger)
if not self._file_access_check(new_path, debugger, is_write=True, is_open=False):
return ACCESS_EPERM(debugger)

return True

def do_renameat(self, debugger: Debugger) -> bool:
old_path, old_path_error = self.read_path('renameat', debugger, debugger.uarg1)
if old_path_error is not None:
return old_path_error

new_path, new_path_error = self.read_path('renameat', debugger, debugger.uarg3)
if new_path_error is not None:
return new_path_error

if not self._file_access_check(old_path, debugger, is_write=True, is_open=False, dirfd=debugger.uarg0):
return ACCESS_EPERM(debugger)
if not self._file_access_check(new_path, debugger, is_write=True, is_open=False, dirfd=debugger.uarg2):
return ACCESS_EPERM(debugger)

return True


class CompiledExecutor(BaseExecutor, metaclass=_CompiledExecutorMeta):
executable_size = env.compiler_size_limit * 1024
Expand Down