Skip to content

Commit

Permalink
Add basic PCRE matching for open-like system calls
Browse files Browse the repository at this point in the history
  • Loading branch information
quantum5 committed Apr 10, 2019
1 parent 1f5324d commit 07cea12
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 3 deletions.
17 changes: 17 additions & 0 deletions dmoj/cptbox/_cptbox.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ cdef extern from 'ptbox.h' nogil:
double wall_clock_time()
const rusage *getrusage()
bint was_initialized()
bint set_re_fs_read(char *pattern, int length, char *error, int error_length, int *offset)

cdef bint PTBOX_FREEBSD
cdef bint PTBOX_SECCOMP
Expand Down Expand Up @@ -341,13 +342,15 @@ cdef class Process:
cdef public unsigned int _cpu_time
cdef public int _nproc
cdef unsigned long _max_memory
cdef bytes _re_fs_read

def __cinit__(self, int debugger, debugger_type, *args, **kwargs):
self._child_memory = self._child_address = 0
self._child_stdin = self._child_stdout = self._child_stderr = -1
self._cpu_time = 0
self._nproc = -1
self._signal = 0
self._re_fs_read = None

self._debugger = get_ptdebugger(debugger)
if not self._debugger:
Expand Down Expand Up @@ -442,6 +445,20 @@ cdef class Process:
self._exited = True
return self._exitcode

property re_fs_read:
def __get__(self):
return self._re_fs_read

def __set__(self, bytes regex):
cdef char error[128]
cdef int offset
if not self.process.set_re_fs_read(regex, len(regex), error, 128, &offset):
if offset >= 0:
raise RuntimeError('%s at %d' % ((<bytes>error).decode('utf-8', 'replace'), offset))
else:
raise RuntimeError((<bytes>error).decode('utf-8', 'replace'))
self._re_fs_read = regex

property was_initialized:
def __get__(self):
return self.process.was_initialized()
Expand Down
3 changes: 2 additions & 1 deletion dmoj/cptbox/chroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys

from dmoj.cptbox._cptbox import bsd_get_proc_cwd, bsd_get_proc_fdno, AT_FDCWD
from dmoj.cptbox.handlers import ALLOW, ACCESS_DENIED, ACCESS_ENOENT
from dmoj.cptbox.handlers import ALLOW, ACCESS_DENIED, ACCESS_ENOENT, OPEN
# noinspection PyUnresolvedReferences
from dmoj.cptbox.syscalls import *
from dmoj.utils.unicode import utf8text
Expand Down Expand Up @@ -183,6 +183,7 @@ def check(debugger):
return True
log.info('Denied access via syscall %s: %s', syscall, file)
return ACCESS_ENOENT(debugger)
check.with_handler = OPEN
return check

def check_file_access_at(self, syscall, is_open=False):
Expand Down
3 changes: 2 additions & 1 deletion dmoj/cptbox/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
ALLOW = 1
_CALLBACK = 2
STDOUTERR = 3

OPEN = 4
OPENAT = 5

def errno_handler(code):
def handler(debugger):
Expand Down
22 changes: 22 additions & 0 deletions dmoj/cptbox/ptbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@

#include <map>

#ifndef PTBOX_NO_PCRE
# define PTBOX_PCRE 1
#else
# define PTBOX_PCRE 0
#endif

#if PTBOX_PCRE
# define PCRE2_CODE_UNIT_WIDTH 8
# include <pcre2.h>
#endif

#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
# define PTBOX_FREEBSD 1
#else
Expand Down Expand Up @@ -59,6 +70,8 @@
#define PTBOX_HANDLER_ALLOW 1
#define PTBOX_HANDLER_CALLBACK 2
#define PTBOX_HANDLER_STDOUTERR 3
#define PTBOX_HANDLER_OPEN 4
#define PTBOX_HANDLER_OPENAT 5

#define PTBOX_EVENT_ATTACH 0
#define PTBOX_EVENT_EXITING 1
Expand Down Expand Up @@ -105,6 +118,7 @@ typedef int (*pt_event_callback)(void *context, int event, unsigned long param);
class pt_process {
public:
pt_process(pt_debugger *debugger);
~pt_process();
void set_callback(pt_handler_callback, void *context);
void set_event_proc(pt_event_callback, void *context);
int set_handler(int syscall, int handler);
Expand All @@ -117,10 +131,12 @@ class pt_process {
double wall_clock_time();
const rusage *getrusage() { return &_rusage; }
bool was_initialized() { return _initialized; }
bool set_re_fs_read(const char *pattern, int length, char *error, int error_length, int *offset);
protected:
int dispatch(int event, unsigned long param);
int protection_fault(int syscall);
private:
bool handle_open_call();
pid_t pid;
int handler[MAX_SYSCALL];
pt_handler_callback callback;
Expand All @@ -132,6 +148,12 @@ class pt_process {
void *event_context;
bool _trace_syscalls;
bool _initialized;
#if PTBOX_PCRE
pcre2_code *re_fs_read;
pcre2_match_data *re_fs_read_data;
pcre2_match_context *re_fs_read_context;
pcre2_jit_stack *re_fs_read_jit;
#endif
};

class pt_debugger {
Expand Down
100 changes: 100 additions & 0 deletions dmoj/cptbox/ptproc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ void pt_free_process(pt_process *process) {
pt_process::pt_process(pt_debugger *debugger) :
pid(0), callback(NULL), context(NULL), debugger(debugger),
event_proc(NULL), event_context(NULL), _trace_syscalls(true),
#if PTBOX_PCRE
re_fs_read(NULL), re_fs_read_data(NULL), re_fs_read_context(NULL),
re_fs_read_jit(NULL),
#endif
_initialized(false)
{
memset(&exec_time, 0, sizeof exec_time);
Expand All @@ -36,6 +40,15 @@ pt_process::pt_process(pt_debugger *debugger) :
debugger->set_process(this);
}

pt_process::~pt_process() {
#if PTBOX_PCRE
if (re_fs_read) pcre2_code_free(re_fs_read);
if (re_fs_read_data) pcre2_match_data_free(re_fs_read_data);
if (re_fs_read_context) pcre2_match_context_free(re_fs_read_context);
if (re_fs_read_jit) pcre2_jit_stack_free(re_fs_read_jit);
#endif
}

double pt_process::wall_clock_time() {
struct timespec now, delta;

Expand Down Expand Up @@ -67,6 +80,59 @@ int pt_process::set_handler(int syscall, int handler) {
return 0;
}

bool pt_process::set_re_fs_read(const char *pattern, int length, char *error,
int error_length, int *offset) {
int errcode;
PCRE2_SIZE erroffset;

re_fs_read = pcre2_compile((PCRE2_SPTR8) pattern, length, 0, &errcode, &erroffset, NULL);
if (re_fs_read == NULL) {
*offset = (int) erroffset;
pcre2_get_error_message(errcode, (PCRE2_UCHAR8*) error, error_length);
return false;
}

re_fs_read_context = pcre2_match_context_create(NULL);
if (re_fs_read_context == NULL) {
*error = '\0';
strncat(error, "failed to create match context", error_length - 1);
*offset = -1;
pcre2_code_free(re_fs_read);
re_fs_read = NULL;
return false;
}

re_fs_read_jit = pcre2_jit_stack_create(32*1024, 512*1024, NULL);
if (re_fs_read_jit == NULL) {
*error = '\0';
strncat(error, "failed to create JIT stack", error_length - 1);
*offset = -1;
pcre2_match_context_free(re_fs_read_context);
pcre2_code_free(re_fs_read);
re_fs_read_context = NULL;
re_fs_read = NULL;
return false;
}

pcre2_jit_stack_assign(re_fs_read_context, NULL, re_fs_read_jit);

re_fs_read_data = pcre2_match_data_create_from_pattern(re_fs_read, NULL);
if (re_fs_read_data == NULL) {
*error = '\0';
strncat(error, "failed to create match data", error_length - 1);
*offset = -1;
pcre2_jit_stack_free(re_fs_read_jit);
pcre2_match_context_free(re_fs_read_context);
pcre2_code_free(re_fs_read);
re_fs_read_jit = NULL;
re_fs_read_context = NULL;
re_fs_read = NULL;
return false;
}

return true;
}

int pt_process::dispatch(int event, unsigned long param) {
if (event_proc != NULL)
return event_proc(event_context, event, param);
Expand Down Expand Up @@ -103,6 +169,34 @@ int pt_process::protection_fault(int syscall) {
return PTBOX_EXIT_PROTECTION;
}

bool pt_process::handle_open_call() {
#if PTBOX_PCRE
if (!re_fs_read) {
return false;
}

unsigned long file_ptr = (unsigned long) debugger->arg0();
char *file = debugger->readstr(file_ptr, 4096);
int result;

printf("Opening file: %s\n", file);
result = pcre2_match(re_fs_read, (PCRE2_SPTR8) file, PCRE2_ZERO_TERMINATED,
0, 0, re_fs_read_data, re_fs_read_context);
debugger->freestr(file);

if (result >= 0) {
return true;
} else if (result == PCRE2_ERROR_NOMATCH) {
return false;
} else {
fprintf(stderr, "PCRE2 match error: %d\n", result);
return false;
}
#else
return false;
#endif
}

int pt_process::monitor() {
bool in_syscall = false, first = true, spawned = false, execve_allowed = true;
struct timespec start, end, delta;
Expand Down Expand Up @@ -275,6 +369,12 @@ int pt_process::monitor() {
//printf("Killed by callback: %d\n", syscall);
exit_reason = protection_fault(syscall);
continue;
case PTBOX_HANDLER_OPEN:
if (!handle_open_call() && !callback(context, syscall)) {
exit_reason = protection_fault(syscall);
continue;
}
break;
default:
// Default is to kill, safety first.
//printf("Killed by DISALLOW or None: %d\n", syscall);
Expand Down
5 changes: 4 additions & 1 deletion dmoj/cptbox/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,12 @@ def __init__(self, debugger, _, args, executable=None, security=None, time=0, me
if not callable(handler):
raise ValueError('Handler not callable: ' + handler)
self._callbacks[call] = handler
handler = _CALLBACK
handler = getattr(handler, 'with_handler', _CALLBACK)
self._handler(call, handler)

if hasattr(security, 'fs_jail'):
self.re_fs_read = utf8bytes(security.fs_jail.pattern)

self._started = threading.Event()
self._died = threading.Event()
if time:
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

# Allow manually disabling seccomp on old kernels. WSL doesn't have seccomp.
has_seccomp = not is_wsl and os.environ.get('DMOJ_USE_SECCOMP') != 'no'
has_pcre = os.environ.get('DMOJ_USE_PCRE') != 'no'

try:
from Cython.Build import cythonize
Expand Down Expand Up @@ -127,6 +128,8 @@ def unavailable(self, e):

if has_seccomp:
libs += ['seccomp']
if has_pcre:
libs += ['pcre2-8']
if sys.platform.startswith('freebsd'):
libs += ['procstat']

Expand Down

0 comments on commit 07cea12

Please sign in to comment.